rabbitmq 拾遗
1 在spring boot中开发 rabbitmq 的时候
通常来说,消息重试3次以上未处理成功,就是Consumer开发出现了严重问题。需要修改Consumer代码,提升版本/打补丁之类的处理方案。
通过全局配置文件,开启消息消费重试机制,配置重试次数。当RabbitMQ未收到Consumer的确认反馈时,会根据配置来决定重试推送消息的次数,当重试次数使用完毕,无论是否收到确认反馈,RabbitMQ都会删除消息,避免内存泄漏的可能。具体配置如下:
#开启重试 spring.rabbitmq.listener.retry.enabled=true #重试次数,默认为3次 spring.rabbitmq.listener.retry.max-attempts=5
2 在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两个选项用来控制消息的投递可靠性模式。
rabbitmq 整个消息投递的路径为:
producer->rabbitmq broker cluster->exchange->queue->consumer
message 从 producer 到 rabbitmq broker cluster 则会返回一个 confirmCallback 。
message 从 exchange->queue 投递失败则会返回一个 returnCallback 。我们将利用这两个 callback 控制消息的最终一致性和部分纠错能力。
3 mandatory和immediate是AMQP协议中basic.pulish方法中的两个标志位,它们都有当消息传递过程中不可达目的地时将消息返回给生产者的功能。具体区别在于:
1). mandatory标志位
当mandatory标志位设置为true时,如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返还给生产者;当mandatory设为false时,出现上述情形broker会直接将消息扔掉。
2). immediate标志位
当immediate标志位设置为true时,如果exchange在将消息route到queue(s)时发现对应的queue上没有消费者,那么这条消息不会放入队列中。当与消息routeKey关联的所有queue(一个或多个)都没有消费者时,该消息会通过basic.return方法返还给生产者。
概括来说,mandatory标志告诉服务器至少将该消息route到一个队列中,否则将消息返还给生产者;immediate标志告诉服务器如果该消息关联的queue上有消费者,则马上将消息投递给它,如果所有queue都没有消费者,直接把消息返还给生产者,不用将消息入队列等待消费者了。
注意:在RabbitMQ3.0以后的版本里,去掉了immediate参数支持,发送带immediate标记的publish会返回如下错误:
“{amqp_error,not_implemented,"immediate=true",'basic.publish'}”
为什么移除immediate标记? immediate标记会影响镜像队列性能,增加代码复杂性,并建议采用"设置消息TTL"和"DLX"等方式替代。
4 rabbitmq 脑裂问题
rabbitmq 脑裂问题,实质上是个网络分区问题, 确切来说是网络不稳定导致的问题。
rabbitmq集群的网络分区容错性不好,在网络比较差的情况下容易出错,最明显的就是脑裂问题了。
记住 不要将你的rabbitmq集群建立在广域网上,除非你使用federation或者shovel等插件。
所谓的脑裂问题,就是在多机集群中节点与节点之间失联,都认为对方出现故障,而自身裂变为独立的个体,各自为政,那么就出现了抢夺对方的资源,争抢启动,至此就发生了事故。
举个栗子:
A和B是集群上的两个节点,分别拥有一部分集群的数据a,b, 如果这时发生分区问题,两个节点无法通信,A以为B宕机,B以为A宕机,于是就出现了:
如果A存在B的备份,那就以完整数据运行,B存在A的数据备份,也是同样, 那这里就造成共享数据损坏。
如果 A,B 各自仅拥有a,b 的数据,那要么都无法恢复/启动,要么就瓜分数据运行。
分区现象会在rabbitmq UI 界面上提醒
Network partition detected
Mnesia reports that this RabbitMQ cluster has experienced a network partition. There is a risk of losing data. Please read RabbitMQ documentation about network partitions and the possible solutions.
判定条件
默认情况下,一个节点在60s之内不能连接上另一个节点(可用net_ticktime 参数调整),就判定这个节点挂了。即使之后节点网络又连通了,由于节点都认为对方挂了,所以Mnesia 还是会发送网络分区的情况并且将情况记录在日志中。
(许多围绕网络分区的细节都与Mnesia的行为相关。Mnesia是一个分布式数据库管理系统(DBMS),适合于电信和其它需要持续运行和具备软实时特性的Erlang应用,是构建电信应用的控制系统平台开放式电信平台(OTP)的一部分。)
除了网络失败原因,操作系统的挂起和恢复也会导致集群内部节点发生分区, 因为发生挂起的节点不会认为自己已经失败或者不能工作,但是其他节点会这么认为。比如:将节点运行在你的电脑上,你合上电脑;或者你将节点运行在虚拟机里,系统管理程序将节点挂起了。
如何恢复
人力干扰
部分重启。选择受信分区即网络环境好的分区,重启其他分区,让他们重新加入受信分区中,之后再重启受信分区,消除警告⚠️
全部重启。停掉整个集群,然后启动,不过要保证第一个启动的是受信分区。不然还没等所有节点加入,自己就挂了。
自动处理
rabbitmq 提供了3种模式
ignore 默认配置,即分区不做任何处理。要使用这种,就要保证网络高可用,例如,节点都在一个机架上,用的同一个交换机,这个交换机连上一个WAN,保证网络稳定。
pause_minority。优先暂停‘少数派’。就是节点判断自己在不在‘少数派’(少于或者等于集群中一半的节点数),在就自动关闭,保证稳定区的大部分节点可以继续运行。
autoheal, 关闭客户端连接数最多的节点。 这里比较有意思,rabbitmq 会自动挑选一个‘获胜’分区,即连接数最小的,重启其他分区。(如果平手,就选节点多的,如果节点也一致,那就以未知方式挑选‘获胜者’)。这个更关心服务的连续性而不是数据的完整性。
5 ack会用来判断是否删除消息的,ack 跟 prefetch count还有一个互动,在有 ack 的模式下,如果 prefetch count 是 N,那么没有收到 ack 的消息数量达到 N 就不会再向 channel 推消息了。
使用线程池时应用服务器既充当生产者又充当消费者,也是消息队列中间件的实现者,使用消息队列时中间件、生产者、消费者可以部署在不同的应用机器上(当然也可以部署在一台服务器上但很少有人这么用);
出于第2点线程池更适合非分布式的系统,但在分布式架构下消息队列明显是更优项;
使用消息队列会带来额外的网络开销;
消息队列的耦合性更低,可扩展性更好,适用于弱一致性的场景,如对log日志的解耦;
消息队列自动实现消息的持久化,中间已经实现了大量功能,如消息转发、消息拒绝、消息重试,以及对消息的一些监控,例如消息的消费状态、消息的消费速率等,使用线程池如果需要很多功能还要自己去实现,例如需要执行状态需要打印队列数量、计算消息消费速度;
在不同系统间的服务调用(调用协议也可能不一致)线程池很难实现或开销很大,这时候消息队列可以屏蔽不同机器或不同协议的问题;
使用消息队列会提升系统的复杂度,网络抖动怎么办?最大队列长度怎么设置?超时时间又设置多少?Qos又设置为多少?消费者多少个比较合适?Channel cache size又该设置为多少?业务线可能都是用同一个Mq,你占资源太多,或者设计不当可能会导致整个Mq故障。