1. 什么是消息队列(Message Queue)?
答题技巧与拓展:**
- 问为什么使用MQ?首先说MQ概念,然后说MQ优点,最后优点的使用场景,并举例。
概念:
应用程序之间的通信方法,是一种中间件。简单理解:把要传输的数据放在队列中,双方通过队列传递数据。
优点:
异步、解耦、削峰(应用场景都是优点)。
缺点:
系统可用性降低(MQ挂掉,系统崩溃)、系统复杂度提高(消息重复消费、消息丢失、消息传递的顺序性)、数据一致性问题(AB两系统数据保存成功, C系统保存失败)。
应用场景:
- 异步处理:不影响主流程的操作,使用MQ异步处理。举例:用户注册后,通过MQ异步发送注册邮件或注册短信。
- 应用解耦:通过MQ进行传递数据,可以隔离系统上下游环境变化带来的不稳定因素。举例:用户下单后,订单系统需要通知库存系统。
- 流量削锋:通过MQ接收所有数据,避免流量过大,导致应用挂掉。举例:秒杀活动。
- 日志处理:解决大量日志传输的问题。举例:大数据中使用Kafka接收数据。
- 消息通讯:MQ一般都内置了高效的通信机制,因此可以用在的消息通讯。举例:点对点消息队列、聊天室等。
常见MQ:
- ActiveMQ:万级;基于主从的高可用架构;有较低的概率丢失数据;JMS规范;
- RabbitMQ:万级;基于主从的高可用架构;AMQP规范(特点:消费者跨平台、交换机组件等);erlang语言开发、性能好、社区活跃等。
- Kafka:十万级;基于分布式的高可用架构;经过参数优化配置,可以做到0丢失;主要定位是在日志处理。
- RocketMQ:十万级;基于分布式的高可用架构;经过参数优化配置,可以做到0丢失;Java实现。
2. 你们公司生产环境用的是什么消息中间件?
首先说明公司选用的是什么消息中间件,然后说不同MQ的选型分析。
选型分析:
- ActiveMQ:是老牌的消息中间件,现在国内好多传统公司在运用,功能全面。但是它无法支付互联网的高并发、高负载,在互联网公司使用较少。
- RabbitMQ:稳定、社区活跃,版本迭代快;基于AMQP规范,支持跨平台。但是基于erlang语言开发的,分析源码比较困难。
- RocketMQ:阿里开源的,经过阿里的生产环境的超高并发、 高吞吐的考验,性能卓越,同时还支持分布式事务等特殊场景,Java语音开发的。
- Kafka:提供的MQ功能明显较少一些。 但是它的优势在于超高吞吐量的实时日志采集、实时数据同步、实时数据计算。一般在大数据领域使用。
总结:
- 中小型公司:技术实力较为一般,技术挑战不是特别高,用RabbitMQ;
- 大型公司:基础架构研发实力较强,用RocketMQ。
- 大数据领域:实时计算、日志采集等场景,用Kafka是业内标准的,绝对没问题。
3. MQ与RPC的区别:
- 相同:
- 解耦、子系统之间的交互(特别是异构子系统)。
- 不同:
- RPC侧重功能调用,并且关注结果,多半是同步的;
- MQ考虑系统性能,不要求时效,支持订阅发布式数据传输。
4. MQ有哪些常见问题?
- 消息的顺序问题
- 消息的重复问题
- 消息的丢失问题
- 消息的一致性问题
5. 消息顺序问题:
概念:
消息有序,是指可以按照消息的发送顺序来消费。
举例:
假如生产者产生了 2 条消息:M1、M2,假定M1发送到S1,M2发送到 S2,如果要保证M1先于M2被消费,怎么做?
解决方案:
- 方案1:保证生产者 - MQServer - 消费者是一对一的关系:
- 缺陷:并行度就会成为消息系统的瓶颈(吞吐量不够)
- 更多的异常处理,比如:只要消费端出现问题,就会导致整个处理流程阻塞,我 们不得不花费更多的精力来解决阻塞的问题。
- 方案2:通过合理的设计或者将问题分解来规避:
- 解决1:不关注乱序。
- 解决2:队列无序并不意味着消息无序,从业务层面来保证消息的顺序(添加序号等),而不仅仅是依赖于消息系统,是一种更合理的方式。
6. 消息不被重复消费(消息消费时的幂等性)?
造成消息重复的重要原因:
网络的不确定性。 因为网络因素是不可控的,解决消息重复问题,就变成了:如果消费端收到两条一样的消息,应该怎样处理,即幂等性问题?
幂等性概念:
对资源(消息)的进行操作,无论操作一次还是多次,其资源本身不发生变化。
幂等性操作场景:
- 强校验场景:
- 场景举例:与金钱相关的关键消息,必须强校验。
- 校验方法:基于数据库的唯一键等。
- 弱校验场景:
- 场景举例:可以有小概率出现重复消费的非关键消息。
- 校验方法:基于Redis的Set数据类型等。
解决方法:
- 通过主键来检查数据,如果有主键就更新,无数据就插入;(神州租车的做法)
- 消费一条消息就往数据库里插入一条数据(JMSMessageID),每次都去数据库判断是否有相同的数据,有证明已经消费过;(碧有信的做法)
- 写Redis时,使用Set数据类型 ,天然的幂等性;
- 消费者发送消息带上全局唯一的id,消费者拿到消息后,根据这个id去Redis里查询,没有消费过就处理,并且写入这个 id 到Redis,反之,则不处理。
7. 消息丢失了怎么办?
丢失的情况:
生产者丢失、MQ Server(Broker)丢失、消费者丢失。
生产者丢失:
- 丢失原因:生产者向MQ写数据的时候,可能数据半路就丢失了,可能是网络问题。
- 解决:使用事务机制(遇到异常后,回滚消息)或confirm机制(写入成功后,MQServer给生产者回复一个ACK消息)。
MQ Server(Broker)丢失:
- 丢失原因:MQ Server自己数据丢失,可能是服务器宕机了。
- 解决:持久化消息;使用MQ集群,降低MQ挂机的可能性,防止数据丢失。
消费者丢失:
- 丢失原因:消费者在消费时,刚消费到,还没有处理,消费者进程挂了,需要重启,此时MQ认为你已经消费过了。
- 解决:使用ACK机制的手动应答。
8. 消息传递的一致性:
前提:
保证消息正常传递(生产者发送消息前,处理完所有业务;消息持久化,防止消息丢失;选择合适的消息确认机制)
方法1:消息事件表 + 分布式锁 + 幂等性消费 + 定时补偿 ,保证消息一致性 ***
处理流程:Event事件表(DB中)确定消息是否被消费。Redis分布式锁保证幂等性。

