RabbitMQ 深入浅出

RabbitMQ特性

  • 可靠性:使用一些机制来保证可靠性,如持久化、传输确认、发布确认、事务等
  • 灵活的路由:在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ提供了内置的交换器,针对复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制实现自己的交换器
  • 扩展性:多个RabbitMQ节点组成一个集群,可以动态扩展节点数量
  • 多种协议:除了支持原生的AMQP协议,还支持STOMP、MQTT协议
  • 管理界面:提供了一个易用的界面,监控和管理消息、集群
  • 插件机制:内置多种插件,并可以自行编写插件

基础概念

在这里插入图片描述

生产消息

消息一般包括两个部分:消息体(payload)和标签(label),标签用来表述这条消息,比如:交换器名称,路由键,可以根据标签把消息发给感兴趣的消费者;

消息发送到交换器(exchange),有交换器将消息路由到一个或多个队列,如果路由不到,会返回给生产者或者直接丢弃。

消费消息

消费者订阅的是队列,消费者消费消息只是消费payload,在消息路由的过程中,标签会丢失,存入到队列的只是消息体;

rabbitmq不支持在队列层面广播消息,如果多个消费者订阅一个队列,会执行轮训的策略,保证只有一个消费者消费消息

rabbitmq同时支持推和拉两种消息消费模式:Basic.Consume推,Basic.Get拉。

当autoAck参数置为false,对于RabbitMQ服务端而言 ,队列中的消息分成了两个部分 : 一部分是等待投递给消费者的消息:一部分是已经投递给消费者,但是还没有收到消费者确认信号的消息。如果RabbitMQ一直没有收到消费者的确认信号,并且消费此消息的消费者已经断开连接,则RabbitMQ会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可能还是原来的那个消费者。

消息者消费消息时,可以把消费和处理放在两个线程,提高消费吞吐率

如果只想从队列获得单条消息而不是持续订阅,建议还是使用Basic.Get进行消费.但是不能将Basic.Get放在一个循环里来代替Basic.Consume,这样做会严重影响RabbitMQ的性能.如果要实现高吞吐量,消费者理应使用Basic.Consume方法。

broker

即一个服务器节点

web管理地址:http://localhost:15672/

队列

每条消息只会被消费一次,rabbit的数据物理存储都在队列里面,交换器的使用并不真正耗费服务器的性能,而队列会。

按照RabbitMQ官方建议,生产者和消费者都应该尝试创建(这里指声明操作)队列。这是一个很好的建议,但不适用于所有的情况。如果业务本身在架构设计之初已经充分地预估了队列的使用情况,完全可以在业务程序上线之前在服务器上创建好(比如通过页面管理、 RabbitMQ命令或者更好的是从配置中心下发),这样业务程序也可以免去声明的过程,直接使用即可。

发布订阅

每条消息会发送给所有的订阅者,但是同一个组的订阅者只有一个会收到消息

消息分区

保证特征ID的数据只会被指定的消费者消费

路由键

发送消息时指定交换器到队列的路由关系,生产者在发送消息一般会指定一个路由键

绑定建

配置交换器到队列的路由关系,一个交换器和一个队列之间可以配置多个绑定建;一个exchange可以同时绑定多个相同的绑定建;

绑定建不一定会起作用,一般和exchange类型有关系,比如fanout类型exchange会把消息传给所有和他关联的队列

交换器

对应Kafka中的Topic,数据是发送到exchange,然后指定路由键

交换器类型
  1. fanout:把消息发送到所有和交换器绑定的队列
  2. direct:路由键和绑定建相同时,路由到队列
  3. topic:模糊匹配,绑定键里面用#代表一个字符,*代表多个字符
  4. headers:需要head全部匹配绑定建才能路由成功,性能差不推荐

Connection

每个connection对应一个tcp长连接

Channel

是一个虚拟的Connection,channel公用一个Connection可以复用一个tcp链接,避免一个进程创建多个tcp链接,Channel不能在多个线程中共享

每个 Channel 都拥有自己独立的线程。最常用的做法是一个 Channel 对应一个消费者, 也就是意味着消费者彼此之间没有任何关联。当然也可以在一个 Channel 中维持多个消费者, 但是要注意一个问题,如果 Channel
中的一个消费者一直在运行,那么其他消费者的 callback 会被"耽搁" 。

