文章目录
顺序消息
顺序消息是指消息的消费顺序和产生顺序相同。顺序消息分为全局顺序消息和部分顺序消息,全局顺序消息指某个Topic下的所有消息都要保证顺序;部分顺序消息只要保证每一组消息被顺序消费即可,比如订单消息(订单的生成、付款、发货),只要保证同一个订单ID 的三个消息能按顺序消费即可。
全局顺序
- 默认是不保证顺序的。
- 如创建一个Topic ,默认八个写队列,八个读队列。这时候一条消息可能被写入任意一个队列里;
- 在数据的读取过程中,可能有多个Consumer ,每个Consumer 也可能启动多个线程并行处理,所以消息被哪个Consumer 消费,被消费的顺序和写人的顺序是否一致是不确定的
- 要保证全局顺序消息, 需要先把Topic 的读写队列数设置为一,然后Producer 和Consumer 的并发设置也要是一。简单来说,为了保证整个Topic的全局消息有序,只能消除所有的并发处理,各部分都设置成单线程处理。这时高并发、高吞吐量的功能完全用不上了
部分顺序
- 在发送端,要做到把同一业务ID 的消息发送到同一个MessageQueue;在消费过程中,要做到从同一个MessageQueue 读取的消息不被并发处理,这样才能达到部分有序
- 具体实现
- 发送端使用
MessageQueueSelector
类来控制把消息发往哪个Message Queue; - 消费端通过使用
MessageListenerOrderly
类来解决单Message Queue 的消息被并发处理的问题
MessageListenerOrderly
实现:为每个Consumer Queue 加个锁,消费每个消息前,需要先获得这个消息对应的ConsumerQueue 所对应的锁,这样保证了同一时间,同一个ConsumerQueue 的消息不被并发消费,但不同ConsumerQueue 的消息可以并发处理
消息重复问题
-
RocketMQ 选择了确保一定投递,保证消息不丢失,但有可能造成消息重复。
-
消息重复一般情况下不会发生,但是如果消息量大,网络有波动,消息重复就是个大概率事件。比如Producer有个函数
setRetryTimesWhenSendFailed
,设置在同步方式下自动重试的次数,默认值是2 ,这样当第一次发送消息时,Broker 端接收到了消息但是没有正确返回发送成功的状态,就造成了消息重试 -
解决消息重复有两种方法:
- 第一种方法是保证消费逻辑的幕等性(多次调用和一次调用效果相同);
- 另一种方法是维护一个巳消费消息的记录,消费前查询这个消息是否被消费过。
消息优先级
RocketMQ 特点
- 先入先出的队列,不支持消息级别或者Topic 级别的优先级
- 业务中简单的优先级需求,可以通过间接的方式解决
间接的方式的优先级处理
- 一个 Topic 里有多种相似类型的消息,如 AA 、AB 、AC
- 当AB 、AC 的消息量很大,但是处理速度很慢,那么会有堆积,这时少量的 AA 类型(优先级高)就在排在AB 、AC 后面等待
- 解决方式:分拆到两个Topic 里
- AA 类型的消息在一个单独的Topic,AB 、AC 类型的消息在另外一个Topic
- 应用程序创建两个Consumer ,分别订阅不同的Topic
- 不同MessageQueue的优先级,固定 MessageQueue 的写入。
- Producer 根据一定的规则,写入固定的 MessageQueue
- DefaultMQPushConsumer 默认是采用循环的方式逐个读取一个Topic 的所有MessageQueue ,这样某个MessageQueue 消息数增多,等待时间增长,但不会造成其他MessageQueue等待时间增长
- 强优先级。
- 有一个应用程序同时处理TypeA 、TypeB 、TypeC 三类消息:TypeA 处于第一优先级,要确保只要有TypeA 消息,必须优先处理; TypeB 处于第二优先级; TypeC 处于第三优先级。
- 解决:自己编码实现优先级控制
- 三类消息在一个Topic 里,可以使用PullConsumer ,自主控制Mes sag巳Queue 的遍历,以及消息的读取
- 如果上述三类消息在三个Topic 下,需要启动三个Consumer , 实现逻辑控制三个Consumer 的消费
动态增减机器
动态增减NameServer
有四种方式可设置NameServer 的地址,按优先级由高到低依次介绍:
- 编码设置
- broker 设置,需要在其配置文件中配置
namesrvAddr=name-server-ip1:port;name-server-ip2:port
- producers 和 consumers 编码
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
producer.setNamesrvAddr("name-server1-ip:port;name-server2-ip:port");
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name");
consumer.setNamesrvAddr("name-server1-ip:port;name-server2-ip:port");
- mqadmin 命令行
sh mqadmin command-name -n name-server-ip1:port;name-server-ip2:port -X OTHER-OPTION
- 自定义了命令行工具
DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt("please_rename_unique_group_name");
defaultMQAdminExt.setNamesrvAddr("name-server1-ip:port;name-server2-ip:port");
- Java 启动参数设置
# 对应的option
rocketmq.namesrv.addr
- Linux 环境变量设置
# 环境变量
NAMESRV_ADDR
- 通过HTTP 服务来设置(动态设置)
- 当上述方法都没有使用,程序会向一个HTTP地址发送请求来获取NameServer地址
- 通过Java option
rocketmq.namesrv.domain
参数来覆盖jmenv.tbsite.net
; - 通过Java option
rocketmq.namesrv.domain.subgroup
参数来覆盖nsaddr
。
# 默认地址-淘宝的测试地址
http://jmenv.tbsite.net:8080/rocketmq/nsaddr
通过HTTP 服务方式看似繁琐,但它是唯一支持动态增加NameServer ,无须重启其他组件的方式。使用这种方式后其他组件会每隔2 分钟请求一次该URL ,获取最新的Na meServer 地
动态、增减Broker
动态新增 Broker
- 只增加Broker 不会对原有的Topic 产生影响,原来创建好的Topic 中数据的读写依然在原来的那些Broker 上进行
- 负载处理
- 把新建的Topic 指定到新的Broker 机器上,均衡利用资源
- 通过
updateTopic
命令更改现有的Topic 配置,在新加的Broker 上创建新的队列
# TestTopic 是现有Topic名称 ,新增Broker 地址是192.168.0.1:10911
# 结果:在新增的Broker上,为TestTopic 新创建了读写队列。
sh ./bin/mqadmin updateTopic -b 192.168.0.1:10911 -t TestTopic -n 192.168.0.100:9876
动态减少 Broker
- 当一个Topic 只有一个MasterBroker ,停掉这个Broker 后,消息的发送肯定会受到影响,需要在停止这个Broker 前,停止发送消息。
- 当某个Topic 有多个Master Broker ,停了其中一个,消息丢失情况如下:
- 如果使用同步方式send ( msg )发送,在DefaultMQProducer 内部有个自动重试逻辑,其中一个Broker 停了,会自动向另一个Broker 发消息,不会发生丢消息现象。(异步也可以,使用异步重试)
- 用sendOneWay 方式,会丢失切换过程中的消息
各种故障对消息的影响
建议设置
- 多Master ,每个Master 带有Slave;
- 主从之间设置成 SYNC_MASTER;
- Producer 用同步方式写;
- 刷盘策略设置成SYNC FLUSH
就可以消除单点依赖,即使某台机器出现极端故障也不会丢消息。