消息中间件的作用
工作中经常用到消息中间件来解决系统间的解耦问题或者高并发消峰问题,但是消息的可靠性如何保证一直是个很大的问题,万一消息丢了怎么办?什么情况下消息就不见了呢?下面通过这篇文章,我们就聊聊RabbitMQ 中消息的可靠性如何解决的?
rabbitMQ的消息传递过程
rabbitMQ的丢失场景
1.生产者生产消息到RabbitMQ Server 消息丢失场景
1) 外界环境问题导致:
发生网络丢包、网络故障等造成RabbitMQ Server端收不到消息,因为生产环境的网络是很复杂的,网络抖动,丢包现象很常见,下面会讲到针对这个问题是如何解决的。
2) 代码层面,配置层面,考虑不全导致消息丢失
事例1: 一般情况下,生产者使用Confirm模式投递消息,如果方案不够严谨,比如RabbitMQ Server
接收消息失败后会发送nack消息通知生产者,生产者监听消息失败或者没做任何事情,消息存在丢失风险;
事例2:
生产者发送消息到exchange后,发送的路由和queue没有绑定,消息会存在丢失情况,下面会讲到具体的例子,保证意外情况的发生,即使发生,也在可控范围内。
2.RabbitMQ Server中存储的消息丢失或可靠性不足
1)消息未完全持久化,当机器重启后,消息会全部丢失,甚至Queue也不见了
假如:你仅仅持久化了Message,而Exchange,Queue没有持久化,这个持久化是无效的。
记得之前公司有一哥们忘记持久化Queue导致机器重启后,Queue不见了,自然Message也丢失了。2)单节点模式问题,如果某个节点挂了,消息就不能用了,业务可能瘫痪,只能等待
如果做了消息持久化方案,消息会持久化硬盘,机器重启后消息不会丢失;但是还有一个极端情况,这台服务器磁盘突然坏了(公司遇到过磁盘问题还是很多的),消息持久化不了,非高可用状态,这个模式生产环境慎重考虑。
3)普通集群模式:某个节点挂了,该节点上的消息不能用,有影响的业务瘫痪,只能等待节点恢复重启可用(建立在消息持久化)
虽然这个模式进步了一点点,多个节点,但是消息还是不能保证可靠,为什么呢?
因为RabbitMQ 集群模式有点特殊,队列的内容仅仅存在某一个节点上面,不会存在所有节点上面,所有节点仅仅存放消息结构和元数据(可以理解为索引,这也是为了提高性能,如果每次把所有内容同步到所有节点是有开销代价的)。
这里有三个节点,通常情况下一个磁盘节点,两个内存节点,首先先说明下, Queue1 内容仅仅存在节点note1上面,在创建队列的时候已经固定了,note2,note3 仅仅存放的是元数据,这个一定要清楚,Producer发送消息到note2,note2 会同步元数据到其他节点,内容会同步note1。
那我们想下,图中的Q1问题,note1挂了,这个节点的Queues全部暂时不可用,节点恢复后可用。
我们说下图片中备注2中的问题,Producer发送消息到note2,note2在同步note1前note1挂了,此时你的心情是怎么样的。。。后面会讲具体的策略
4)镜像模式:可以解决上面的问题,但是还是有意外情况发生
比如:持久化的消息,保存到硬盘过程中,当前队列节点挂了,存储节点硬盘又坏了,消息丢了,怎么办?下面会详细介绍
3.RabbitMQ Server到消费者消息丢失
消费端接收到相关消息之后,消费端还没来得及处理消息,消费端机器就宕机了,此时消息如果处理不当会有丢失风险,后面会讲到如何处理这个情况,消费端也有ack机制
rabbitMQ 消息丢失机制
1. 生产者生产消息到RabbitMQ Server可靠性保证
这个过程,消息可能会丢,比如发生网络丢包、网络故障等造成消息丢失,一般情况下如果不采取措施,生产者无法感知消息是否已经正确无误的发送到exchange中,如果生产者能感知到的话,它可以进行进一步的处理动作,比如重新投递相关消息以确保消息的可靠性。
1.1 通常有一种方案可以解决:就是 AMQP协议提供的一个事务机制
RabbitMQ客户端中Channel 接口提供了几个事务机制相关的方法:
channel.txSelect :开始事务
channel.txCommit:提交事务
channel.txRollback:回滚事务
不推荐中这样做,因为这是同步的,一旦发生阻塞十分影响效率
1.2 幸运的是RabbitMQ提供了一个改进方案,即发送方确认机制(publisher confirm)
首先生产者通过调用channel.confirmSelect方法将信道设置为confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一deliveryTag和multiple参数),这就使得生产者知晓消息已经正确到达了目的地了。
其实Confirm模式有三种方式实现:
串行confirm模式:producer每发送一条消息后,调用waitForConfirms()方法,等待broker端confirm,如果服务器端返回false或者在超时时间内未返回,客户端进行消息重传。
批量confirm模式:producer每发送一批消息后,调用waitForConfirms()方法,等待broker端confirm。
异步confirm模式:提供一个回调方法,broker confirm了一条或者多条消息后producer端会回调这个方法。
我们分别来看看这三种confirm模式