MQ
1.简介
消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以简单地描述为:当不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要使用消息队列的时候。消息至少被成功消费一次
1.1 解决的问题
- 应用解耦 多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败
- 流量削峰 广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况
- 异步消息 多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间
1.2 对比
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级 | 万级 | 十万级,高吞吐 | 十万级,高吞吐 |
topic数量对吞吐量的影响 | 无 | 无 | topic数量对吞吐量影响较小 | topic数量对吞吐量影响较大 |
时效性 | 毫秒级 | 微秒级 | 毫秒级 | 毫秒级以内 |
可用性 | 高,基于主从架构实现高可用 | 高,基于主从架构实现高可用 | 非常高,分布式架构 | 非常高,分布式架构 |
消息可靠性 | 有较低概率丢失数据 | 经过配置,可以做到0丢失 | 经过配置,可以做到0丢失 | |
功能支持 | 支持多种语言 | 支持java,c++(不稳定) | ||
使用场景 | 不建议使用 | 性能要求低,可靠性要求低的场景 | 适合对性能要求高,可靠性要求高的场景 | 大数据领域的实时计算、日志采集等场景 |
1.2 消息队列模型
1.2.1 点对点模型
每个消息只有一个接收者,一旦被消费,就不再在消息队列中,发送者和接收者间没有依赖性,发送者发送消息后,不管有没有接收者在运行,不会影响下一次发送
1.2.2 发布订阅模型
每个消息可以有多个订阅者,每个订阅者都可以接收到订阅主题的所有消息
1.3 消息队列实现分布式事务
分布式事务指事务的参与者,支持事务的服务器,资源服务器以及事务管理器分别位于不同的分布式系统的不同节点上,且属于不同的应用,分布式事务需要保证这些操作要么全部成功,要么全部失败
1.3.1 RocketMQ事务原理
- 在消息队列上开启一个事务主题
- 事务中第一个执行的服务发送一条“半消息”,半消息和普通消息的唯一区别是,在事务提交之前,对于消费者来说,这个消息是不可见的
- 给消息队列发送半消息成功后,发送半消息的服务就会开始执行本地事务,根据本地事务执行结果来决定事务消息提交或者回滚,RocketMQ提供事务反查来解决异常情况,如果RocketMQ没有收到提交或者回滚的请求,Broker会定时到生产者上去反查本地事务的状态,然后根据生产者本地事务的状态来处理这个“半消息”是提交还是回滚。值得注意的是我们需要根据自己的业逻辑来实现反查逻辑接口,然后根据返回值Broker自己做提交或者回滚,而且这个反查接口已经做到了无状态的,请求到任意一个生产者节点都会返回正确的数据
- 本地事务成功后会让这个“半消息”变成正常消息,供分布式事务后面的步骤执行自己的本地事务
2. RocketMQ
2.1 整体架构
RocketMQ集群中包含4个模块:Nameserver,Broker,Producer,Consumer。
- Nameserver: 存储当前集群所有Broker的信息,Topic跟Broker的对应关系,相当于nacos的服务注册发现中心
- Broker: 集群最核心模块,主要负责Topic消息存储,消费者的消费位点管理(消费进度)
- Producer: 消息生产者,每个生产者都有一个ID(编号),多个生产者实例可以共用同一个ID(生产者集群)
- Consumer: 消息消费者,每个订阅者也有一个ID(编号),多个消费者实例可以共用同一个ID(消费者集群)
2.2 特性
2.2.1 消息消费顺序
消费消息时,严格按照发送的顺序来进行消息的消费,RocketMQ能严格的保证消息的消费顺序
全局顺序
指定一个topic,所有消息严格按照FIFO进行消费,性能不高(一个队列)
分区顺序
指定一个topic,所有消息根据sharding key进行区块分区,具有相同的sharding key的消息将被发送到同一个队列,同一个队列内的消息严格按照FIFO顺序进行消费
2.2.2 消息可靠性
发送端消息可靠性
生产者在发送消息时,同步消息失败会重投,异步消息有重试,oneway没有任何保证。消息重投保证消息尽可能发送成功、不丢失,但可能会造成消息重复,消息重复在RocketMQ中是无法避免的问题。消息重复在一般情况下不会发生,当出现消息量大、网络抖动,消息重复就会是大概率事件。另外,生产者主动重发、consumer负载变化也会导致重复消息
重试策略:
- retryTimesWhenSendFailed:同步发送失败重投次数,默认为2,因此生产者会最多尝试发送retryTimesWhenSendFailed + 1次。不会选择上次失败的broker,尝试向其他broker发送,最大程度保证消息不丢。超过重投次数,抛出异常,由客户端保证消息不丢。当出现RemotingException、MQClientException和部分MQBrokerException时会重投。如果因为超时,那么便不再重试。
- retryTimesWhenSendAsyncFailed:异步发送失败重试次数,默认为2,异步重试不会选择其他broker,仅在同一个broker上做重试,不保证消息不丢。如果因为超时,那么便不再重试。
- retryAnotherBrokerWhenNotStoreOK:消息刷盘(主或备)超时或slave不可用(返回状态非SEND_OK),是否尝试发送到其他broker,默认false,重要的消息可以开启。
存储端消息可靠性
出现情况 | 影响 |
---|---|
Broker正常关闭 | 正常启动并恢复所有数据 |
Broker异常Crash | 同步刷盘可以保证数据不丢失,异步刷盘可能导致少量数据丢失 |
OS Crash | 同步刷盘可以保证数据不丢失,异步刷盘可能导致少量数据丢失 |
机器掉电,但是能立即恢复供电情况 | 同步刷盘可以保证数据不丢失,异步刷盘可能导致少量数据丢失 |
机器无法开机(可能是cpu、主板、内存等关键设备损坏) | 单节点故障 |
磁盘设备损坏 | 单节点故障 |
解决单节点故障:采用增加Slave节点,主从异步复制仍然可能有极少量数据丢失,主从同步复制可以完全避免单节点故障问题,但是对性能会有一定影响
消费端消息可靠性
消费重试
消费者从RocketMQ拉取到消息之后,需要返回消费成功来表示业务方正常消费完成。因此只有返回CONSUME_SUCCESS才算消费完成,如果返回CONSUME_LATER则会按照不同的messageDelayLevel时间进行再次消费,时间分级从秒到小时,最长时间为2个小时后再次进行消费重试,如果消费满16次之后还是未能消费成功,则不再重试,会将消息发送到死信队列,从而保证消息存储的可靠性
死信队列(默认有效期3天,不会被重复消费)
消费满16次还未能成功消费的消息,消息队列不会立刻将消息丢弃,而是将消息发送到死信队列,其名称是在原队列名称加%DLQ%,如果消息进入了死信队列,则可以通过RocketMQ提供的相关接口从死信队列获取到相应的消息,保证了消息消费的可靠性
回溯消费
回溯消费是指Consumer已经消费成功的消息,或者之前消费业务逻辑有问题,现在需要重新消费。要支持此功能,则Broker存储端在向Consumer消费端投递成功消息后,消息仍然需要保留。重新消费一般是按照时间维度,例如由于Consumer系统故障,恢复后需要重新消费1小时前的数据。RocketMQ Broker提供了一种机制,可以按照时间维度来回退消费进度,这样就可以保证只要发送成功的消息,只要消息没有过期,消息始终是可以消费到的
2.3 集群模式
2.3.1 单Master
只有一个broker,会出现单点问题,master宕机后服务无法使用
2.3.2 多Master
多个master,没有slave。同一个topic的queue平均分布在不同的master节点上
优点:配置简单,单个master宕机对应用无影响,在磁盘配置为RAID10(磁盘阵列)时,即使机器宕机不可恢复情况下,由于RAID10磁盘非常可靠,消息也不会丢失(异步刷盘会丢失少量消息)
缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可以订阅(不可消费),消息实时性会受影响
2.3.3 多Master多Slave模式-异步复制
Broker集群由多个master构成,每个master又配置多个slave(在配置了RAID10磁盘阵列的情况下,一个master配置一个slave即可)。
master与slave的关系是主从关系,即master负责处理消息的读写请求,slave仅负责消息的备份与master宕机后的角色切换
特点,master宕机后,slave自动切换为master,由于短暂延迟,会出现少量消息丢失问题
2.3.4 多Master多Slave模式-同步双写
master收到消息后等待slave同步数据成功后才向producer返回成功ACK,即master与slave都要写入成功后才会返回成功ACK。
优点:安全性高,不会丢失数据
缺点:性能低,性能比异步复制模式略低(大约低10%左右),目前版本,master宕机后,slave无法自动切换成master
主从模式从master/slave读写方式
(主从消息同步是slave从master主动拉取消息)
-
生产者在写消息时,一般优先写入master
-
消费者拉取消息时,master和slave都有可能,根据msater的负载情况和slave的同步情况。msater负载过高,建议下次从slave获取消息,slave同步不完全,建议下次从master获取消息
broker宕机分析
slave宕机 对系统影响不大,读写压力全部集中到master上
master宕机 基于Dledger的raft实现自动选主
2.4 存在的问题及解决办法
2.4.1 重复
生产者重复发送消息
不建议在发送时处理重复消息,对系统性能影响较大
- 业务判断法
RocketMQ支持消息查询的功能,消息发送前去RocketMQ查询一下是否已经发送过该条消息,存在则不发送,不存在发送到RocketMQ。在高并发的场景下,每条消息在发送到RocketMQ时都去查询一下,会影响接口的性能
- redis缓存法
redis分布式锁,在发送消息到RocketMQ成功之后,向redis中插入一条数据,如果发生重试,则先去redis中查询是否存在,存在的话不再发送消息。redis宕机就会查不到消息信息
消费者重复消费消息
- 数据库表唯一约束
在数据库中创建一个消息表,生产者在发送消息时,赋予消息一个唯一标识,消费者在消费消息后,便在数据库中插入对应的消息消息,新消息达到时,根据新消息的唯一标识去数据库中查询是否存在,存在则丢弃该消息,不存在,消费完后插入数据库
存在的话不再发送消息。redis宕机就会查不到消息信息
消费者重复消费消息
- 数据库表唯一约束
在数据库中创建一个消息表,生产者在发送消息时,赋予消息一个唯一标识,消费者在消费消息后,便在数据库中插入对应的消息消息,新消息达到时,根据新消息的唯一标识去数据库中查询是否存在,存在则丢弃该消息,不存在,消费完后插入数据库