RocketMq 消息发送
一、消息消费模式
消息消费模式由消费者来决定,可以由消费者设置MessageModel来决定消费模式。默认为集群消费模式
consumer.setMessageModel(MessageModel.BROADCASTING); //广播消费模式
consumer.setMessageModel(MessageModel.CLUSTERING); //集群消费模式
集群消息
集群消息是指集群化部署消费者
当使用集群消费模式时,MQ 认为任意一条消息只需要被集群内的任意一个消费者处理即可。
特点
- 每条消息只需要被处理一次,broker只会把消息发送给消费集群中的一个消费者
- 在消息重投时,不能保证路由到同一台机器上
- 消费状态由broker维护
广播消息
当使用广播消费模式时,MQ 会将每条消息推送给集群内所有注册过的客户端,保证消息至少被每台机器消费一次。
特点
- 消费进度由consumer维护
- 保证每个消费者消费一次消息
- 消费失败的消息不会重投
二、消息发送
1.同步发送
消息发送时进入同步等待状态,可以保证消息投递一定到达。(可靠同步消息)
在重要的通知消息,SMS通知,SMS营销系统等广泛的场景中使用可靠的同步传输。
producer.send(message);
2.异步发送
想要快速发送消息,又不想丢失的时候可以使用异步消息。(可靠异步消息)
异步传输通常用于对时间敏感的业务场景中。
producer.send(message, new SendCallback() {...});
3.单项消息
只发送消息,不等待服务器响应,只发送请求不等待应答。此方式发送消息的过程耗时非常短,一般在微秒级别。
单向传输用于要求中等可靠性的情况,例如日志收集。
producer.sendOneway(message);
4.批量消息发送
可以多条消息打包一起发送,减少网络传输次数提高效率。
- 批量消息要求必要具有同一topic、相同消息配置
- 不支持延时消息
- 建议一个批量消息最好不要超过1MB大小
- 如果不确定是否超过限制,可以手动计算大小分批发送
producer.send(Collection c)
方法可以接受一个集合 实现批量发送
public SendResult send(
Collection<Message> msgs) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return this.defaultMQProducerImpl.send(batch(msgs));
}
如果不确定一次批量发送是否超过大小限制(1MB),那么可以拆分列表,拆表可以参考官网实例rocketMq官网
5.消息延迟发送
RocketMQ使用messageDelayLevel可以设置延迟投递
默认配置
messageDelayLevel 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
这个配置项配置了从1级开始,各级延时的时间,可以修改这个指定级别的延时时间;
时间单位支持:s、m、h、d,分别表示秒、分、时、天;
若要设置为延迟投递,需要在broker.conf
中添加如上配置,并在发送消息时设置延迟时间级别
message.setDelayTimeLevel(1);
重试机制
投递消息或者消费消息,由于网络的原因导致消息丢失,可以通过重试机制来投递或发送消息
默认超时时间,sendMsgTimeout默认3000毫秒,即3秒,可通过修改设置
producer.setSendMsgTimeout(5000);
producer重试
当在超时时间内未收到broker的确认信号,会重试,可以配置重试次数或者当发送的broker错误时,可以尝试给其它broker发
// 异步发送时 重试次数,默认 2
producer.setRetryTimesWhenSendAsyncFailed(1);
// 同步发送时 重试次数,默认 2
producer.setRetryTimesWhenSendFailed(1);
// 是否向其他broker发送请求 默认false
producer.setRetryAnotherBrokerWhenNotStoreOK(true);
producer在发送同步/异步可靠消息后,会接收到SendResult,表示消息发送成功
SendResult其中属性sendStatus表示了broker是否真正完成了消息存储
当sendStatus!="ok"的时候,应该重新发送消息,避免丢失。
consumer重试
正常情况下在consumer真正消费完消息后应该发送ack,通知broker该消息已正常消费,从queue中剔除
当ack因为网络原因无法发送到broker,broker会认为词条消息没有被消费,此后会开启消息重投机制把消息再次投递到consumer
消费超时时间(单位分种,默认15分种):consumer.setConsumeTimeout()
设置RECONSUME_LATER
可以重新消费
broker重试
只有在消息模式为MessageModel.CLUSTERING集群模式时,Broker才会自动进行重试,广播消息不重试
重投使用messageDelayLevel
默认值
messageDelayLevel 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
6.事务消息
分布式系统中的事务可以使用TCC(Try、Confirm、Cancel)、2pc(Two-phase Commit)来解决分布式系统中的消息原子性
RocketMQ 4.3+提供分布事务功能,通过 RocketMQ 事务消息能达到分布式事务的最终一致
2pc:二阶段提交,RocketMQ采用2pc实现事务控制,需要实现XO协议,完成分布式控制,分为二阶段,第一阶段尝试提交,第二阶段确认ok。
第一阶段尝试提交,如果本地事务执行成功,再提交一次。第一次提交的数据不会立即生效,这些数据会维持一个状态,这个状态是不可用的,当第二阶段确认后这个数据才是可用的,如果第二阶段发的不是确认信号,这个数据就是临时数据,状态是不可用的。
RocketMQ实现事务流程及方式
**Half Message:**预处理消息,当broker收到此类消息后,会存储到RMQ_SYS_TRANS_HALF_TOPIC的消息消费队列中
**检查事务状态:**Broker会开启一个定时任务,消费RMQ_SYS_TRANS_HALF_TOPIC队列中的消息,每次执行任务会向消息发送者确认事务执行状态(提交、回滚、未知),如果是未知,等待下一次回调。
**超时:**如果超过回查次数,默认回滚消息
Producer发送端开启事务,发送Half Message消息给broker,broker接受到半消息后,知道发送端需要开启事务,那broker会将半消息写入会存储到RMQ_SYS_TRANS_HALF_TOPIC(HF)的消息消费队列中,将消息保存到磁盘,给Producer确认。
Producer收到半消息确认后,执行本地事务,如果执行rollback,需要将HF消息队列里的消息撤销,磁盘里的数据会被清除,如果commit,那会将真正的消息写入消息队列并撤销HF消息。此时消息才能被Consumer消费。
但是如果事务执行时间过长,broker不知道事务需要多久,所以broker开启一个定时任务,从半消息队列中取到事务的相关信息,去定时检查真正事务执行状态。
Producer提供一个回调方法,按照业务逻辑回馈本地事务执行成功或失败,broker会调起回调方法,当回调能连接上,且返回成功,说明之前producer事务未commit。
回调如果连接不上,那重连,多次连不上,那将事务置未失败。回调如果超时,默认事务回滚。
consumer只有当消息是真正的可用状态,才能消费。
TransactionListener的两个方法
executeLocalTransaction:半消息发送成功触发此方法来执行本地事务
checkLocalTransaction:broker将发送检查消息来检查事务状态,并将调用此方法来获取本地事务状态
本地事务执行状态
LocalTransactionState.COMMIT_MESSAGE
执行事务成功,确认提交
LocalTransactionState.ROLLBACK_MESSAGE
回滚消息,broker端会删除半消息
LocalTransactionState.UNKNOW
暂时为未知状态,等待broker回查
三、消息消费
消息订阅
消费者使用subscribe方法进行过滤,第一个参数为订阅的topic,第二个为过滤的TAG
consumer.subscribe("TopicTest", "TagA||TagB");
顺序消费
顺序消费表示消息消费的顺序同生产者为每个消息队列发送的顺序一致,
所以如果正在处理全局顺序是强制性的场景,需要确保使用的主题只有一个消息队列。
若消费者同一主题有多个消息队列,那只能保证局部顺序,即同一个队列queue中有序。
producer发送消息有序:
producer的send()方法中添加MessageQueueSelector接口的实现类,并重写select选择使用的队列,将消息发送到同一个队列中
producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
//选择队列
return mqs.get(arg1);
}
}, 1);
consumer顺序消费:注册监听器时使用MessageListenerOrderly
监听器
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.println(Thread.currentThread() + ":" + new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
如何让RocketMQ保证消息的顺序消费
- 同一topic
- 同一个QUEUE
- 发消息的时候一个线程去发送消息
- 消费的时候 一个线程 消费一个queue里的消息
- 多个queue 只能保证单个queue里的顺序
保证有序参与因素
- FIFO
- 队列内保证有序
- 消费线程
并发消费
使用``Mes