c# Rabbit 消息确认机制

本文围绕消息确认展开,分析了消息丢失的多种情况,如网络、路由、队列参数等问题导致的丢失。介绍了发送确认的方法,包括AMQP事务、Confirm模式等,以及接收确认的方式,如手动和自动确认。还给出了应对消息丢失的策略,如设置备用交换机、队列持久化等。

不想看细节 直接看总结

总结

1.网络出现问题,或信道问题,程序层将会引发错误,捕获错误信息即可发现消息丢失
2.路由出现问题,消息未被路由到队列中。
有两种方法可以获得通知
第一种: 投递消息时mandatory 参数设置为true 如果消息未被路由到队列中将会引发 basic.return 事件
第二种: 配置交换机的备用交换机 alternate-exchange 消息无法被路由时,消息将投递到备用交换机,备用交换机 使用扇形交换机绑定一个队列即可得到未被路由的消息
两者同时启用的情况,并不冲突,第一种的条件是未被路由到队列的时候才会触发,如果使用第二种方法使消息通过备用交换机到达了队列,那么basic.return 将不会触发,如果 备用交换机绑定队列,或者无法路由到队列,将会触发basic.return 事件
3.消息到达队列,由于队列的x-message-ttl 参数导致消息超时被删
通过配置消息队列的参数 x-dead-letter-exchange(交换机) 与 x-dead-routing-key(路由键) 解决
当消息超时被删除时会被投递到x-dead-letter-exchange交换机 使用 x-dead-routing-key路由键
4.消息到达队列,由于队列的x-max-length 或者 x-max-length-bytes 的限制.
这时候有两种情况:
第一种: x-overflow 参数为drop-head(这个是默认的): 将会使消息队列把前面的消息删除,以便加入新的消息,
删除的消息捕获,参照3
第二种:x-overflow 参数为reject-publish: 将使消息队列拒收超出范围的消息。
解决方案 使用生产者的Confirm 模式, 这将引发消息投递失败

5.消费者取出消息后,消费者未及时消费,消费者宕机,使用消费者手动确认模式。(这可能引发新的问题。重复消费)

后面的都是废话

分析

如何保证消息一定被消费者接收?
如果,发送成功 + 接收成功 那么就能保证消息一定被消费者接收。
有些时候,我们希望消息被及时处理(客户端及时响应),那么应该再加上消息超时机制。当消息处理超时时提示玩家服务器繁忙(这种情况应该很少,还是可以接受的)

发送确认

使用 AMQP事务 来确认消息发送

//开启事务
channel.TxSelect();
//事务提交
channel.TxCommit();
//事务回滚
channel.TxRollback();
示例:

            try {
                channel.TxSelect();
                channel.BasicPublish("aaa","aabb",false,null,Encoding.UTF8.GetBytes(count.ToString()));
                Debug.Log("发送的数据:" + count);
                count ++;
                channel.BasicPublish(exchange,routingkey,false,null,Encoding.UTF8.GetBytes(msg));
                channel.BasicPublish("aaa","aabb",false,null,Encoding.UTF8.GetBytes(count.ToString()));
                Debug.Log("发送的数据:" + count);
                count  ++;
                Debug.Log("12222");
                channel.TxCommit();
                Debug.Log("3333");
            } catch (Exception e) {
                
                Debug.Error("发送失败:" + e.ToString());
                if(channel.IsOpen)
                channel.TxRollback();
            }

要注意的是:
BasicPublish 并未得到实际执行,也就是说 及时 exchange 不存在 BasicPublish 执行也不会报错,直到执行 TxCommit 时,事务中的命令才会被正常执行, 如果Boker 执行错误(任何一条指令执行错误)将会回滚,也就是说符合数据一致性,比如说投递 ABC三条消息,当 执行投递C消息是引发了异常,则AB两条消息将会取消发送。
TxRollback 回滚的使用, 为何加 channel.isOpen 判断呢,因为异常可能本是由channel 引发的,这时候使用channel回滚将会导致错误。(百度有很多都是错误的写法,要注意啊!)
TxRollback 必须在 TxCommit 执行前使用,也就是说 TxCommit 一定要放到 try 语句的末尾,不然先执行了TxCommit TxRollback 将会不起作用。
TxRollback 一般是使用在事务中穿插了C# 语言的代码,C#代码异常使其回滚。 如果中间只用Rabbit操作,无需使用TxRollback。 因为 TxCommit 执行错误,Rabbit 会自动回滚,而且Rabbit操作的异常会导致信道关闭,如果这时候使用TxRollback 不当毫无作用而且还会引起异常。

