网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。比如有一个 Topic 消息为水果,那么水果可以有其他的标签 可以是 香蕉、西瓜、草莓等等,我们可以把对应的消息,打上对应的标签(Tag),这个就是方便我们在消费的时候做对应的筛选。
标签能够有效地保持代码的清晰度和连贯性,并优化 RocketMQ 提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。
Offset (偏移量)
在 RocketMQ 中,有很多 offset 的概念。一般我们只关心暴露到客户端的 offset。不指定的话,一般指的是消费者消息的偏移量(ConsumerOffset)
Message queue 是无限长的数组。一条消息进来下标就会涨 1,而这个数组的下标就是 offset。
Message queue 中的 max offset 表示消息的最大 offset,Consumer offset 可以理解为标记 Consumer Group 在一条逻辑 Message Queue 上,消息消费到哪里即消费进度。(Offset)
在 RocketMQ 中,有很多 offset 的概念。一般我们只关心暴露到客户端的 offset。不指定的话,一般指的是消费者消息的偏移量(ConsumerOffset)
Message queue 是无限长的数组。一条消息进来下标就会涨 1,而这个数组的下标就是 offset。
Message queue 中的 max offset 表示消息的最大 offset,Consumer offset 可以理解为标记 Consumer Group 在一条逻辑 Message Queue 上,消息消费到哪里即消费进度。
RocketMq的工作流程
1)首先启动NameServer。NameServer启动后监听端口,等待Broker、Producer以及Consumer连上来
2)启动Broker。启动之后,会跟所有的NameServer建立并保持一个长连接,定时发送心跳包。心跳包中包含当前Broker信息(ip、port等)、Topic信息以及Borker与Topic的映射关系
3)创建Topic。创建时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic
4)Producer发送消息。启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic所在的Broker;然后从队列列表中轮询选择一个队列,与队列所在的Broker建立长连接,进行消息的发送
5)Consumer消费消息。跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,进行消息的消费
RocketMQ 消费模式有几种?
消费模型由Consumer决定,消费维度为Topic。
集群消费
一条消息只会被同Group中的一个Consumer消费
多个Group同时消费一个Topic时,每个Group都会有一个Consumer消费到数据
广播消费
消息将对一个Consumer Group下的各个Consumer实例都消费一遍。即使这些Consumer属于同一个Consumer Group,消息也会被Consumer Group中的每个Consumer都消费一次
/**
* 3. 设置消息模式,默认是CLUSTERING
* MessageModel.BROADCASTING 广播消费模式
* MessageModel.CLUSTERING 集群消费模式
*/
consumer.setMessageModel(MessageModel.BROADCASTING);
消费消息是 push 还是 pull ?
RocketMQ没有真正意义的push,都是pull,虽然有push类,但实际底层实现采用的是长轮询机制,即拉取方式。
broker端属性 longPollingEnable 标记是否开启长轮询。默认开启
追问:为什么要主动拉取消息而不使用事件监听方式?
事件驱动方式是建立好长连接,由事件(发送数据)的方式来实时推送。
如果 broker 主动推送消息有可能造成消息在 consumer 端堆积过多,同时又不能被其他 consumer 消费的情况。
而 pull 的方式可以根据当前自身情况来pull,不会造成过多的压力而造成瓶颈。所以采取了pull的方式。
// 1. 创建消费者(Pull)对象
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("GROUP\_TEST");
// 1. 创建消费者(Push)对象
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("GROUP\_TEST");
如何保证消息的顺序消费
首先多个 queue 只能保证单个 queue 里的顺序,queue 是典型的 FIFO,天然顺序。多个 queue 同时消费是无法绝对保证消息的有序性的。所以总结如下:
同一 topic,同一个 QUEUE,发消息的时候一个线程去发送消息,消费的时候 一个线程去消费一个 queue 里的消息。
追问:怎么保证消息发到同一个 queue ?
Rocket MQ 给我们提供了 MessageQueueSelector 接口,可以自己重写里面的接口,实现自己的算法,举个最简单的例子:判断i % 2 == 0,那就都放到 queue1 里,否则放到 queue2 里。
for (int i = 0; i < 5; i++) {
Message message = new Message("orderTopic", ("hello!" + i).getBytes());
producer.send(
// 要发的那条消息
message,
// queue 选择器 ,向 topic中的哪个queue去写消息
new MessageQueueSelector() {
// 手动 选择一个queue
@Override
public MessageQueue select(
// 当前topic 里面包含的所有queue
List<MessageQueue> mqs,
// 具体要发的那条消息
Message msg,
// 对应到 send() 里的 args,也就是2000前面的那个0
Object arg) {
// 向固定的一个queue里写消息,比如这里就是向第一个queue里写消息
if (Integer.parseInt(arg.toString()) % 2 == 0) {
return mqs.get(0);
} else {
return mqs.get(1);
}
}
},
// 自定义参数:0
// 2000代表2000毫秒超时时间
i, 2000);
}
消息重复消费
影响消息正常发送和消费的重要原因是网络的不确定性。
引起重复消费的原因
ACK
正常情况下在 consumer 真正消费完消息后应该发送 ack,通知 broker 该消息已正常消费,从 queue 中剔除
当 ack 因为网络原因无法发送到 broker,broker 会认为此条消息没有被消费,此后会开启消息重投机制把消息再次投递到 consumer
消费模式
在 CLUSTERING 模式下,消息在 broker 中会保证相同 group 的 consumer 消费一次,但是针对不同group 的 consumer 会推送多次
解决方案
去重操作直接放在了消费端,消费端处理消息的业务逻辑保持幂等性。那么不管来多少条重复消息,可以实现处理的结果都一样。
Broker中的消息被消费后会立即删除吗?
不会,每条消息都会持久化到 CommitLog 中,每个 Consumer 连接到 Broker 后会维持消费进度信息,当有消息消费后只是当前 Consumer 的消费进度(CommitLog的offset)更新了。
追问:那么消息会堆积吗?什么时候清理过期消息?
默认72小时后会删除不再使用的 CommitLog 文件
- 检查这个文件最后访问时间
- 判断是否大于过期时间
- 指定时间删除,默认凌晨4点
如何保证消息不丢失
首先在如下三个部分都可能会出现丢失消息的情况:
- Producer端
- Broker端
- Consumer端
Producer端如何保证消息不丢失
采取 send() 同步发消息,发送结果是同步感知的。
发送失败后可以重试,设置重试次数。默认 3 次。
producer.setRetryTimesWhenSendFailed(10);
集群部署,比如发送失败了的原因可能是当前Broker宕机了,重试的时候会发送到其他Broker上。
Broker端如何保证消息不丢失
修改刷盘策略为同步刷盘。默认情况下是异步刷盘的。
flushDiskType = SYNC_FLUSH
集群部署,主从模式,高可用。
Consumer端如何保证消息不丢失
完全消费正常后在进行手动 ack 确认。
消息堆积如何处理
首先要分析一下消息堆积可能造成的原因
1、如果是机器本身的原因,比如消费者组有几个消费者服务挂掉了,剩下少量消费者消费能力不足导致的消费积压,那就正常重新启动,然后慢慢再去消费积压的消息。
2、如果是生产者端由业务暴增引起的生产过快,而消费者端消费能力不足,这个时候就可以采取生产者端限流或者进行消费者扩容;这个时候要注意,如果生产者只是短期暴增或者消息的业务不是很重要可以采用限流,如果是长期暴增真正的业务量上涨就必须要进行消费者扩容。
3、如果是消费者挂了,然后 broker 堆积了很多消息,然后可以先把堆积的消息读到别的地方比如 mysql 或者 es 然后去后续进行处理,然后把 RocketMQ 堆积的消息删掉,启动消费者保障消费者正常消费,这里要注意的是删除堆积消息之前,需要停止 mq。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
)]
[外链图片转存中…(img-Q4ar9hKA-1715416915782)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!