目录
一、简述
消息队列是一种异步通信机制,用于在不同系统、服务或组件之间传递数据(称为 “消息”)。它的核心思想是 “生产者 - 消费者模型”:一方(生产者)发送消息到队列,另一方(消费者)从队列中读取并处理消息,两者无需直接交互,甚至可以在不同时间运行。
简单来说,我们可以把消息队列看作是一个存放消息的容器,当我们需要使用消息的时候,直接从容器中取出消息供自己使用即可(极其类似水库)。
二、作用/优点
消息队列是分布式系统中重要的组件之一。使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。
🔍
- 异步处理:将耗时操作转为后台异步任务,降低主链路延迟。
- 削峰填谷:通过队列缓冲突发流量,保护下游系统不被冲垮。
- 解耦:生产方与消费者仅依赖消息协议,无需直接调用,减少系统间耦合。
众所周知,队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。
2.1.异步处理(提高系统性能)
异步 ≠ 立即成功
把同步链路拆成两段后,必须让用户对“最终一致性”有感知,否则就会出现 “前端提示成功,后台却失败” 的交易纠纷(就比如说是在购买火车票时,前端提示购票成功,但是实际上后台并没有这个乘车人的信息)。
2.2.削峰填谷
先将短时间高并发产生的事务消息存储在消息队列中,然后后端服务再慢慢根据自己的能力去消费这些消息,这样就避免后端服务直接崩掉。
🔍
- 削峰(Peak Shaving):在高流量或高峰时段,通过将请求或任务异步排队到消息队列中,可以平滑处理请求的高峰,防止系统过载。
- 填谷(Valley Filling):在流量低谷时段,系统可以“消化”之前积压的工作,使得资源使用更加均衡。
2.2.1实例
在电商平台,秒杀服务十分常见,在这种短期内突增的请求直接打到后端很容易造成后端平台崩坏,所以在这种场景下就需要消息队列进行缓冲。
2.3.降低系统耦合性
生产者(客户端)发送消息到消息队列中去,接受者(服务端)处理消息,需要消费的系统直接去消息队列取消息进行消费即可而不需要和其他系统有耦合, 这显然也提高了系统的扩展性。
三、消息队列可能会带来的问题
💡最大、最致命的问题:系统整体可用性被消息队列的“单点故障风险”拖垮,导致全链路雪崩。
一旦MQ集群崩溃(哪怕只是瞬时脑裂或磁盘写满),所有依赖它的上下游系统会连锁失效:
生产者:消息堆积本地内存,最终OOM或阻塞主线程(如Kafka Producer缓存打满后阻塞用户请求)。
消费者:拿不到消息就空转,但下游业务逻辑可能因“无数据”触发异常(如订单系统因库存扣减消息丢失导致超卖)。
运维:紧急扩容时才发现MQ集群的横向扩展有上限(如RabbitMQ镜像队列无法水平扩容,Kafka分区重分配耗时数小时)。
简而言之,MQ的“高可用”只是它自己的高可用,不是业务的高可用。一旦它出问题,整个系统的容错能力可能比不用MQ时更差。
四、JMS VS AMQP
4.1.JMS
4.1.1简介
JMS 即 Java 消息服务规范,它定义了一套标准 API,让不同应用组件可以把消息发送到“消息中间件”,再由中间件异步地递送给接收方。
核心作用:解耦、可靠、异步。
4.1.2JMS的两种消息类型
①点到点(P2P)模型
使用队列作为消息通信载体,满足生产者消费者模式,一个消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。
②发布/订阅(Pub/Sub)模型
发布订阅模型(Pub/Sub) 使用主题(Topic)作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不到该条消息的。
🔍 什么是主题(Topic)?
主题(Topic) 就是消息队列里的一个"逻辑频道";可以把它想象成微信群名(如“公司通知群”)或是广播电台的频段(如 FM 103.7)
核心规则:
生产者只负责把消息发到某个“群名”(Topic),比如发到
order-events
。消费者提前“加群”订阅这个 Topic,就能实时收到群发消息。
后加入的消费者(广播后才订阅的人)看不到历史消息——就像你今天才加入微信群,看不到昨天的聊天记录。
简而言之,Topic 是消息的“地址标签”,告诉 MQ 这条消息要广播给哪些订阅者,但不会对后来者补发旧消息。
4.1.3JMS的5种消息正文格式
JMS定义了五种不同的消息正文格式,以及调用的消息类型,允许开发者发送并接收以一些不同形式的数据,提供现有消息格式的一些级别的兼容性。
- StreamMessage :Java原始值的数据流
- MapMessage:一套名称-值对
- TextMessage:一个字符串对象
- ObjectMessage:一个序列化的 Java对象
- BytesMessage:一个字节的数据流
💡这五种消息类型只是 JMS(Java Message Service)规范里的「标准信封格式」,告诉生产者/消费者如何把数据塞进消息里、再怎样取出来。
当然可以全用
TextMessage
传 JSON,然后自己反序列化;也可以混着用:订单事件用
MapMessage
,文件上传用BytesMessage
,完全取决于业务场景。简而言之,它们只是"怎么装载信息"的规范,跟"怎么运输"(Topic/Queue、持久化、事务等)是两码事。
4.2 AMQP
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 高级消息队列协议(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。
RabbitMQ 就是基于 AMQP 协议实现的。
💡RabbitMQ 虽然基于 AMQP,但并不“兼容”JMS——它只是额外做了一个 JMS 客户端库(RabbitMQ JMS Client),把 AMQP 的语义“桥接”成 JMS 的 API,方便 Java 程序像用 ActiveMQ 一样去调用;底层走的仍是 AMQP 协议。
对比维度 | JMS (Java Message Service) | AMQP (Advanced Message Queuing Protocol) |
---|---|---|
定义/本质 | Java 消息服务 API 规范(Sun/Oracle 制定) | 跨语言的 二进制应用层协议(OASIS 标准化) |
适用语言 | 仅限 Java(JVM 语言) | 任何语言(C、Python、Go、Java…) |
协议 vs API | 提供 接口/类库,无网络协议细节 | 定义 字节级协议格式(wire-level protocol) |
消息模型 | 两种:Queue(PTP)、Topic(Pub/Sub) | 基于 Exchange + Queue 的灵活路由模型 |
消息类型 | 5 种内置类型:Text、Bytes、Map、Stream、ObjectMessage | 类型由 消息属性(content-type) 自定义,协议不限制 |
互操作性 | 仅兼容 JMS Provider(ActiveMQ、HornetMQ…) | 任何 AMQP 兼容 Broker(RabbitMQ、Qpid、Azure Service Bus…) |
代表实现 | ActiveMQ、OpenJMS、IBM MQ(JMS 模式) | RabbitMQ、Apache Qpid、Azure Service Bus |
与 RabbitMQ | 需 JMS 客户端桥接(非原生) | 原生支持(RabbitMQ 即 AMQP 0-9-1 实现) |
由于 RabbitMQ比较经典,所以后续再单独对它进行详细讲解。
五、常见的消息队列对比
消息队列 | 一句话定位 | 最大亮点 | 最大痛点 | 典型场景 |
---|---|---|---|---|
ActiveMQ | 老牌 JMS 实现,已过时 | 协议/功能最全 | 性能最差、社区停滞 | 仅供存量系统维护 |
RabbitMQ | 低延迟、功能丰富、易上手 | 微秒级延迟,管理界面友好 | Erlang 源码难定制,十万级吞吐量天花板 | 常规业务、订单/支付 |
RocketMQ | 阿里 Java 系、可二次开发 | 百万级吞吐、事务消息、定时消息 | 阿里主导、社区一般、API 非 JMS | 电商、金融、需要定制 |
Kafka | 大数据领域事实标准 | 百万级吞吐、可水平扩展、不丢数据 | 仅保证“至少一次”,功能极简 | 日志、实时计算、指标管道 |
思考题
怎么才能验证消息丢没丢?
如何保障消息的顺序性?
如何保障消息的事务性,保证消息不重复消费?