当每个信道的流量不是很大时,复用单一的Connection 可以在产生性能瓶颈的情况下有效地节省TCP连接资源。但是当信道本身的流量很大时,这时候多个信道复用一个 Connection 就会产生性能瓶颈,进而使整体的流量被限制了。 此时就需要开辟多个 Connection,将这些信道均摊到这些 Connection 中

客户端

消费者

rabbit支持push和pull两种模式,其中Basic.consumer是push模式,Basic.get是pull模式

消费确认机制

队列分为两部分,一部分为等待发出的消息,一部分是已经发出去但是还没得到消费者ack确认的消息。如果客户端一直没有ack确认,又断开了链接,rabbit会把消息重新进入队列,等待投递给下一个消费者

Rabbit进阶

消息发送设置

  • mandatory:当消息没有路由到任意一个队列时,则消息返回生产者(但同时设置了备份交换器时,会发送到备份交换器,而不会返回生产者)
  • immediate:当消息路由的队列没有一个消息者,则消息返回生产者

RabbitMQ 3.0版本开始去掉了对 immediate 参数的支持,对此 RabbitMQ 官方解释是: immediate 参数会影响镜像队列的性能 ,增加了代码复杂性,建议采用 TTL 和 DLX 的方法替代

下面是配置一个监听消息返回生产者的监听器

channel.basicPublish("exchange_demo", "routingkey_demo", true, true, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body);
                System.out.println(msg);
            }
        });

备份交换器

当发送的消息没有路由到任何一个队列,则发送到备份交换器(备胎交换器),名字:alternate-exchange

如果路由键在备份交换器上也没有匹配到队列,则消息将丢失,所以建议备份交换器用fanout类型;另外如果备份交换器没有不存在消息也会丢失

Map<String, Object> args = new HashMap<String, Object>(); 
args.put("alternate-exchange", "myAge");
channe1.exchangeDeclare("normalExchange" , "direct" , true , false , args);

如果同时设置备份交换器和mandatory,则备份交换器生效,如果不想设置监听器则设置备份交换器是种选择

消息过期时间(ttl)

  1. 可以设置队列级别的过期时间
  2. 也可以对设置单条消息的过期时间
  3. 当同时存在时,取最小值
  4. 如果不设置ttl消息不会过期
  5. 如果ttl为零,表示除非可以直接投递给消费者,否则立即丢弃
//队列级别
Map<String, Object> args = new HashMap<String, Object>(); 
argss.put("x-message-ttl" , 6000);
channel.queueDeclare(queueName, durable, exclusive, autoDelete, args);

//消息级别
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); 
builder.deliveryMode(2);//持久化消息
builder.expiration("60000");//设置 TTL=60000ms
AMQP.BasicProperties properties = builder.build() ; 
channel.basicPublish(exchangeName, routingKey, mandatory, properties, "ttl Test Message".getBytes());

队列级别的消息过期会马上删除,而消息级别的过期不会马上删除,只有等消费时才会删除,因为全局扫描性能消耗大,而队列级别只会扫描队列头部

死信队列

DLX全程dead-letter-exchange,死信交换器,和死信交换器绑定就是死信队列

变为死信的几种情况
  1. 消息被拒绝(Basic.Reject/Basic.Nack并且requeue参数为false)
  2. 消息过期
  3. 队列长度达到最大
channel.exchangeDeclare("dlx_exchange" , "direct");
Map<String, Object> args = new HashMap<String, Object>(); 
args.put("x-dead-letter-exchange", "dlx_exchange");
args.put("x-dead-letter-routing-key" , "dlx-routing-key");
//为队列myqueue添加DLX
channel.queueDeclare("myqueue", false, false, false, args);

延迟队列

rabbitmq没有直接提供延迟队列,但是可以通过私信队列(dlx)和消息过期时间(ttl)模拟出延迟队列

优先级队列

优先级高的消息拥有被优先被消费的权利

当生产者生产速度小于消费者时,优先级没有意义,因为队列始终最多只有一条消息,对于单条消息优先级没有意义

