消息队列场景
什么是消息队列?
消息队列是一个使用队列来通信的组件,它的本质就是个转发器,包含发消息、存消息、消费消息。
消息队列怎么选型?
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级 | 万级 | 10万级 | 10万级 |
时效性 | 毫秒级 | 微秒级 | 毫秒级 | 毫秒级 |
可用性 | 高(主从) | 高(主从) | 非常高(分布式) | 非常高(分布式) |
消息重复 | 至少一次 | 至少一次 | 至少一次 最多一次 | 至少一次最多一次 |
消息顺序性 | 有序 | 有序 | 有序 | 分区有序 |
支持主题数 | 千级 | 百万级 | 千级 | 百级,多了性能严重下滑 |
消息回溯 | 不支持 | 不支持 | 支持(按时间回溯) | 支持(按offset回溯) |
管理界面 | 普通 | 普通 | 完善 | 普通 |
消息队列使用场景有哪些?
- 异步处理:缩短用户响应时间,提高系统吞吐量,各服务可独立运行,互不干扰。
- 应用解耦:降低系统间耦合度,一个系统的变更或故障不易影响其他系统,提升系统可维护性与扩展性。
- 流量削峰:保护后端服务不被高流量冲垮,可按下游处理能力调节流量,避免系统崩溃 。
消息重复消费怎么解决?
业务端对于已经消费成功的消息,保存在本地数据库或Redis缓存业务中,进行业务表示,每次处理前先进行校验,保证幂等性。
消息丢失怎么解决的?
消息生产阶段:只要能正常接收到MQ中间件的ack确认响应,就表示发送成功,所以只要处理号消息的返回值和异常,如果返回异常则进行消息重发,那么这个阶段是不会出现消息丢失的。
消息存储阶段:生产者在发布消息是,MQ中间件通常会写入多个节点,也就是创建多个副本,即便其中一个节点挂掉,也能保证集群的数据不丢失。
消息消费阶段:消费者接收消息并处理消息之后,才回复ack的话,那么消息是不会丢失的。不能收到消息就会回ack,否则可能消息处理中途就挂掉了,消息便丢失了。
消息队列的可靠性怎么保证?
消息持久化:在系统崩溃、重启或者网络故障等情况下,未处理的消息不会丢失。
消息确认机制:消费者在成功处理消息后,应该向消息队列发送确认(ack)。消息队列只有收到确认后,才会将消息从队列中移除。如果没有收到确认,消息队列会在一定时间内重发消息给消费者。
消息重试策略:当消费者处理消息失败后,需要选择合适的重试策略。可以是设置重试次数和重试间隔时间;也可以是发送消息到死信队列中,以便后续的排查和处理。
消息队列的顺序性怎么保证?
有序消息处理场景的识别:明确业务场景中哪些消息是需要保证顺序的,对于需要顺序处理的消息,要确保消息队列和消费者能够按照特定的顺序进行处理。
消息队列对顺序性的支持:Kafka可以通过将消息划分到同一个分区(Partition)来保证消息在分区内是有序的,消费者按照分区顺序读取消息就可以保证消息顺序。但这也可能会限制消息的并行处理程度,需要在顺序性和吞吐量之间进行权衡。
消费者顺序处理消息:消费者在处理消息时,应该避免并发处理可能导致的打乱情况。可以使用单线程或者使用对顺序消息进行串行化处理后的线程池等方法,确保消息按照正确的顺序被消费。
如何保证幂等性?
幂等性:同一操作的多次执行对系统状态的影响与一次执行结果一致。
实现幂等性的核心方案:
- 唯一标识(幂等键):客户端为每一个请求生成全局唯一ID,服务端校验该ID是否已处理,适用于场景:接口调用、消息消费等。
- 数据库事务+乐观锁:通过版本号或状态字段控制并发更新,确保多次更新等同于单次操作,适用场景:数据库记录更新(如余额扣减、订单状态变更)。
- 数据库唯一约束:利用数据库唯一索引防止重复数据写入,适用场景:数据插入(如订单创建)。
- 分布式锁:通过锁机制保证同一时刻仅有一个请求执行关键操作,适用场景:高并发下的资源抢夺(如秒杀)。
- 消息去重:消息队列生产者为每一条消息生成唯一的消息ID,消费者在处理消息前,先检查该消息ID是否已经处理过,如果已经处理过则丢弃该消息。
如何处理消息队列的消息积压问题?
原因:生产者的生产速度大于消费者的消费速度。
解决方案:
- 批量处理消息
- 增加Topic的队列数和消费组机器的数量
- 临时紧急扩容
临时紧急扩容的大概思路:
1.先修复consumer消费者的问题,以确保其恢复消费速度,然后将现有consumer都停掉。
2.新建一个topic,partition是原来的10倍。临时建立好原先10倍数量的queue。
3.写一个临时的分发数据的cunsumer程序,这个程序部署上去,消费积压的数据,消费之后不做耗时的处理,直接轮询写入临时建立好的10倍数量的queue。
4.接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据。这个做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据。
5.等消息消费完积压的数据后,恢复原先的部署架构,重新用原先的consumer机器来消费消息。
如何保证数据一致性,事务消息如何实现?
- 生产者产生消息,发送带MQ服务器
- MQ收到消息后,将消息持久化到存储系统。
- MQ服务器返回Ack到生产者。
- MQ服务器把消息push给消费者
- 消费者消费完消息,响应ACK
- MQ服务器收到ACK,认为消息消费成功,即在存储中删除消息。
- 生产者产生消息,发送一条半事务消息到MQ服务器
- MQ收到消息后,将消息持久化到存储系统,这条消息的状态是待发送状态。
- MQ服务器返回ACK确认到生产者,此时MQ不会触发消息推送事件
- 生产者执行本地事务
- 如果本地事务执行成功,即commit执行结果到MQ服务器;如果执行失败,发送rollback。
- 如果是正常的commit,MQ服务器更新消息状态为可发送;如果是rollback,即删除消息。
- 如果消息状态更新为可发送,则MQ服务器会push消息给消费者。消费者消费完就回ACK。
- 如果MQ服务器长时间没有收到生产者的commit或者rollback,它会反查生产者,然后根据查询到的结果执行最终状态。
消息队列是参考哪种设计模式?
观察者模式:
观察者模式实际上就是一对多的关系,即存在一个主题和多个观察者,主题也是被观察者,当主题发布消息时,会通知各个观察者,观察者将会收到最新消息。
发布订阅模式
发布订阅模式和观察者模式的区别就是发布者和订阅者完全解耦,通过中间的发布订阅中心进行消息通知,发布者并不知道自己发布的消息会通知给谁。
让你写一个消息队列,该如何进行架构设计?
- 首先是消息队列的整体流程,producer发送消息给broker,broker存储好,broker再发送给consumer消费,consumer回复消费确认等。
- producer发送消息给broker,broker发消息给consumer消费,那就需要两次RPC了,RPC如何设计呢?可以参考开源框架Dubbo,你可以说说服务发现、序列化协议等等
- broker考虑如何持久化呢,是放文件系统还是数据库呢,会不会消息堆积呢,消息堆积如何处理呢。
- 消费关系如何保存呢?点对点还是广播方式呢?广播关系又是如何维护呢?zk还是config server
- 消息可靠性如何保证呢?如果消息重复了,如何幂等处理呢?
- 消息队列的高可用如何设计呢?可以参考Kafka的高可用保障机制。多副本 -> leader & follower -> broker挂了重新选举leader即可对外服务。
- 消息事务特性,与本地业务同个事务,本地消息落库;消息投递到服务端,本地才删除;定时任务扫描本地消息库,补偿发送。
- MQ得伸缩性和可扩展性,如果消息积压或者资源不够时,如何支持快速扩容,提高吞吐?可以参照一下Kafka的设计理念,broker -> topic -> partition,每个partition放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给topic增加partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了。
RocketMQ
消息队列为什么选择RocketMQ的?
- 开发语言优势:RocketMQ使用Java语言开发,更容易上手和阅读源码。
- 社区氛围活跃:RocketMQ是阿里巴巴开源且内部在大量使用的消息队列,是经得起考验的,并且能够针对线上的复杂环境提供相应的解决方案。
- 特性丰富:RocketMQ的高级特性达到了12种,例如顺序消息、事务消息、消息过滤、定时消息等。丰富的特性,能够为我们复杂的业务场景尽可能多地提供思路和解决方案。