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,它是服务端的限流机制。

  参见:https://stackoverflow.com/questions/25795695/how-to-poll-the-rabbitmq-to-get-messages-in-order-of-priority-continuously

丰富的插件体系

  不得不说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/88224974https://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.htmlhttps://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/

posted @ 2017-02-05 18:20  zhjh256  阅读(1102)  评论(0编辑  收藏  举报