//设置队列的最大优先级
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-max-priority", 10);
channel.queueDeclare("queue.priority", true, false, false, args) ;

//设置消息的优先级
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); 
builder.priority(5);
AMQP.BasicProperties properties = builder.build();
channel.basicPublish("exchange_priority","rk_priority",properties, "messages".getBytes());

RPC

RPC的处理流程:
  1. 当客户端启动时,创建一个匿名的回调队列。
  2. 客户端为RPC请求设置2个属性:replyTo,设置回调队列名字;correlationId,标记request。
  3. 请求被发送到rpc_queue队列中。
  4. RPC服务器端监听rpc_queue队列中的请求,当请求到来时,服务器端会处理并且把带有结果的消息发送给客户端。接收的队列就是replyTo设定的回调队列。
  5. 客户端监听回调队列,当有消息时,检查correlationId属性,如果与request中匹配,那就是结果了。
/**
 * rpc客户端
 */
public class RPCClient {

    private Connection connection;
    private Channel channel;

    /**
     * 回复队列
     */
    private String replyQueueName;

    public RPCClient() throws IOException, TimeoutException {
        channel = RabbitConfig.createChannel();

        //监听回复队列
        replyQueueName = channel.queueDeclare().getQueue();
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String response = new String(body);
                System.out.println(properties.getCorrelationId() + " [.] Got '" + response + "'");
                System.exit(1);
            }
        };
        channel.basicConsume(replyQueueName, true, consumer);
    }

    /**
     * 发送消息
     *
     * @param number
     */
    public void call(int number) throws IOException,
            ShutdownSignalException, ConsumerCancelledException {
        String corrId = UUID.randomUUID().toString();

        AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
                .correlationId(corrId)
                .replyTo(replyQueueName)
                .build();

        System.out.println(" [x] Requesting n*n(" + number + ")");
        channel.basicPublish("", RPCServer.RPC_QUEUE_NAME, props, String.valueOf(number).getBytes());
    }

    public void close() throws Exception {
        connection.close();
    }

    public static void main(String args[]) throws Exception {
        RPCClient fibRpc = new RPCClient();
        fibRpc.call(new Random().nextInt(10));
    }
}
/**
 * rpc服务端
 */
public class RPCServer {
    public static final String RPC_QUEUE_NAME = "request_queue";

    public static void main(String args[]) throws Exception {
        Channel channel = RabbitConfig.createChannel();

        channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
        channel.basicQos(1);

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                AMQP.BasicProperties replyProps = new AMQP.BasicProperties.Builder()
                        .correlationId(properties.getCorrelationId())
                        .build();
                String message = new String(body);
                int n = Integer.parseInt(message);
                String response = String.valueOf(n * n);
                System.out.println(" [.] n*n(" + message + ")=" + response);
                channel.basicPublish("", properties.getReplyTo(), replyProps, response.getBytes());
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
        System.out.println(" [x] Awaiting RPC requests");
    }
}

持久化

RabbitMQ 的持久化分为三个部分:交换器的持久化、队列的持久化和消息的持久化 。

交换器持久化

表示这个交换器元信息是否持久化,在创建交换器时指定durable=true,如果为false,重启服务器这个交换器就丢失了

队列持久化

表示这个队列元信息是否持久化,在创建队列时指定durable=true,如果为false,重启服务器这个队列就丢失了,而且里面的消息也丢失了

消息持久化

队列的持久化并不代表消息就会持久化,消息的持久化对应BasicProperties.deliveryMode=2,MessageProperties.PERSISTENT_TEXT_PLAIN就是持久化的

镜像队列

当服务器接收消息后,并不会马上持久化到磁盘,会等待fsync调用内核,这期间丢失数据也会让数据丢失,可以设置镜像队列减少这种丢数据可能性

另外还可以在发送端引入事务机制或者发送方确认机制来保证消息已经正确地发送并存储至 RabbitMQ 中

生产者确认

在使用 RabbitMQ
的时候,可以通过消息持久化操作来解决因为服务器的异常崩溃而导致的消息丢失,除此之外,我们还会遇到一个问题,当消息的生产者将消息发送出去之后,消息到底有没有正确地到达服务器呢?如果不进行特殊配置,默认情况下发送消息的操作是不会返回任何信息给生产者的,也就是默认情况下生产者是不知道消息有没有正确地到达服务器。如果在消息到达服务器之前己经丢失,持久化操作也解决不了这个问题,因为消息根本没有到达服务器
,何谈持久化?

