目录
RocketMq
基础组成
消息生产者(Producer
)
- 同步发送 - 需要Broker返回确认信息 (重要通知消息)
- 异步发送 - 需要Broker返回确认信息 (链路耗时较长而对响应时间敏感)
- 单向发送 (可靠性要求并不高)
消息消费者(Consumer
) - ACK机制是发生在Consumer端
消费模型由Consumer决定,消费维度为Topic。
- 集群消费(一条消息只会被同Group中的一个Consumer消费)
- 广播消费(消息将对一 个Consumer Group 下的各个 Consumer 实例都消费一遍。)
RocketMQ没有真正意义的push,都是pull。push采用的是长轮询机制
- pull(拉取式消费)主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。
- push(推动式消费-长轮询机制)Broker收到数据后会主动推送给消费端,该消费模式一般实时性较高。(难以根据消费者的状态控制推送速率,适用于消息量不大、消费能力强要求实时性高的情况下。)
代理服务器(Broker Server
)
- 消息中转角色,负责存储消息、转发消息。
- 每个Broker节点,在启动时,都会遍历NameServer列表,与
所有的NameServer建立长连接
,注册自己的信息,之后定时上报
从物理结构上看 Broker 的集群部署方式有四种:
单 Master
多 Master
单台机器宕机期间,该机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受影响。
多 Master 多 Slave(异步复制)
消息采用异步复制方式,主备之间有毫秒级消息延迟。这种方式优点是消息丢失的非常少,且消息实时性不会受影响,Master 宕机后消费者可以继续从 Slave 消费,中间的过程对用户应用程序透明,不需要人工干预,性能同多 Master 方式几乎一样。缺点是 Master 宕机时在磁盘损坏情况下会丢失极少量消息。
多 Master多 Slave(异步双写)
消息采用同步双写方式,主备都写成功才返回成功。优点是数据与服务都没有单点问题,Master 宕机时消息无延迟,服务与数据的可用性非常高。缺点是性能相对异步复制方式略低,发送消息的延迟会略高。
名字服务(Name Server
)-(路由管理,路由注册,服务发现)
RocketMq中的NameServer是一个无状态
(zookeeper有状态)的命名服务(相关组件的状态在内存中),类似于dubbo中的zookeeper。生产者或消费者能够通过NameServer查找各主题相应的Broker IP列表。多个Namesrve实例组成集群,但相互独立,没有信息交换
。
一般NameServer先启动,broker后启动,启动后向所有的NameServer注册,produce发送消息时,会先从NameServer获取可用的broker地址,根据负载算法选择一个并发送。
主题(Topic) - 每个主题包含若干条消息,每条消息只能属于一个主题。
Topic是个抽象的概念,每个Topic底层对应N个queue,而数据也真实存在queue上的。
消费保证
- 发送成功后返回consume_success
- 回溯消费
RocketMQ顺序消息
大部分逻辑依赖客户端,也就是Producer和Consumer。
- RocketMQ的做法是有顺序关系的消息都发送到同一个queue上,自然他们也会存到同一个broker上。根据之前讲的broker消息的存储逻辑,同一个queue的消息,先到的肯定放在前面,所以只要客户端在发送的时候使用单线程,发完一条再发另一条,消息在broker上保存的顺序自然也是按发送的顺序。
- Broker不知道消息的状态,那就把保证顺序这件事交给Consumer,因为第一步中有顺序关系的消息已经在同一个queue里了,consumer拿消息的时候本来也是按照存的顺序来的,所以Broker不需要做任何特殊逻辑。
RocketMQ去重
MsgId
- ip地址决定了分布式作业环境下生产的id值唯一
- 进程id决定了单机上多个客户端实例间生产的id值唯一
- count作为原子Integer类型,决定了单实例运行时高并发下生产的值唯一
- time 乃当前时间戳 - 当月开始时间戳的long值,保证应用月内重启不会重复。
offsetMsgId
服务端ip地址+服务端消息的物理分区偏移量来达到唯一值id。
1、极为严谨的业务必须业务幂等。
2、宽松业务可以考虑使用OffsetMsgId作为去重id。
Rocketmq分布式事务
分布式系统中的事务可以使用TCC(Try、Confirm、Cancel)、2pc来解决分布式系统中的消息原子性。
Prepared阶段(预备阶段)
该阶段主要发一个消息到rocketmq,但该消息只储存在commitlog中,但consumeQueue中不可见,也就是消费端(订阅端)无法看到此消息。
commit/rollback阶段(确认阶段)
该阶段主要是把prepared消息保存到consumeQueue中,即让消费端可以看到此消息,也就是可以消费此消息。
1、在扣款之前,先发送预备消息 (第一步先给 Broker 发送事务消息即半消息,并且 RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令)
2、发送预备消息成功后,执行本地扣款事务
3、扣款成功后,再发送确认消息
4、消息端(加钱业务)可以看到确认消息,消费此消息,进行加钱
- 如果发送预备消息成功,执行本地事务成功,但发送确认消息失败;这个就有问题了,因为用户A扣款成功了,但加钱业务没有订阅到确认消息,无法加钱。这里出现了数据不一致。
那RocketMq是怎么解决的呢?-状态回查
因为预备消息最终肯定会变为commit消息或Rollback消息,所以遍历预备消息去回查本地业务的执行状态,如果发现本地业务没有执行成功就rollBack,如果执行成功就发送commit消息。
@RocketMQTransactionListener(txProducerGroup = "tx-add-bonus-group")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AddBonusTransactionListener implements RocketMQLocalTransactionListener {
private final ShareService shar