RabbitMq中的消息只能存在队列中,消费者从队列中获取消息并消费。
而Kafka讲消息存在topic中,对应的队列只是topic实际存储文件中的位移标识。
多个消费者订阅同一个队列时,轮询,所以也不支持队列层面的广播(可以二次开发)
RabbitMq内真实情况:
生产者将消息发送给Exchange,由交换器将消息路由到一个或多个队列。若路由不到,则返回生产者,或直接丢弃
Rounting key需要与交换器类型和绑定键(BindingKey)联合起来使用。
通过绑定将交换器和队列关联。
生产者将消息发送给交换器时,需求一个RountingKey,当BindingKey和RountingKey相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器时,这些绑定允许使用相同的BindingKey。
BindingKey并不是所有的情况都生效,取决于交换器类型。
Connection,TCP连接
Channel,AMQP信道,每个信道都会被指派一个唯一 的ID。
引入信道的原因:
多Connection也可以用,但是在多线程情况下,建立多个Connection即建立多个TCP连接,而建立和销毁TCP连接是非常昂贵的,如遇使用高峰,性能也会有瓶颈。RabbitMq采用类似NIO的做法,选择TCP连接复用。
注:NIO,非阻塞IO,包含Channel,Buffer和Selector。NIO基于Channel和Buffer进行操作,数据总是从信道读取数据到缓冲区,或者从缓冲区读取到信道中。Selector用于监听多个信道的事件。因此,单线程可以监听多个数据的信道。
包含三层:
Channel或Connection中有个isOpen方法,但是有可能会产生竞争,所以并不可靠。
正确的是,在使用Channel的时候处于关闭状态的时候,会抛出ShutdownSignalException异常;Connection关闭的时候,会抛出SocketException或IOException异常。捕捉异常即可。
交换器:当属性值设置为true时,当所有的队列断开于交换器的绑定,那么交换器会自动删除。如果从未被绑定过,则不会被删除。
队列:当所有的相关消费者断开连接时,队列将会被删除
autodelete属性针对的是曾经有过但后来没有的事物。
设置是否是内置的。如果设置为true,表示是内置的交换器。客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。
设置是否排他。为true则设置队列是排他的,则队列仅对首次声明它的连接(Connection)可见,并在断开连接时自动删除。
同一个Connection中的Channel可以同时访问同一连接创建的排他队列。
首次是指,如果一个连接已声明一个排他队列,则其他连接不允许创建同名的排他队列。
与普通队列不同,一旦连接关闭或客户端退出,该排他队列都会被自动删除。
这种队列适用于一个客户端同时发送和读取消息的应用场景。
RabbitMq的消息存储在队列中,因此交换器的使用并不会真正消耗服务器的性能,而队列会。
如果要衡量RabbitMq的QPS只需要看队列即可。
分为两种:推(Push)模式和拉(Pull)模式。推模式采用Basic.Consume进行消费,拉模式采用Basic.Get进行消费。
推:持续订阅
RabbitMq不会为未确认(即autoack为false时,消费者还未返回ack)的消息设置过期时间,判断的唯一依据是该消息的消费者连接是否已经断开。
两个参数:
mandatory:为true时,交换器无法根据自身的类型和路由键找到符合的队列,那么RabbitMq会调用Basic.Return命令将消息返回给生产者;为false,则消息直接丢弃。
immediate(RabbitMq现已不支持该参数的维护):为true,如果交换器在将消费路由到队列时发现队列上并不存在任何消费者,那么消息不会存入该队列。所有与路由匹配的队列都没有消费者时,会通过Basic.Return返回给生产者。
两种一起使用,则以两者较小的那个数值为准。
消息在队列中的时间超过TTL,则变成“死信”,消费者无法再接收(不绝对)。
不设置TTL代表消息永不过期;设置为0,代表除非此时可以直接将消息投递给消费者,否则直接丢弃。
x-expires(单位:毫秒)参数可以控制队列被自动删除前处于未使用状态的时间。未使用的意思是队列上没有任何的消费者,也没有被重新声明,并且在过期时间段内也未调用过Basic.Get命令。
会确保在过期时间到达后将队列删除,但不保障删除的动作的及时性。在RabbitMq重启后,持久化的队列的过期时间会被重新计算。
也叫做死信交换器或死信邮箱。
当一个消息在一个队列中变成死信后,它能被重新发送到另一个交换器中,这个交换器就是DLX,绑定DLX的队列就称之为死信队列。
消息变成死信的几种情况:
延迟队列:存储的对象是对应的延迟消息。
优先级队列:默认最低为0。
事务机制和confirm机制的QPS差不多,confirm机制略胜一点点。因为confirm机制也是每发送一条消息就需要等待服务端的确认,实际上是一种串行同步等待的方式。事务机制也一样,发送消息后等待服务端确认,之后再发送消息。两者的存储确认原理相同,尤其对于持久化的消息来说,两者都需要等待消息确认落盘之后才会返回。
事务机制和confirm机制是互斥的,不能共存。
事务机制和confirm机制确保的是消息能够正确的发送到RabbitMq,这里是指发动到交换器,如果此交换器没有匹配的队列,那么消息也会丢失。
优化:批量或异步。
默认:轮询
有可能会造成整体吞吐量下降
优化:计数,每发一条消息,计数+1,消费完一条消息,计数-1。设置一个计数上限。类似于TCP/IP的滑动窗口。(对于拉模式的消费方式无效)
一般消息中间件的消息传输保障分为三个层级
RabbitMq支持”最多一次“和”最少一次“
虚拟主机(vhost):每一个RabbitMq服务器都能创建虚拟的消息服务器。每一个vhost本质上是一个独立的小型RabbitMq服务器,拥有自己独立的队列,交换器及绑定关系等,并且它拥有自己独立的权限。
RabbitMq里,权限规则是以vhost为单位的。
RabbitMq提供了三种方式来定制化服务
集群的所有节点都会备份所有的元数据信息:
如果关闭了集群中的所有节点,则需要确保在启动的时候最后关闭的那个节点是第一个启动的。若第一个启动的不是最后关闭的节点,那么这个节点会等待最后关闭的节点启动。这个等待时间是30秒,如果没有等到,那么这个先启动的节点也会失败。在最新的版本中会有重试机制,默认重试10次30秒以等待最后关闭的节点启动。
在重试失败之后,当前节点也会因失败而关闭自身的应用。
RabbitMq可通过3种方式实现分布式部署:集群(只能部署在局域网)、Federation和Shovel。3种方式不互斥,可选择一种或多种方式的组合进行部署。
Federation(AMQP协议):该插件的设计目的是使RabbitMq在不同的broker节点之间进行消息传递而无须建立集群。可以让多个交换器或者多个队列进行联邦。一个联邦交换器或一个联邦队列接收上游(指位于其他broker上的交换器或队列)的消息。联邦交换器能够将原本发送给上游交换器的消息路由到本地的某个队列中;联邦队列则允许一个本地消费者接收到来自上游队列的消息。
Shovel(也是基于AMQP协议)优势:松耦合,支持广域网,高度定制。
Federation从一个交换器中转发消息到另一个交换器,Shovel只是简单从某个broker上的队列中消费消息,然后转发消息到另一个broker上的交换器。
需要使用AMQP协议构建一个类似于TCP协议中的Ping的检测程序。
不管是持久化的消息还是非持久化的消息都可以被写入到磁盘。
持久化的消息在到达队列时就被写入到磁盘,并且如果可以,持久化的消息也会在内存中保存一份备份,当内存吃紧的时候会从内存中清除。
非持久化的消息一般只保存在内存中,在内存吃紧的时候会被换入到磁盘中。
这两种类型的消息的落盘处理都在RabbitMq的“持久层”只完成。
持久层包含两个部分:队列索引和消息存储。消息可以直接存储在队列索引中,也可以被保存在消息存储中。最佳的配备时较小的消息存在队列索引中,较大的存在消息存储中。
消息的删除只是从ETS表删除指定消息的相关信息,同时更新消息对应的存储文件的相关信息。执行消息删除操作时,并不立即对在文件中的消息进行删除,也就是说消息依然在文件中,仅仅是标识为垃圾数据而已。当一个文件中都是垃圾数据时可以将这个文件删除。也可以将逻辑上相邻的两个文件中的有效数据进行整理合并。合并的时候会锁定两个文件。
如果消息投递的目的队列是空的,并且有消费者订阅了这个队列,那么消息就会直接发送给消费者,不会经过队列这一步。消息存入队列后,不是固定不变的。
RabbitMq中的队列消息有四种状态:
对于持久化的消息,消息内容和消息索引都必须先保存在磁盘上,才会处于上述状态中的一种,而gamma状态的消息是只有持久化的消息才会有的状态。
队列具备两种模式:default和lazy。默认为default。
惰性队列:会尽可能地将消息存入磁盘中,而在消费者消费到对应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储。
当内存使用超过配置的阈值或磁盘剩余空间低于配置的阈值时,RabbitMq都会暂时阻塞客户端的连接并停止接收从客户端发来的消息,以此避免服务崩溃。与此同时,客户端与服务端的心跳检测也会失效。
流控机制是用来避免消息的发送速率过快而导致服务器难以支撑的情形。内存和磁盘告警相当于全局的流控,一旦出发会阻塞集群中所有的Connection,而流控是针对单个Connection的。
如果消费者与slave建立连接并消费,实质上会从master上获取消息。
所有对master的操作都会通过组播GM的方式同步到各个slave中。
GM模块实现的事一种可靠的组播通信协议,该协议能够保证组播消息的原子性,即保证组中活着的节点要么都收到消息要么都收不到。它的实现大概为:将所有的节点形成一个循环链表,每个节点都会监控位于自己左右两边的节点,当有节点新增时,相邻的节点保证当前广播的消息会复制到新的节点上;当有节点失效时,相邻的节点会接管以保证本次广播的消息会复制到所有节点。
RabbitMq集群节点内部通信端口默认为25672,两两节点之间都会有信息交互。如果某节点出现网络故障,或者是端口不通,则会致使与此节点的交互出现中断,这里就会有个超时判定机制,继而判定网络分区。
RabbitMq提供了三种方法自动地处理网络分区:pause-minority模式、pauser-if-all-down模式和autoheal模式。默认为ignore模式,即不自动处理网络分区,需要人工介入。
pause-minority模式:当发生网络分区时,集群中的节点在观察到某些节点“down”的时候,会自动检测其自身是否处于“少数派”,RabbitMq会自动关闭这些节点的运作。这里的关闭指的是RabbitMq应用的关闭,而Erlang虚拟机并不关闭。
pauser-if-all-down模式:集群中的节点在和所配置的列表中的任何节点不能交互时才会关闭。
autoheal模式:当认为发生网络分区时,RabbieMQ 会自动决定一个获胜 (winning)的分区,然后重启不在这个分区中的节点来从网络分区中恢复。一个获胜的分区是指客户端连接最多的分区,如果产生一个平局,即有两个或者多个分区的客户端连接数一样多,那么节点数最多的一个分区就是获胜分区。如果此时节点数也一样多,将以节点名称的宇典序来挑选获胜分区。
优化网络配置的一个重要目标就是提高吞吐量,比如禁用Nagle算法(主要用于减少延迟)、增大TCP缓冲区的大小。每个TCP连接都分配了缓冲区。一般来说,缓冲区越大,吞吐量也会越高,但是每个连接上耗费的内存也就越多,从而使总体服务的内存增大,这是一个权衡的问题。