RabbitMQ 针对这个问题,提供了两种解决方式:

事务方式
try {
    channel.txSelect();
    channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
    int result = 1 / 0;
    channel.txCommit();
} catch (Exception e) {
    e.printStackTrace();
    channel.txRollback();
};

事务确实能够解决消息发送方和 RabbitMQ 之间消息确认的问题,只有消息成功被 RabbitMQ 接收,事务才能提交成功,否则便可在捕获异常之后进行事务回滚,与此同时可以进行消息重发。但是使用事务机制会"吸干" RabbitMQ 的性能,那么有没有更好的方法既能保证消息发送方确认消息已经正确送达,又能基本上不带来性能上的损失呢?从 AMQP 协议层面来看并没有更好的办法,但是 RabbitMQ 提供了一个改进方案 ,即发送方确认机制,详情请看下一节的介绍。

确认方式

事务机制在一条消息发送之后会使发送端阻塞,以等待 RabbitMQ
的回应,之后才能继续发送下一条消息。相比之下,发送方确认机制最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用程序便可以通过回调方法来处理该确认消息,如果
RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack(Basic.Nack) 命令,生产者应用程序同样可以在回调方法中处理该 nack命令。

//
channel.confirmSelect();
for(int i=0;i<batchCount;i++){
	channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
}
if(!channel.waitForConfirms()){
	System.out.println("send message failed.");
}

批量 confirm 和异步 confirm 这两种方式所呈现的性能要比其余两种好得多。事务机制和普通 confirm 的方式吐吞量很低,但是编程方式简单,不需要在客户端维护状态(这里指的是维护 deliveryTag 及缓存未确认的消息)。批量 confirm 方式的问题在于遇到 RabbitMQ 服务端返回 Basic.Nack 需要重发批量消息而导致的性能降低。异步 confirm 方式编程模型最为复杂,而且和批量 confirm 方式一样需要在客户端维护状态。在实际生产环境中强烈建议读者使用异步 confirm 的方式 。

消费者要点

消息分发

当 RabbitMQ 队列拥有多个消费者时 ,队列收到的消息将以轮询(round-robin)的分发方式发送给消费者,RabbitMQ不管消费者是否消费并已经确认(Basic.Ack)了消息,很多时候轮询的分发机制也不是那么优雅。那么该如何处理这种情况呢?这里就要用到 channel.basicQos()

举例说明,在订阅消费队列之前,消费端程序调用了 channel.basicQos(5) ,之后订阅了某个队列进行消费。 RabbitMQ会保存一个消费者的列表,每发送一条消息都会为对应的消费者计数,如果达到了所设定的上限,那么 RabbitMQ 就不会向这个消费者再发送任何消息。 直到消费者确认了某条消息之后 ,RabbitMQ将相应的计数减1,之后消费者可以继续接收消息,直到再次到达计数上限。这种机制可以类比于TCP/IP中的"滑动窗口"。

channel.basicQos(1); //设置消费者所能保留的最大未确认消息条数,Basic.Qos 的使用对于拉模式的消费方式无效.
channel.basicQos(fectchCount, global); //global=false表示对此信道上新加入消费者加以限制,global=true表示对此信道上所有的消息者加以限制
消息顺序性

事务回滚、消息确认拒绝、消息被拒绝并重入队列、延迟时间不同、消息优先级不同等都可能使消息乱序

弃用QueueingConsumer

QueueingConsumer采用LinkedBlockingQueue存储消息数据,当消费者消费能力更不上接收速度可能OOM,当然可以使用channel.basicQos(1)避免

为了避免不必要的麻烦,建议在消费的时候尽量使用继承 DefaultConsumer 的方式

消息传输保障

一般消息的消息传输保障支持:最少一次、最多一次、恰好一次,并且没有mq可以支持恰好一次,恰好一次需要使用最少一次,然后客户端做幂等处理(一般使用全局唯一ID)

  • rabbitmq只支持最少一次和最多一次,不支持恰好一次
  • 最少一次需要生产者开启事务或者消息确认机制,并且交换器队列消息设为持久化,绑定正确,autoAck=false;
  • 最多一次就是不做上面的所有准备