- 问题1:生产者如何知道消费者没有消费一条消息?
- 创建一个event表,用来维护消息的消费状态(核心字段:JMS队列名称、业务ID、完成状态等)。
- 问题2:生产者重新发送的频率(即定时任务频率)?
- 根据业务的实时性要求、消费者的能力和可以堆积的信息进行判断。
- 问题3:消费者处理重复的消息?
- 利用event表,使用Redis分布式事务锁(使用Redission框架,或使用String类型的并设置失效时间的简单方式实现不可重入的锁),实现幂等性消费。
方法2:消费者主动查询,推拉结合:
消费者启动一个定时任务,查询生产者需要它消费的数据,但是这样会影响消费者的消费能力。
使用场景:通知业务,适用这种推拉结合的方式。
处理流程:提供了一个http接口供用户查询(该http接口不一定在生产者),即用户可以接受推送消息,也可以主动去查询。

9. 如何解决消息积压的问题?
思路1:临时紧急扩容
-
-
- 先修复Consumer出现的问题,确保其恢复消费速度。
-
- 创建一个新的Topic,建立大小是原先10倍的Queue数量。(RocketMQ的概念)
-
- 将现有Cnosumer都停掉,写一个临时的分发数据的Consumer程序,将消息写入到临时的Queue中。
-
- 临时启动一批新机器部署Consumer,来消费积压数据,等消费完积压数据之后,再恢复原部署的架构。
-
思路2:积压数据直接丢弃,在业务低估时,批量重导
Tips:消息设置TTL,在大量数据积压时,可能过期进入死信队列。
10. 如何设计一个MQ,如何设计一个分布式的MQ:
MQ主要涉及三个角色,Producer、Consumer、Broker(MQ服务器),其中Broker是我们设计的重点,Broker需要考虑:遵循的规范和模式(例如,通信协议)、消息的存储、消息的传输、支持事务、支持延迟等:
-
- 通信协议的选择:JMS(例如ActiveMQ)、AMQP(例如RabbitMQ)、自行设计等。
- 消息存储(持久化)的选择:内存、KV分布式(Redis)、关系数据库、文件系统等。
- 消费的关系处理:点对点、发布与订阅、广播与集群等。
分布式MQ在普通MQ的基础上,还需要考虑:数据一致性、集群可用性、集群容错性等分布式下需要关注的点:
-
- 分布式理论:CAP理论 -> BASE理论 -> 消息队列一般遵循AP,保证可用性和容错性,牺牲一致性。实现最终一致性。
- 引入注册中心:用于Producer、Consumer、Broker的注册与发现。使用ZK(CP)、Netty自己封装(AP)等。
- 保证可用性:集群管理,选主(选举算法:ZAB)、主备、副本集等。
- 支持动态扩缩容:数据分片存储等
- 支持分布式事务、使用分布式锁等。