RocketMQ
是一个 队列模型 的消息中间件,具有高性能、高可靠、高实时、分布式 的特点。它是一个采用 Java
语言开发的分布式的消息系统,由阿里巴巴团队开发,在 2016 年底贡献给 Apache
,成为了 Apache
的一个顶级项目。 在阿里内部,RocketMQ
很好地服务了集团大大小小上千个应用,在每年的双十一当天,更有不可思议的万亿级消息通过 RocketMQ
流转。
RocketMQ中的消息模型
RocketMQ
中的消息模型按 主题模型 实现的。对主题模型的实现,每个消息中间件的底层设计不同,就比如 Kafka
中的 分区 ,RocketMQ
中的 队列 ,RabbitMQ
中的 Exchange。
RocketMQ中的角色:
Producer Group
生产者组:代表某一类的生产者,比如我们有多个秒杀系统作为生产者,这多个合在一起就是一个Producer Group
生产者组,它们一般生产相同的消息。Consumer Group
消费者组:代表某一类的消费者,比如我们有多个短信系统作为消费者,这多个合在一起就是一个Consumer Group
消费者组,它们一般消费相同的消息。Topic
主题:代表一类消息,比如订单消息,物流消息等等
每个消费组在每个队列上维护一个消费位置。发布订阅模式中一般会涉及到多个消费者组,而每个消费者组在每个队列中的消费位置都是不同的。
一个主题有多个队列以提高并发能力。RocketMQ
通过使用在一个 Topic
中配置多个队列并且每个队列维护每个消费者组的消费位置 实现了 主题模式/发布订阅模式。
RocketMQ架构
RocketMQ
技术架构中有四大角色 NameServer
、Broker
、Producer
、Consumer。
Broker:存数据
一个 Topic
分布在多个 Broker
上,一个 Broker
可以配置多个 Topic
,它们是多对多的关系。
NameServer:一个注册中心,主要提供两个功能:Broker 管理 和 路由信息管理。
Producer:生产消息
Consumer:消费消息
Broker
需保证高可用,只使用一个broker压力会很大,需要使用多个 Broker
来保证负载均衡。消费者生产者直接和多个 Broker
相连,当 Broker
修改时,会牵连着每个生产者和消费者,产生耦合。
Broker
做了集群且进行主从部署 ,由于消息分布在各个 Broker
上,一旦某个 Broker
宕机,则该Broker
的消息读写会受到影响。 Rocketmq
提供了 master/slave
的结构,salve
定时从 master
同步数据,如果 master
宕机,则 slave
提供消费服务,但是不能写入消息。
NameServer
也做集群部署,以保证 HA
,但 去中心化 ,即没有主节点。 RocketMQ
中, 单个 Broker 和所有 NameServer 保持长连接 ,并且每隔 30 秒 Broker
会向所有 Nameserver
发心跳,心跳包含自身的 Topic
配置信息 。
在生产者向 Broker
发送消息,先从 NameServer
获取关于 Broker
的路由信息,然后通过 轮询向每个队列中生产数据以保证负载均衡。
消费者通过 NameServer
获取所有 Broker
的路由信息,向 Broker
发送 Pull
请求来获取消息数据。Consumer
可以以两种模式启动—— 广播(Broadcast)和集群(Cluster)。广播模式下,一条消息会发送给 同一个消费组中的所有消费者 ,集群模式下消息只会发送给一个消费者。
生产者和消费者分组
生产者分组:RocketMQ 5.x 版本,生产者是匿名的,无需管理生产者分组;历史版本3.x 和 4.x,已经使用的生产者分组可以废弃无需再设置,不会对当前业务产生影响。
消费者分组:是多个消费行为一致的消费者的负载均衡分组。
消费者分组中的订阅关系、投递顺序性、消费重试策略是一致的。
- 订阅关系:Apache RocketMQ 以消费者分组的粒度管理订阅关系,实现订阅关系的管理和追溯。
- 投递顺序性:Apache RocketMQ 的服务端将消息投递给消费者消费时,支持顺序投递和并发投递,投递方式在消费者分组中统一配置。
- 消费重试策略: 消费者消费消息失败时的重试策略,包括重试次数、死信队列设置等
顺序消费
RocketMQ
在主题上是无序的,只在队列层面保证有序。
普通顺序是指消费者通过同一个消费队列收到的消息是有顺序的 ,不同消息队列收到的消息则可能是无顺序的。普通顺序消息在 Broker
重启情况下不会保证消息顺序性 (短暂时间)。
严格顺序是指消费者收到的所有消息均是有顺序的。严格顺序消息即使在异常情况下也会保证消息的顺序性 。
一般而言, MQ
能容忍短暂乱序,推荐使用普通顺序。
普通顺序模式时: Producer
生产消息,会进行轮询来向同一主题的不同消息队列发消息。若此时有几个消息分别是同一个订单的创建、支付、发货,在轮询的策略下,这三个消息会被发送到不同队列,导致无法使用一个队列的有序特性来保证消息有序。
解决方法:将同一语义下的消息放入同一个队列(比如这里是同一个订单),使用 Hash 取模法 来保证同一个订单在同一个队列中。
发送异常:
选择队列后会与 Broker 建立连接,通过网络请求将消息发送到 Broker 上,如果 Broker 挂了或者网络波动发送消息超时此时 RocketMQ 会进行重试。
重新选择其他 Broker 中的消息队列进行发送,默认重试两次,可以手动设置
producer.setRetryTimesWhenSendFailed(5);
消息过大:
消息超过 4k 时 RocketMQ 会将消息压缩后在发送到 Broker 上,减少网络资源的占用。
重复消费
问题:有一个积分系统,负责为用户加积分。消息队列发给订单系统 AAA 的订单信息,要求是给 AAA 的积分加上 500。积分系统收到 AAA 的订单信息,处理完后,返回处理成功信息给消息队列时,出现网络波动(或 Broker 意外重启等),这条回应没有发送成功。消息队列没收到积分系统的回应会尝试重发消息,导致又给 AAA 的账户加上 500 积分。
解决:给消费者实现 幂等 ,即对同一个消息的处理结果,执行多少次都不变。
如何实现:根据特定场景使用特定的解决方案。可以使用 写入 Redis
来保证,因为 Redis
的 key
和 value
天然支持幂等。还可使用 数据库插入法 ,基于数据库的唯一键来保证重复数据不会被插入多条。