Rabbit高阶

储存机制

持久层是一个逻辑上的概念,实际包含两个部分: 队列索引(rabbit_queue_index)和消息存储(rabbit_msg_store)。rabbit_queue_index负责维护队列中落盘消息的信息,包括消息的存储地点、是否己被交付给消费者、是否己被消费者ack等。每个队列都有与之对应的一个rabbit_queue_index。rabbit_msg_store 以键值对的形式存储消息,它被所有队列共享,在每个节点中有且只有一个

经过rabbit_msg_store处理的所有消息都会以追加的方式写入到文件中,当一个文件的大小超过指定的限制 (file_size_limit) 后,关闭这个文件再创建一个新的文件以供新的消息写入。文件名(文件后缀是".rdq") 从0开始进行累加,因此文件名最小的文件也是最老的文件。在进行消息的存储时, RabbitMQ会在ETS(Erlang Term Storage) 表中记录消息在文件中的位置映射(Index)和文件的相关信息(FileSummary)。

消息的删除只是从ETS表删除指定消息的相关信息,同时更新消息对应的存储文件的相关信息。执行消息删除操作时,井不立即对在文件中的消息进行删除,也就是说消息依然在文件中,仅仅是标记为垃圾数据而己。当一个文件中都是垃圾数据时可以将这个文件删除。当检测到前后两个文件中的有效数据可以合并在一个文件中,井且所有的垃圾数据的大小和所有文件(至少有3个文件存在的情况下)的数据大小的比值超过设置的阈值GARBAGE FRACTION(默认值为0.5)时才会触发垃圾回收将两个文件合井。执行合井的两个文件一定是逻辑上相邻的两个文件。

队列的结构

通常队列由rabbit_amqqueue_process和backing_queue这两部分组成,rabbit_amqqueue_process负责协议相关的消息处理,即接收生产者发布的消息、向消费 者交付消息、处理消息的确认(包括生产端的confirm和消费端的ack)等。backing_queue是消息存储的具体形式和引擎,并向rabbit_amqqueue_process提供相关的接口以供调用

  • alpha: 消息内容(包括消息体、属性和headers)和消息索引都存储在内存中。
  • beta: 消息内容保存在磁盘中,消息索引保存在内存中。
  • gamma: 消息内容保存在磁盘中,消息索引在磁盘和内存中都有 。
  • delta: 消息内容和索引都在磁盘中。
    在这里插入图片描述
    一般情况下,消息按照Ql→Q2→Delta→Q3→Q4这样的顺序步骤进行流动,但并不是每一条消息都一定会经历所有的状态,这个取决于当前系统的负载状况。从Q1至Q4基本经历内存到磁盘,再由磁盘到内存这样的一个过程,如此可以在队列负载很高的情况下,能够通过将一部分消息由磁盘保存来节省内存空间,而在负载降低的时候,这部分消息又渐渐回到内存被消费者获取,使得整个队列具有很好的弹性。

通常在负载正常时,如果消息被消费的速度不小于接收新消息的速度,对于不需要保证可靠不丢失的消息来说,极有可能只会处于alpha状态。对于durable属性设置为true的消息,它一定会进入gamma状态,并且在开启publisher confirm机制时,只有到了gamma状态时才会确认该消息己被接收,若消息消费速度足够快、内存也充足,这些消息也不会继续走到下一个状态。

在系统负载较高时,已接收到的消息若不能很快被消费掉,这些消息就会进入到很深的队列中去,这样会增加处理每个消息的平均开销。因为要花更多的时间和资源处理"堆积"的消息,如此用来处理新流入的消息的能力就会降低,使得后流入的消息又被积压到很深的队列中 继续增大处理每个消息的平均开销,继而情况变得越来越恶化,使得系统的处理能力大大降低。

应对这一问题一般有3种措施:

  1. 增加prefetch_count的值,即一次发送多条消息给消费者,加快消息被消费的速度,详细用法可以参考4.9.1节;
  2. 采用multiple ack,降低处理ack带来的开销,详细用法可以参考3.5节:
  3. 流量控制,详细内容可以参考9.3节。

