RocketMQ基础特性
订阅与发布
发布:消息的发布是指某个生产者向某个topic发送消息;
订阅:消息的订阅是指某个消费者关注了某个topic中带有某些tag的消息,进而从该topic消费数据。
消息顺序
消息有序指的是一类消息消费时,能按照发送的顺序来消费。例如:一个订单产生了三条消息分别是订单创建、订单付款、订单完成。消费时要按照这个顺序消费才能有意义,但是同时订单之间是可以并行消费的。RocketMQ可以严格的保证消息有序。
消息顺序分为全局顺序消息和分区顺序消息,全局顺序是指一个Topic下的所有消息都要保证顺序,部分顺序消息只需要保证每一组消息被顺序消费即可。
- 全局顺序:对于指定的一个Topic,所有消息按照先入先出(FIFO)的顺序进行发布和消费。适用场景:性能要求不高,所有的消息都严格按照FIFO原则进行消息发布和消费的场景
- 分区顺序消息:对于指定的一个Topic,所有消息根据sharding key来进行分区。同一个分区内的消息按照严格的FIFO顺序进行发布和消费。**Sharding key 是顺序消息中用来区分不同分区的关键字段,和普通消息的 Key 是完全不同的概念。**适用场景:性能要求高,以 sharding key 作为分区字段,在同一个区块中严格的按照 FIFO 原则进行消息发布和消费的场景。
消息过滤
RocketMQ的消费者可以根据Tag进行消息过滤,也支持自定义属性过滤。消息过滤这个机制目前是在Broker端实现的,优点是减少了对于消息发布者无用消息的网络传输,缺点是增加了Broker的负担,而且Broker端实现相对复杂
回溯消费
回溯消费是指消费者已经消费成功过的消息,由于业务上需求需要重新消费,所以有了这个功能。Borker在向消费者投递消息成功后,消息仍然需要保留。并且重新消费一般是按照时间维度,例如由于消费者应用系统故障,恢复后需要重新消费1小时前的数据,那么可以使用Broker提供的这一机制,按照时间维度来回退消息消费进度。RocketMQ支持按照时间回溯消费,时间维度精确到毫秒。
定时消息
定时消息(延迟队列)是指消息被发到Broker后,不会被立即消费,等待到了特定时间再投递给真正的Topic。broker有配置项messageDelayLevel,默认值为“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”,18个level。可以配置自定义messageDelayLevel。注意,messageDelayLevel是broker的属性,不属于某个topic。在生产端发消息时,设置delayLevel等级即可:msg.setDelayLevel(level)。level有以下三种情况:
- level == 0,消息为非延迟消息
- 1<=level<=maxLevel,消息延迟特定时间,例如level==1,延迟1s
- level > maxLevel,则level== maxLevel,例如level==20,延迟2h
定时消息会暂存在名为SCHEDULE_TOPIC_XXXX
的Topic中,并且根据delayTimeLevel存入特定的queue,queueId = delayTimeLevel – 1,即一个queue只存相同延迟的消息,保证具有相同发送延迟的消息能够被顺序消费。broker会以调度的方式消费SCHEDULE_TOPIC_XXXX,将消息存入真实的Topic。
需要注意的是,定时消息会在第一次写入以及调度写入真实Topic时,都会进行计数。因此,发送数量、tps都会变高
死信队列
死信队列用于处理无法被正常消费的消息。当一条消息初次消费失败,消息队列会自动进行重试消费;达到预设定的最大重试次数之后,如果消息还是消费失败,则表明消费者正常情况下无法正确的消费该消息,此时消息队列不会立刻丢弃这条消息,而是会将其写入到该消费者对应的特殊队列中。
RocketMQ将这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),将存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。在RocketMQ中,可以通过使用console控制台对死信队列中的消息进行重发来使得消费者实例再次进行消费。
消息可靠性
RocketMQ支持消息的高可靠,影响消息可靠性的几种情况:
- Borker非正常关闭
- Broker异常Crash
- OS Crash
- 机器掉电但是能立即恢复供电情况
- 机器无法开机(可能是cpu、主板、内存等关键设备损坏)
- 磁盘设备损坏
1、2、3、4 四种情况都属于硬件立即能恢复正常工作的情况,RocketMQ在这四种情况下能保证消息不丢,或者会丢失少量的数据(主要看刷盘方式是异步还是同步)
5、6 属于单点故障,并且无法恢复。一旦发生在此节点上的数据就会全部丢失。RocketMQ在这两种情况下,通过异步复制机制,可以保证99%的消息不丢。但是仍然可能会有极少量的消息会丢失。通过同步双写技术,可以完全避免单点故障少量消息丢失的风险,能100%保证消息不会丢失,但是同步双写势必会影响性能,适合对消息可靠性极高的应用场景。例如:与Money相关的应用。注:RocketMQ从3.0版本开始支持同步双写
至少一次
至少一次(At least Once)是指每个消息必须至少投递一次,消费者先把消息Pull到本地,消费完成后,才向服务器返回ack(确认收到信息机制)。如果没有消费就一定不会ack消息,RocketMQ可以很好的支持这一特性
事务消息
RocketMQ事务消息(Transactional Message)指应用本地事务和发送消息操作可以被定义到全局事务中,要么同时成功,要么同时失败。RocketMQ的事务消息提供类似 X/Open XA 的分布事务功能,通过事务消息能达到分布式事务的最终一致。
消息重试
消费者消费消息失败后,提供了一种重试机制,让消息再重新投递消费一次。消费者消费消息失败一般定义有以下几种情况:
- 消息本身的原因:由于消息本身的原因,例如反序列化失败、消息数据本身无法处理(业务异常:例如话费充值,当前消息的手机号被注销,无法充值)等。这种消息一般需要跳过这条消息,然后去消费其他消息,而这条消息如果立即重试消费,大概率也是不成功的,所以就要定义一种定时重试机制,即:过10秒之后再进行重试消费
- 由于消费者的下游应用服务不可用:例如网络不可达、数据库连接不可用等。遇到这种错误,即时跳过当前失败的消息,消费其他消息同样也会报错。这种情况建议应用先休眠30秒,再消费下一条消息。这样可以减轻Broker重试消息的压力
RocketMQ会为每个消费组都设置一个Topic名为%RETRY%+consumerGroup
的重试队列(注意:这里这个重试队列是为每一个消费组来定义的,而不是针对Topic为维度来定义的)。由于暂时保存因为各种异常而导致消费端无法消费的消息,考虑到异常恢复一般需要一些时间,会为重试队列设置多个重试级别,每个重试级别都有与之对应的重新投递的延时时间,重试次数越多,重试延迟越大。RocketMQ对于重试消息的处理是先把消息保存到名为SCHEDULE_TOPIC_XXXX
的Topic延迟队列中,后台定时任务按照对应的时间进行Delay后重新保存至%RETRY%+consumerGroup
的重试队列中。
消息重投
生产者在发送消息时,同步消息失败会重投,异步消息有重试机制,oneway没有任何保证。消息重投机制尽可能保证消息发送成功、不丢失,但可能会造成消息重复,消息重复在RocketMQ中是无法避免的问题。消息重复在一般情况下不会发生,只有在出现消息量大、网络抖动时,消息重复就会是大概率事件。另外,生产者主动重发消息、消费者负载节点变化也会导致重复消息。
下面有三种方案可以设置消息重投策略:
- retryTimesWhenSendFailed:设置同步发送失败重投次数,默认为2。算上初次发送消息,生产者会最多尝试发送
retryTimesWhenSendFailed + 1
次。不会选择上次失败的Broker进行发送,会选择尝试向其他的Broker节点发送,最大程度保证消息不丢失。超过重投次数则抛出异常,由客户端保证消息不丢。当出现RemotingException、MQClientException和部分MQBrokerException异常时会重新投递消息。 - retryTimesWhenSendAsyncFailed:设置异步发送失败重试次数,异步重试不会选择其他Broker,仅会在同一个Broker节点做重投,不保证消息不丢。
- retryAnotherBrokerWhenNotStoreOK:消息刷盘(主或备)超时或slave不可用(返回状态非SEND_OK),是否尝试发送到其他broker。默认是false,十分重要的消息可以开启。
流量控制
生产者流控,一般用来处理Broker处理能力达到瓶颈的问题;消费者流控,用来处理消费能力达到流控的问题
生产者流控:
- commitLog文件被锁时间超过osPageCacheBusyTimeOutMills设置的值时,默认为1000ms,返回流控
- 如果开启transientStorePoolEnable == true,并且Broker为异步刷盘的主机,且transientStorePool中资源不足,就拒绝当前send请求,返回流控。
- broker每隔10ms检查send请求队列头部请求的等待时间,如果等待时间超过waitTimeMillsInSendQueue(默认200ms),拒绝当前send请求,返回流控。
注意,生产者流控,不会尝试消息重投。
消费者流控:
- 消费者本地缓存消息数超过pullThresholdForQueue时(默认1000)
- 消费者本地缓存消息大小超过pullThresholdSizeForQueue时(默认100MB)。
- 消费者本地缓存消息跨度超过consumeConcurrentlyMaxSpan时(默认2000)。
消费者流控的结果是降低拉取频率。
总结
上面对RocketMQ的基础特性做了一些介绍,如果你能细心看完这些特性,就会发现。其实Rocket这些特性根据业务场景解决方案可以分成几类:
-
基础功能
消息的订阅和发布,基本都是每个消息队列中间件都有的功能。根据实现方式的不同可能会带来性能上的差异。
-
性能优化
消息过滤,消息可以根据Key和ID以及Tag进行过滤消费,既降低了Broker的性能压力,又减少了消费端对无用消息的处理。
流量控制:在Broker达到一定性能瓶颈以及消费者处理消息功能性能瓶颈后,对消息的消费进行限流,延时消费
死信队列:减少无意义的消息投递,降低消费者和Broker的压力
-
特殊场景
消息顺序:在消息队列内保证消息有序的投递或消费,免去业务内处理消息顺序问题
事务消息:解决分布式事务问题,以及消息的脏数据问题
-
消息安全、可靠
回溯消费、至少一次、消息可靠性、消息重试、消息重投这些机制主要保证消息不会丢失、消息的重复投递、消息消费失败、消息重新消费这些问题。