MQ基本介绍
MQ(Message Queue): 是一种应用程序间的通信方法。有别于程序之间直接通过远程调用进行通信,应用程序使用MQ进行通信时,通信的发起方将消息(数据)写入队列,接收方通过检 索队列中的消息(数据)来进行的。通常会使用MQ中间件进行信息的中转,而无需在程序之间建立直接的联接。用于接收、存储、分发消息的独立应用程序。常见的MQ中间件有:RabbitMQ 、RocketMQ、Kafka、Redis、ActiveMQ等。
MQ的组成
- Broker: 消息服务器,作为Server提供消息核心服务。
- Producer:消息生产者,业务的发起方,负责产生消息,并将消息传递给Broker。
- Consumer:消息消费者,业务的处理方,负责从Broker获取消息,并进行相关业务处理。
- Topic:消息的主题,发布订阅模式下,生产者产生的消息将被发送到Topic后,由Broker分发给所有订阅此Topic的消费者,实现消息的广播。
- Queue:消息队列,点对点模式下,生产者产生的消息将发送到Queue,消费者订阅此队列,接收相关消息。
- Message:消息体,根据不同消息协议格式,对消息内容进行编码封装,以在网络上进行传输。
主要的MQ协议介绍
- JMS(Java MessageService):由Sun公司早期提出的消息标准,旨在为java应用提 供统一的消息操作。从使用角度看,JMS和JDBC担任差不多的角色,用户都是根据相 应的接口可以和实现了JMS的服务进行通信,进行相关的操作。
- AMQP(advanced message queuing protocol):在2003年时被提出,最早用于解决金融 领域,不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的 说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不 从API层进行限定,而是直接定义网络交换的数据格式,天然是跨平台的。
- MQTT(Message Queuing Telemetry Transport):是IBM开发的一个即时通讯协议,该 协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和 致动器。格式简洁、占用带宽小。
- XMPP(Extensible Messaging and Presence Protocol):基于xml格式的消息协议, 多用于即时通讯及现场控测。协议扩展性好,安全性高、占用带宽大。
- 其它基于TCP/IP的自定义协议:部分中间件,如:redis、kafka、RocketMQ等根据自 身需要未严格遵循MQ规范,而是基于TCP\IP自行封装了一套协议,通过网络socket接 口进行传输,实现了MQ的功能。
主流MQ中间件的对比
MQ中间件的对比
MQ主要应用场景
应用解耦
应用之间通过引入MQ,使 应用之间的交互改为通过MQ消息来触发,降低应用之间的耦合。
解耦对比图
- 消息的生产者和消费者不强制要求了解对方的业务,只需发布和订阅消息即可。
- 生产者和消费者不用管对方的状态,无需同时在线。
- 最佳实践:引入消息驱 动模式,将业务操作的结果封装成事件消息通过MQ发布出来。
流量消峰
在高并发的场景中,可以通过引入MQ,将同步操作改为异步操作,通过消费者的数量,控制系统处理的并发度。
在秒杀等高并发场景中,短时间内会涌入大量的请求。通过MQ中间件将请求存储到队列中,通过消息消费者来处理业务,从而达到削峰的目的。
处理要点:
- 将原来的单个业务处理接口,拆分为业务提交接口与结果查询接口。
- 业务提交接口收到请求后,不直接处理相关业务,而是将请求发送给MQ中间件。
- 调用方通过轮询或回调的方式,异步获取业务处理结果。
事务最终一致性
使用MQ作为事务的二次提交的中间节点,存储事务状态,事务参与者通过MQ接收事务状态,进行相应的事务操作。
处理要点:
- 通过消息传递事务的状态。
- 需要用事务消息,保证业务事务与消息事务的一 致。
- 保证消费的幂等。
- 利用延时消息或定时任 务进行兜底。
数据同步
数据发生变更时,通过 MQ将数据变更信息,分发到数据库、Redis、Hbase、搜索引擎等同构或异构系统中。
处理要点:
- 接收数据库binlog 并通过MQ将数据库变动信息发布出去。
- 消息消费时要注意保证消费的顺序。
使用MQ的常见问题
消息的事务问题
应用程序本身的事务与mq中间件的事务之间状态可能不一致,导致消息多发或漏发。
MQ的事务问题原因
有两个层面:
- MQ中间件本身的事务: 大多数的MQ中间件都是支持事务的,但这个事务指的是MQ资源本身的事务。 例如:通过MQ发送多条消息时,由MQ中间件保证多条消息同时发送成功,或同时发送失败。
- 业务事务与MQ事务的一致性: 由于业务操作的数据库事务和消息中间件的事务分属于不同的资源,分产生分布式事务问题。除个别MQ中间件外,大部分MQ中间件都不支持分布式事务,无法保证业务事务与MQ事务的一致性。
- 消息事务嵌入到业务事务内: 当消息已发出,而业务事务由于各种原因失败回滚,会造成不该发送的消息发出去了。
- 消息事务在业务事务外: 当消息发送失败时,会造成消息的漏发。
常见解决方案
消息表
将消息的发送过程拆分两步:
- 在业务事务中将消息内容记录到消息表中。
- 通过定时任务,扫描消息表中未完成的消息记录,执行消息发送操作。
RocketMQ事务消息
事务消息发送步骤如下:
- 发送方将半事务消息发送至 RocketMQ ,RocketMQ 将消息持久化成功之后,向发送方返回Ack 确认消息已经发送成功,此时消息为半事务消息。
- 发送方开始执行本地事务逻 辑并根据本地事务执行结果向服务端提交二次确认 (Commit 或是 Rollback), 服务端收到Commit 状态则将半事务消息标记为可投递,订阅方最终将收到该消息;服务端收到Rollback状态则删除半事务消息,订阅方将不会接受该消息。
自研事务消息
- 将消息持久 化到数据库中,以保持与业务事务的一致。
- 利用spring事务同步器获得spring事务的状态。
- 异常重试时,需要调用确认接口,确认业务状态。
MQ的顺序问题
在数据同步等对顺序要求高的场景,要保证从消息发送、消息存储、消息消费的全链路的顺序一致。
顺序错乱的常见原因
消息发送:多线程并行发送,有顺序的消息被写入到不同的分区,异常重试。
消息消费:多消费者并行消费,或消费者内部启用多线程消费。异常重试。
为什么要保证消息的顺序?
如果业务上通过消息发送的数据是有前后顺序关系时,则必须保证消息的顺序。 例如:通过MQ同步数据时,如果增、改、删的顺序在同步时变为删、改、增,数据将不被删除。
常见解决方案
RocketMQ的顺序消息
- RocketMQ中的顺序消息分为:全局顺序消息 和分区顺序消息。
- 全局顺序消息:所有消息存在同一个topic的同一个分区下,消费时同一时刻只有一个消费者消费。
- 分区顺序消息:消息按分片键拆分到不同分 区上,消费时,同一分区,同一时刻只有一个消费者。
自研分区顺序消息
- 通过ZK协调生产者与消 费者。
- 生产者在启动时,将队列的分片信息注册到zk上发送消息时按分片键将消息拆分到不同队列上。
- 生产者发送消息时,根据分片键,将消息发送到不同的队列上。
- 消费者启动时从zk拉取队列分片信息,选择无消费者的分片进行监听,并将监听状态注册到zk上。
存在问题:
- 热点消息问题 由于消息的分片键选择不合理,造成各分区的消息不平衡。
- 消息的异常处理 当消费出现异常时,如果跳过此消息,会导致后续的消息顺序不正确,如果不跳过此消息,则会造成消息积压。
- 消息消费的并行度取决于topic的分区数。 有多少个分区,就有多少个消费者并行消费。
利用版本号保证顺序
- 消息的顺序需要选定一个分组键,消息的顺序基于此分组键来进行分组排序。
- 消息发送时,附加上消息的版本号或顺序号。
- 消息消费时,查询消息所在分组当前的版本号,并与当前消息的版本号比对。如果当前消息的版本号<=分组的版本号,则丢弃此消息。否则消费此消息。
- 消息消费成功,更新分组的当前版本号。
消息的防重与幂等
当同一消息被重复投递或消费时,能保证消息消费的幂等性。那什么是防重与幂等呢?
防重:保证相同的消息只最多只执行一次 。
幂等:保证相同的消息,每次执行时结果相同。
为什么需要做消息的防重或幂等
- 主流的消息中间件都只保证消息最少被投递一次,不保证只投递一次。
- 消息发送时重复发送当消息已被服务器持久化,但因网络闪断、服务器宕机等原因导致消息生产者认为消息发送失败,尝试再次发送消息。
- 中间件投递消息时重复投递消息中间件在对集群做缩、扩容时触发Rebalance,消费者可能收到重复消息 网络闪断、客户端重启时消息中间件在网络恢复后,会对消息尝试重新投递。
常见解决方案
- 通过数据库唯一索引防重: 可使用消息的id或业务id做为数据库中表的主键或唯一索引,通过数据库进行防重。
- 通过redis防重: 使用消息的id或业务id作为redis的防重key,通过redis防重。
- 通过业务状态控制做到消费幂等。 通过在sql语句中加入状态过滤条件,只执行指定状态的sql语句,达到幂等性。
- 使用redis防重时,由于redis与数据库之间可能 存在状态不一致,无法做到绝对的防重。如对防重 要求高,可使用数据库进行防重。
如何安全的对消息业务进升级?
当系统中同时存在新老消息时,或消费逻辑发生变化时,需要设计版本机制保证消息的兼容性。
升级时的问题
通常基于性能和高可用的考虑,同一队列的消费者会有多个。当消息的内容或消息处理逻辑发生变 化,消费者需要升级时,会产生以下问题:
- 无法保证新消息由新消费者处理,实现不了线上的灰度验证。
- 由于在程序升级期间会同时存在新老消息和消费者,造成新消息被老消费者消费,或老消息被新 消费者消费。这两种情况都可能造成问题。
如何安全的升级消费逻辑
注册消费者
消费者流程图
如上图所示,需要将消息增加上具体的版本号,并将生产者的ip和端口号注册到zk上,这样消费者在接收到与自己版本不同到消息时,可以在zk中获取到对应版本到消费者,并将消息内容通过rest等请求方式发送给对应版本号的消费者进行处理,来保证消息的正确处理。