惰性队列

RabbitMQ从3.6.0版本开始引入了惰性队列(Lazy Queue)的概念。惰性队列会尽可能地将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储。

当RabbitMQ需要释放内存的时候,会将内存中的消息换页至磁盘中,这个操作会耗费较长的时间,也会阻塞队列的操作,进而无法接收新的消息。虽然RabbitMQ的开发者们一直在升级相关的算法,但是效果始终不太理想,尤其是在消息量特别大的时候。

惰性队列会将接收到的消息直接存入文件系统中,而不管是持久化的或者是非持久化的,这样可以减少了内存的消耗,

队列具备两种模式: default和lazy。默认的为default模式,在3.6.0之前的版本无须做任何变更。lazy模式即为惰性队列的模式

Map<String, Object> args = new HashMap<String, Object>(); 
args.put("x-queue-mode", "lazy");
channel.queueDeclare("myqueue", false, false, false, args);

如果要将普通队列转变为惰性队列,那么我们需要忍受同样的性能损耗,首先需要将缓存中的消息换页至磁盘中,然后才能接收新的消息。反之,当将一个惰性队列转变为普通队列的时候,和恢复一个队列执行同样的操作,会将磁盘中的消息批量地导入到内存中。

内存及磁盘告警

RabbitMQ 可以对内存和磁盘使用量设置阔值,当达到阑值后,生产者将被阻塞(block),直到对应项恢复正常

当内存使用超过配置的阑值或者磁盘剩余空间低于配置的阑值时,RabbitMQ都会暂时阻塞(block)客户端的连接(Connection)井停止接收从客户端发来的消息,以此避免服务崩溃。与此同时,客户端与服务端的心跳检测也会失效。
在这里插入图片描述

被阻塞的Connection的状态要么是blocking,要么是blocked。前者对应于并不试图发送消息的Connection,比如消费者关联的Connection,这种状态下的Connection可以继续运行。而后者对应于一直有消息发送的Connection,这种状态下的Connection会被停止发送消息。 注意在一个集群中,如果一个Broker节点的内存或者磁盘受限,都会引起整个集群中所有的Connection被阻塞。

因此建议生产和消费的逻辑可以分摊到独立的Connection之上而不发生任何交集,客户端程序可以通过添加BlockedListener来监昕相应连接的阻塞信息

流控

流控机制是用来避免消息的发送速率过快而导致服务器难以支撑的情形。内存和磁盘告警相当于全局的流控(Global Flow Control),一旦触发会阻塞集群中所有的Connection,而本节的流控是针对单个Connection的,可以称之为Per-Connection Flow Control或者Internal Flow Control

镜像队列

如果RabbitMQ集群是由多个Broker节点组成的,那么从服务的整体可用性上来讲,该集群对于单点故障是有弹性的,但是同时也需要注意:尽管交换器和绑定关系能够在单点故障问题上幸免于难,但是队列和其上的存储的消息却不行,这是因为队列进程及其内容仅仅维持在单个节点之上,所以一个节点的失效表现为其对应的队列不可用。

在通常的用法中,针对每一个配置镜像的队列(以下简称镜像队列〉都包含一个主节点(master)和若干个从节点(slave)

slave会准确地按照master执行命令的顺序进行动作,故slave与master上维护的状态应该是相同的。如果master由于某种原因失效,那么"资历最老"的slave会被提升为新的master。根据slave加入的时间排序,时间最长的slave即为"资历最老"。发送到镜像队列的所有消息会被同时发往master和所有的slave上,如果此时master挂掉了,消息还会在slave上,这样slave提升为master的时候消息也不会丢失。除发送消息(Basic.Publish)外的所有动作都只会向master发送,然后再由master将命令执行的结果广播给各个slave。

在这里插入图片描述

RabbitMQ的镜像队列同时支持publisher confirm和事务两种机制。在事务机制中,只有当前事务在全部镜像中执行之后,客户端才会收到Tx.Commit-Ok的消息。同样的,在publisher confirm机制中,生产者进行当前消息确认的前提是该消息被全部进行所接收了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值