rabbitmq学习笔记及架构(包括集群)
介绍
总的来说,rabbitmq使用erlang(Elixir,排名50-60)是erlang的友好版,编译成erlang执行码)语言编写,其架构类似于servlet容器运行servlet应用,底层是erlang VM、然后是erlang节点,上面是应用。如下所示:
每个MQ中运行的应用可通过rabbitmqctl status看到,如下:
[root@iZbp112kwadw1qt8emked5Z logs]# rabbitmqctl status Status of node rabbit@iZbp112kwadw1qt8emked5Z ... [{pid,30445}, {running_applications, [{rabbitmq_tracing,"RabbitMQ message logging / tracing","3.5.6"}, {rabbitmq_management,"RabbitMQ Management Console","3.5.6"}, {rabbitmq_management_agent,"RabbitMQ Management Agent","3.5.6"}, {rabbit,"RabbitMQ","3.5.6"}, {os_mon,"CPO CXC 138 46","2.2.7"}, {rabbitmq_web_dispatch,"RabbitMQ Web Dispatcher","3.5.6"}, {webmachine,"webmachine","1.10.3-rmq3.5.6-gite9359c7"}, {mochiweb,"MochiMedia Web Server","2.7.0-rmq3.5.6-git680dba8"}, {amqp_client,"RabbitMQ AMQP Client","3.5.6"}, {xmerl,"XML parser","1.2.10"}, {inets,"INETS CXC 138 49","5.7.1"}, {mnesia,"MNESIA CXC 138 12","4.5"}, {sasl,"SASL CXC 138 11","2.1.10"}, {stdlib,"ERTS CXC 138 10","1.17.5"}, {kernel,"ERTS CXC 138 10","2.14.5"}]},
各自的含义如下:
在集群模式下的时候,其结构如下:
从逻辑上讲,RabbitMQ集群是单一的message broker(一般来说,建议rabbitmq集群使用三个节点,当然在3.8之前因为采用的是副本模式,所以两个节点也是可以的,3.8采用了投票的模式,需要2N+1个节点),消息队列消费者连接集群中的任一个节点都可以。如果使用典型的负载均衡机制比如LVS或者HAProxy,client只需要访问单一一个地址,由负载均衡器负责load balance,将访问请求分发给各个节点,因为rabbitmq java client支持配置多个地址,所以LVS不是必须也可以做到高可用。
对于Queue来讲,虽然它的metadata在每个节点上都有,但只有在它被创建的那个RabbitMQ 节点上才具有完整的信息:比如state/contents等,这个node被称为此queue的owner node。其他节点只知道这个queue的metadata信息和一个指向queue的owner node的指针。
如果一个client访问RabbitMQ的节点上没有需要的queue的完整信息,RabbitMQ将根据这个指针将请求转发到owner node。如下:
如果这样的话,那万一主节点挂了,queue岂不是又单点了,rabbitmq自然是不会傻到这程度。所以一般来说,对于需要可信传递的消息,应该结合队列级别的mirror(具体是客户端建立queue的时候设置还是policy设置,仍然看具体的开发人员配备,并且rabbitmq队列级别的高可用采用的是半同步模式,而不是异步模式,所以吞吐量会更低、但是可以保证数据一致性,而不会出现数据不一致的情况)。
参考:http://www.rabbitmq.com/ha.html,rabbitmq 3.8开始引入了类似raft的机制,目前采用3.8的生产似乎并不多。
AMQP规范要求
https://www.rabbitmq.com/tutorials/amqp-concepts.html,AMQP协议的核心概念,包括必须实现的队列如amqp.direct、amqp.topic等。
https://zhuanlan.zhihu.com/p/29463325
rabbitmq消费者获取消息的机制
rabbitmq支持消息推和拉两种模式,默认情况下是推的模式,不管是推还是拉,都能够支持流控,但是拉的模式成本会高很多(https://www.cnblogs.com/SupPilot/p/10218377.html)。但是某些场景必须要使用拉模式,比如优先级队列。也有些仅用于推模式,比如qos,它是服务端的限流机制。
丰富的插件体系
不得不说AMQP开了个好头,rabbitmq的插件体系相当丰富,实现了分片插件、优先级队列插件等等,相比redis的非持久化、kafka的主打超高并发,如果只是需要一个正宗、性能也不错的MQ,rabbitmq是相当不错的,差不多no 1了,不比商业版的MQ如IBM MQ差。
rabbtimq的消费者ack机制
默认情况下,采用no-ack的方式进行确认,也就是说,每次Consumer接到数据后,而不管是否处理完 成,RabbitMQ Server会立即把这个Message标记为完成,然后从queue中删除了。
为了保证数据不被丢失,RabbitMQ支持消息确认机制,即acknowledgments。为了保证数据能被正确处理而不仅仅是被Consumer收到,那么我们不能采用no-ack。而应该是在处理完数据后发送ack。
在处理数据后发送的ack,就是告诉RabbitMQ数据已经被接收,处理完成,RabbitMQ可以去安全的删除它了。
如果Consumer退出了但是没有发送ack,那么RabbitMQ就会把这个Message发送到下一个Consumer。这样就保证了在Consumer异常退出的情况下数据也不会丢失。
需要注意的是,rabbitmq并没有使用超时机制判断消息是否有异常,而是通过Consumer的连接中断来确认该Message并没有被正确处理,这种做法各有利弊(kafka用的就是超时)。
如果忘记了ack,那么后果很严重。当Consumer退出时,Message会重新分发。然后RabbitMQ会占用越来越多的内存,由于 RabbitMQ会长时间运行,因此这个“内存泄漏”是致命的。在控制台Queue选项中可以看到有很多unack的消息,如下:
当停掉我们的应用也就是断开和Broker之间的连接,这些unack的消息就会变成等待消费的消息,当我们重启应用的时候,Broker会将这些消息重新发送给我们的消费者,就会导致消费者重复消费消息。
Nack的作用
如果连接没有断开应用要通知服务器让消息重新发送:可以通过channel.nack(message)来让不通过的消息再次进入消息队列。nack有一个额外的参数requeue,如果为true,重新进入队列,否则被丢弃或进入死信队列。
重复消费
不管哪种MQ,都需要消费端保证幂等性以避免重复消费,因为有很多原因会导致消息重复投递(重复发送、异常未ack等),要想框架层面做到幂等,需要确定一个字段用于区分唯一消息,比如消息头上的bizId,可以针对每个队列自定义。
DeliveryTag
delivery_tag是消息投递序号,每个channel对应一个(long类型),从1开始到9223372036854775807范围,在手动消息确认时可以对指定delivery_tag的消息进行ack、nack、reject等操作。
每次消费或者重新投递requeue后,delivery_tag都会增加,理论上该正常业务范围内,该值永远不会达到最大范围上限。可以根据每个消费者对应channel的delivery_tag消费速率计算到达最大值需要的时间。
假设:每秒钟一个消费者可以消费1000w个消息(假设每个消费者一个channel),则 9223372036854775807 / (60 * 60 * 24 * 365 * 1000w) = 29247年后能达到上限数值。
rabbitmq的发送者ack机制及可靠发送
默认交换机
默认交换机(default exchange)实际上是一个由rabbitmq服务器预先声明好的没有名字(名字为空字符串)的直连交换机,显示为(AMQP default),如下。
它有一个特殊的属性使得它对于简单应用特别有用处:那就是每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。
举个栗子:当你声明了一个名为 “search-indexing-online” 的队列,AMQP 代理会自动将其绑定到默认交换机上,绑定(binding)的路由键名称也是为 “search-indexing-online”。因此,当携带着名为 “search-indexing-online” 的路由键的消息被发送到默认交换机的时候,此消息会被默认交换机路由至名为 “search-indexing-online” 的队列中。换句话说,默认交换机看起来貌似能够直接将消息投递给队列,尽管技术上并没有做相关的操作。
所以,理论上是不需要创建人工的交换机也完全可以运行的。也就是和kafka一个模式。
exchange、queue、routingKey的关系参见http://www.360doc.com/content/14/0608/22/834950_384933697.shtml。
消息的有效期
RabbitMQ允许你为消息和队列设置有效期TTL(time to live),可以在队列声明时使用可选参数设置,也可以使用policies命令设置。消息的TTL可以应用于一个队列,一组队列又或者针对每个消息进行设置。为一个队列设置消息的TTL,可以通过命令指定policy的message-ttl参数值,或者在队列声明时,指定相同名称的参数值。TTL还可以基于消息逐个设置,在发送消息时使用AMQP的basic.publish方法指定expiration字段的值,当队列和消息同时设置了TTL,那么服务器将会选择其中较小的值。例如:
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() .expiration("60000").build(); channel.basicPublish("my-exchange", "routing-key", properties, messageBodyBytes);
服务器能保证在使用basic.deliver(推模式)或basic.get-ok(拉模式)的方式获取消息时,死亡消息将不会被传递给消费者。而且,服务器将尝试在消息过期时或之后将其删除。
死信
队列中的消息可能会变成死信消息(dead-lettered),进而当以下几个事件任意一个发生时,消息将会被重新发送到一个交换机:
- 消息被消费者使用basic.reject或basic.nack方法并且requeue参数值设置为false的方式进行消息确认(negatively acknowledged)
- 消息由于消息有效期(per-message TTL)过期
- 消息由于队列超过其长度限制而被丢弃
死信交换机(DLXs)就是普通的交换机,可以是任何一种类型,也可以用普通常用的方式进行声明。
对于任何一个队列,死信交换机可以通过在客户端使用队列参数进行声明,或者是在服务器使用policy命令进行声明创建。当同时使用这两种方式声明一个死信交换机时,队列参数声明的方式将被优先使用。
对于进入的私信交换机的消息,可以使用原来的路由键,也可以指定新的路由键。args.put("x-dead-letter-routing-key", "some-routing-key"),这个貌似意义不大,要设置的话,一般加前缀或后缀。
binding key和routing key的区别
bindingkey是队列和交换机之间的绑定key,而routingkey是生**者发给交换机的一个信息,当routingkey和bindingkey能对应上的时候就发到相应的队列中。如果exchange、bindKey、routingKey没有设置正确,会导致我们发送给交换器(exchange)的消息,由于没有正确的RoutingKey而消息丢失。
如果我们希望知道那些消息经过exchange之后,没有被正确的存入消息队列,那么应该如何进行处理。此时有两种方案:
方案一:使用 mandatory 参数配合 ReturnListener 来进行解决。当mandatory为true时,当交换器无法根据自身的类型和路由键找到一个符合条件的队列时,那么RabbitMQ会调用 Basic.Return 命令将消息返回给生产者。生产者使用ReturnListener 来监听没有被正确路由到消息队列中的消息。默认为false:丢弃。
方案二:使用备份交换器 (alternate exchange) 来进行解决。声明交换器可以在channel.exchangeDeclare的时候 添加 alternate-exchange 参数来实现
具体可见https://blog.csdn.net/fu_huo_1993/article/details/88224974,https://www.rabbitmq.com/ae.html。
rabbitmq的prefetch
ack模式下用于流控和负载均衡的目的,参见https://my.oschina.net/hncscwc/blog/195560,需要注意的是:qos即服务端限流,qos对于拉模式的消费方式无效。在调用basicConsume方法前要先调用basicQos方法(basicQos(int prefetchSize, int prefetchCount, boolean global))批量推送的总数量,ack多少之后才推送,channel级还是消费者级别控制。
网络分区/脑裂
https://www.cnblogs.com/liyongsan/p/9640361.html
https://www.cloudamqp.com/blog/2019-10-18-rabbitmq-version-3-8.html
3.8的Quorum queue采用raft协议,可以解决网络分区问题,但是有限制,不支持TTL,这样就实现不了延时队列了。
https://www.cloudamqp.com/blog/2019-03-28-rabbitmq-quorum-queues.html
对于不同的CA(Raft 算法就可以保障 CAP 中的 C 和 P,但无法保障 A:网络分区时并不是所有节点都可响应请求,少数节点的分区将无法进行服务,从而不符合 Availability。因此,Raft 算法是 CP 类型的一致性算法。 )P,rabbitmq集群支持不同的策略以保证一致性或可用性。
https://www.e-learn.cn/en/share/2511682
https://www.rabbitmq.com/partitions.html
参考指南
http://www.cnblogs.com/flat_peach/archive/2013/04/07/3004008.html
http://www.rabbitmq.com/clustering.html
http://www.cnblogs.com/lion.net/p/5725474.html
https://www.rabbitmq.com/parameters.html#policies
amqp协议的简化版参考:https://www.rabbitmq.com/amqp-0-9-1-quickref.html
rabbitmq最佳实践:https://www.cloudamqp.com/blog/2017-12-29-part1-rabbitmq-best-practice.html
RabbitMQ实战指南
rabbitmq性能测试 https://www.rabbitmq.com/blog/2020/06/04/how-to-run-benchmarks/,https://www.rabbitmq.com/java-tools.html,https://github.com/rabbitmq/rabbitmq-perf-test
rabbitmq java 客户端官方手册 https://www.rabbitmq.com/api-guide.html
深入RabbitMQ 第一部分
https://zhuanlan.zhihu.com/p/103642773可以作为一个纲要。
https://www.rabbitmq.com/documentation.html
不管是否使用原生rabbitmq java client,你会发现总有些场合有些开发使用了spring amqp,所以掌握它也是必须的:https://docs.spring.io/spring-amqp/reference/html/
rabbitmq内部实现机制文档参考:
https://blog.sleeplessbeastie.eu/2020/03/30/how-to-display-rabbitmq-erlang-processes/
https://github.com/rabbitmq/internals
rabbitmq本身开发相关
https://rabbitmq.com/github.html
https://github.com/rabbitmq/rabbitmq-server/blob/main/CONTRIBUTING.md
https://www.erlang-solutions.com/blog/how-to-debug-your-rabbitmq/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!