事务中,如果消息无法路由到队列,是会正常提交,也就是说事务无法保证消息一定到达队列,
只能保证消息到达Boker 与一致性。

事务的最重要的作用是其一致性。如果是单独投递一条消息,使用此来确认,显然毫无用处。

事务的缺点。慢!。
无法保证消息一定到达消息队列。

使用Confirm模式来确认消息发送

单条消息确认

            try {
                channel.ConfirmSelect();
                channel.BasicPublish(exchange, routingkey, mandatory, false, null, Encoding.UTF8.GetBytes(msg));
                if (channel.WaitForConfirms())
                    Debug.Log("消息发送成功!");
                else
                    Debug.Log("消息发送失败!");
            } catch (Exception e) {
                Debug.Error("发送消息错误:" + e.ToString());
            }

多条消息的确认

            try {
                channel.ConfirmSelect();
                channel.BasicPublish("aaa", "aabb", false, null, Encoding.UTF8.GetBytes(count.ToString()));
                count ++;
                channel.BasicPublish("aaa", "aabb", false, null, Encoding.UTF8.GetBytes(count.ToString()));
                count ++;
                channel.BasicPublish("aaa", "aabb", false, null, Encoding.UTF8.GetBytes(count.ToString()));
                count ++;
                channel.BasicPublish("aaa", "aabb", false, null, Encoding.UTF8.GetBytes(count.ToString()));
                count ++;
                channel.WaitForConfirmsOrDie(); //发送失败将引发异常
            } catch (Exception e) {
                Debug.Error("发送消息错误:" + e.ToString());
            }

值得注意的是:channel.ConfirmSelect() 每个信道只需要执行一次,后面都是处于确认模式,这是我一直理解错误的一个问题

异步确认模式
实现 BasicAcks 事件来确认消息是否发送成功
实现 BasicNAcks 事件来确认未发送成功的消息 队列本身的条件限制 ,且溢出模式设置为 reject-publish ,将会接收到的消息

异步效率最高
使用Confirm模式也无法获取消息是否到达队列。

发送确认到达队列

投递消息时使用 mandatory +BasicReturn 事件 来确认消息是否到达队列
使用BasicReturn 事件可以获取到未能正确到达队列的消息。

声明交换机时使用策略 alternate-exchange 设置备用路由,
当消息无法被路由时将会投递到备用路由也可以获取到未正确到达队列的消息。

使用以上两种方法 + Confirm 模式/ 事务确认模式,可以确保消息被送达到 消息队列

送达消息队列后的消息丢失

当消息被送达消息队列后,消息也可能被丢失,
1.有可能系统宕机, 使用队列持久化可以解决此问题。(如果消息很重要,,持久化废效率)
2. 有可能设置了消息超时时间,消息一段时间未被消费将会被扔弃,声明队列时使用策略x-dead-letter-exchange 与 x-dead-routing-key 来获取被扔弃的消息。 被扔弃的消息将会被投递到 x-dead-letter-exchange交换机使用 x-dead-routing-key 路由键来投递消息。设置此选项可以获取到超时的消息。
3. 消息有可能由于 x-max-length 与 x-max-length-byte 的限制被扔弃, x-overflow: 为 drop-head 的情况下,同样的可以使用2号方式来解决
4. 队列被删除…这个没办法,自己控制好。

接收确认

使用BasicConsume 与 BasicGet 获取消息都有个一个参数 noAck,
noAck =true 时表示,代码执行完毕后消息被自动确认,确认后消息将会从消息队列中删除
noAck= false 时表示 消息需要手动确认
使用BasicAck方法来确认一条或多条消息,如果确认的消息不存在 则会产生异常,要注意
使用BasicNack方法来拒绝一条或多条消息,requeue 表示是否重新入列。false 表示消息虽然被拒绝确认但是不会重新回到队列,true 表示会消息会被重新回到队列中
使用BasicReject方法拒绝一条消息,requeue 表示是否重新入列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值