文章目录
消息队列
Rabbit MQ
高可用以及分布式
-
单机模式
-
普通集群模式
通过扩展硬件的方式,提高节点的数量,在
每个节点上维护其它节点的元数据信息
.生产者在生产消息时,将消息放到其中一个节点
.消费者在拉取数据时,通过连接的实例存储的元数据查找queue所在的节点信息,再从对应的节点上区拉取数据.
优点: 可以通过扩展硬件机器的方式扩大集群的吞吐量.多个节点来服务一个queue的读写操作
缺点: 如果存放queue的节点宕机,数据会丢失.如果开启了消息持久化,则在节点重启的这个时间段内,无法从这个节点拉取数据. -
镜像集群模式
通过在不同节点维护queue副本的方式,来达到高可用.每当创建一个queue时,会将元数据信息以及queue信息同时同步到其它的节点.每次写消息时,也会将消息同步到其它的节点.
优点: 保证了集群的高可用性,即使其中的一个节点宕机,其它的节点因为包含了所有的元数据和数据,同样也可以提供服务.
缺点:
1. 集群内部的网络压力和消耗严重
2. 无法做到分布式的存储数据,如果遇到了单机queue数据量巨大,将无法处理.
消息投递确认方式
RabbitMQ 消息投递确认由两部分组成. Producer 投递到 Broker 确认 以及 Exchange 投递到Queue 确认
- Producer 投递到 Broker 确认: 通过
ConfirmCallback
回调重新处理生产者投递失败的情况- Exchange 投递到Queue 确认: 通过
ReturnCallback
回调重新处理投递失败的消息
Kafka
高可用以及分布式
kafka的高可用是由系统的基本架构来决定的.由多个 broker 组成,每个 broker 是一个节点;你创建一个topic,这个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个partition 就放一部分数据。也就是说一个Topic的数据是分布存在多个broker中的,这是一个天然的布式存储系统.
Kafka 0.8 以前没有HA(High Available),如果其中的一个broker宕机,则存储的数据都会丢失. Kafka 0.8以后提供了partition replica的方式,每个partitio 都会生成副本发送到其它的broker,同一个partition的所有副本内部还会选举一个leader,由这个leader 来进行数据的读写操作,保证数据的一致性.
写数据阶段: 由leader将数据落盘,各个follower主动pull数据,各自落盘并向master报告ack,当全部的follower都ack成功,leader返回客户端消息投递成功.
读数据阶段: 客户端只会从leader那里拉取数据,在所有follower都ack前,消息对于客户端是不可见的.
消息投递确认方式
由参数
request.required.acks
控制.
- 配置值
0
, 只管投递,不管消息的有效性- 配置值
1
, 系统默认值,只要partition leader ack 则确认数据已经投递成功- 配置值
-1
, 需要所有的follower ack后则确认数据投递成功
消息幂等性
由参数
enable.idempotence
控制.
- 消息投递幂等性
Kafka内部会为每个Producer 创建一个ID (
PID
),并在内部维护一个 <PID,Partition>->sequenceNum 映射信息.- 如果接受到producer消息的sequenceNum等于维护的sequenceNum + 1 则接受并处理消息
- 如果接受到producer消息的sequenceNum小于维护的sequenceNum + 1 则丢弃消息
- 如果接受到producer消息的sequenceNum小于维护的sequenceNum + N 则说明中间有消息没有接受到消息,会抛出
OutOfOrderSequenceException
异常
- 消息处理幂等性
由客户端自行处理
- 通过消息处理备忘录的方式,在某个地方存储消息是否被已经被消费,每次消费的时候先检查是否已经被消费
- 数据库幂等性可以通过唯一索引,或者操作前查询的方式确认是否可以插入
- Redis 操作set操作天然幂等性
引入消息队列产生的问题
数据丢失
1. 生产者数据丢失
- RabbitMQ
- 开启事务(
channel.txSelect
channel.txCommit
). 如果消息投递失败,会报异常,捕获异常,在异常处理模块重新处理消息发送.缺点是性能消耗严重,吞吐量会下降 - 开启confirm 机制, RabbitMQ 会为每一个消息分配值一个唯一ID ,如果失败则会回调定义的nack接口,在这个接口中重新处理消息发送.这是一个异步的过程
- 开启事务(
- Kafka
- Kafka 开启 acks = all,所有的partition replica ack后再确认消息投递成功
2. 消息队列数据丢失
- RabbitMQ
- 开启消息的持久化,RabbitMQ自己宕机恢复后会自动读取之前存储的数据.极低的可能性出现还没有持久化,RabbitMQ宕机,这时会丢失少量数据.和Producer的confirm机制结合 可以保证即使在极端情况下出现的没有持久化就宕机时Producer可以自己铳发消息
- RabbitMQ 开启持久化,要同时开启queue持久化,和Producer 的消息持久化 (
deliveryMode = 2
)
- Kafka
- 在broker宕机并重新选举新leader的时候,原leader 的一些信息还没同步到follower中,则会丢失一部分数据
- 开启 acks = all ,所有的follower都ack后才同志producer 消息投递成功
- 设置partition的最小副本数量大于1 保证leader 所在broker宕机时最少有一个follower可以作为被选leader
3. 消费者数据丢失
- RabbitMQ
- 关闭消息消费自动确认,改为手动确认
- Kafka
- 关闭自动提交offset,改为手动提交offset.如果在消费数据结束后没来得及提交offset, 需要自己保证消费数据的幂等性
消息顺序性
可能场景
mysql binlog 同步的系统,压力还是非常大的,日同步数据要达到上亿,就是说数据从一个 mysql 库原封不动地同步到另一个mysql 库里面去(mysql ->mysql)。常见的一点在于说比如大数据 team,就需要同步一个 mysql 库过来,对公司的业务系统的数据做各种复杂的操作。
在 mysql 里增删改一条数据,对应出来了增删改 3 条 binlog 日志,接着这三条 binlog发送到 MQ 里面,再消费出来依次执行起码得保证人家是按照顺序来的吧?不然本来是:增加、修改、删除;你愣是换了顺序给执行成删除、修改、增加,不全错了么。本来这个数据同步过来,应该最后这个数据被删除了;结果你搞错了这个顺序,最后这个数据保留下来了,数据同步就出错了。
解决方法
- RabbitMQ
拆分queue 一个消费者对应一个queue,在queue内保持顺序
- Kafka
producer在发送消息时指定规范的key值,消费者用内部队列分配N个队列存储不同的规则Key, 使用N个线程对数据进行消费
消息过期
Consumer 消费数据缓慢,导致数据在有效期内没被消费,消息丢列丢弃数据.最后数据丢失. 在高峰期过后,写程序将丢失的数据重新导入到消息队列中,让消息重新消费
消息堆积
可能场景
消费端每次消费之后要写 mysql,结果 mysql 挂了,消费端 hang 那儿了,不动了;或者是消费端出了个什么岔子,导致消费速度极其慢。
解决方法
- 修复出错的消费端代码
- 新增N个queue,将堆积的消息无耗时的转移到新建立的queue中
- 新增临时的消费端数量,对新增的queue进行消费