一、消息队列典型的应用场景
1、系统解耦
场景说明---当用户下单时,订单系统通知库存系统完成后续操作。

订单系统:用户下单后,订单系统完成内部处理流程,将消息投递至MQ,返回用户下单成功。
库存系统:订阅MQ的消息,进行库存扣减等操作。即使在用户下单时库存系统宕机,也不影响用户完成下单流程,实现订单系统与库存系统的解耦。
2、流量削峰
场景说明:秒杀活动,面对用户高并发同步请求,应用存在宕机风险,如图2所示。

秒杀业务系统收到用户请求后,首先将请求写入消息队列,然后根据自己的处理能力拉取消息进行处理。
二、RocketMQ整体架构
RocketMQ的整体架构主要包含服务发现、生产者、Broker集群和消费者,如图3所示。

Name Server:Broker向NameServer注册路由,NameServer为生产者、消费者提供最新的路由信息。
Broker:负责消息的持久化存储、消息的HA机制以及服务器端消息过滤等功能。一个Master Broker可以有多个Slave Broker,一个Slave Broker只能有一个Master Broker。Broker启动后将自己注册到Name Server,定期向Name Server上报Topic路由信息。
Producer:生产者与Name Server集群中的某个节点(随机)建立长连接,定期从Name Server读取Topic路由信息,并与提供Topic服务的Master Broker建立长连接,且定时向Master Broker发送心跳。
Consumer:消费者与Name Server集群中的某个节点(随机)建立长连接,定期从Name Server拉取Topic路由信息,并与提供Topic服务的Master Broker、Slave Broker建立长连接,且定时向Master Broker、Slave Broker发送心跳。Consumer既可以从Master Broker订阅消息,也可以从Slave Broker订阅消息。
三、RocketMQ消息发送与消费流程
消息发送与消费流程如下:首先生产者发送的消息被顺序写入CommitLog,如图4所示。此处在写CommitLog时使用PageCache会非常高效。

然后,CommitLog日志消息被异步转发到对应的逻辑队列(ConsumeQueue),如图5所示。

ConsumeQueue与Paratiton的概念相对应,并且转发至ConsumeQueue中的消息也是顺序写入的,消费者可以从ConsumeQueue中拉取消息进行消费,如图6所示。从旧到新顺序拉取消息,这里也可以使用PageCache。ConsumeQueue与消费者组中消费者数量的增多会显著提高消费速度。消费者直接和逻辑队列接触而不是commitlog。

ConsumeQueue消息结构包含三个部分:消息在CommitLog中的物理位置偏移量offset、消息实体内容的大小和Message Tag的hash值。我们可以根据offset定位消息在CommitLog文件中的具体位置,该过程是随机读,如图7所示。

虽然多个队列共享 CommitLog 导致回读是逻辑随机读,但只要写入与消费节奏相近,物理读取区域会高度集中,从而充分利用 PageCache,效果接近顺序读。
四、消息类型:顺序消息、延迟消息、事务消息 、死信消息
1、顺序消息
全局顺序消息
全局顺序消息如图9所示,对于一个指定的Topic,所有消息严格按照先入先出(FIFO)的顺序进行发布和消费,此时使用一个队列保证全局顺序会存在严重的性能瓶颈。

分区顺序消息
一般场景下,不要求消息的全局顺序,例如一个订单产生了3条消息,分别是订单创建、付款、完成,消费时同一个订单要按照这个顺序消费才有意义,但是不同订单之间是可以并行消费的。分区顺序消息如图10所示,对于一个指定的Topic,所有消息根据sharding key进行分区(比如按照订单id)。同一个分区内的消息严格按照FIFO顺序进行发布和消费(由于是根据订单id分区的,因此同一个订单的创建、付款、完成消息会出现在同一个队列里并保持顺序性)。图10中的sharding key是顺序消息中用来区分不同分区的关键字段。

为了保证消费端的顺序性,某条消息消费失败会阻塞后续消息的消费。
2、延迟消息
延迟消息是指生产者发送消息后,需要等待指定的时间才可以被消费。
延迟消息典型应用场景举例:用户下单后需要在30分钟内付款,到期前发送消息提醒用户支付。
RocketMQ延迟消息在Broker内部的处理流程如图11所示。

(1)消息发送时修改Topic名称和队列信息。消息一旦由CommitLog转发到ConsumeQueue就会被立即消费,为了避免延迟消息被立即消费,发送消息时将主题的名称修改为特定Topic (SCHEDULE_TOPIC_XXXX),并根据延迟级别确定投递的队列。同时消息相关属性里保存了要投递的目标Topic和队列信息。
(2)转发消息到延迟主题的CosumeQueue中。消息写入CommitLog后会转发到CosumeQueue,计算延迟消息投递时间,投递时间=消息存储时间+延迟级别对应的时间,将它作为消息Tag的哈希值存储到CosumeQueue中。
(3)延迟服务消费SCHEDULE_TOPIC_XXXX消息。ScheduleMessageService消费SCHEDULE_TOPIC_XXXX中的消息,并投递到目标Topic中。ScheduleMessageService根据延迟级别的个数启动对应数量的TimerTask,每个TimerTask负责一个延迟级别的消息消费与投递,根据Tag值判断对应队列的第一个消息是否到期,若到期则进行投递,并继续检查之后的消息,若当前消息未到期则不再检查后续消息。
(4)到期消息重新写入CommitLog。消息到期后,需要投递到目标Topic,由于第一步记录了目标Topic和队列信息,因此重新设置后直接消息存储到CommitLog即可。
(5)CommitLog消息转发至目标Topic下的CosumeQueue,被消费者消费。消息的Topic被重置后写入CommitLog,转发至CosumeQueue后会被消费者直接消费。
3、事务消息

1、MQServer将消息持久化后返回发送方ACK,确认消息发送成功,此时消息为半消息。
2、发送方执行本地事务。
3、发送方根据本地事务执行结果向MQ Server提交Commit或Rollback,若MQ Server 收到Commit则将半消息标记为可投递,订阅方可消费到该消息;若MQServer收到Rollback则删除半消息,订阅方将不会消费该消息。
4、在网络中断、服务重启等特殊情况下,步骤04提交的Commit或Rollback未能到达MQServer,经过固定时间后MQServer将对该消息发起回查。
5、发送方收到消息回查后,检查对应消息的本地事务执行的最终结果。发送方根据本地事务执行的最终结果再次Commit或Rollback,MQServer仍按照步骤04对半消息进行处理。
4、死信消息
一条消息在达到最大重试次数后仍然消费失败,就会进入死信队列(DLQ)。死信队列里的消息默认不会再被自动消费,需要人工干预。
五、消费者、消费者组、队列之间的关系
当使用集群消费模式时,RocketMQ的一条消息只被集群内的任意一个消费者处理。
规则如下-----
1、一个队列同一时间只允许被消费者组下的某一个消费者消费。
2、消费者组下的某个消费者,可以同时消费同一个Topic下不同队列的消息。
3、不同消费者组下的消费者,可以同时消费同一个Topic下相同队列的消息。