rabbitmq简介

基础简介

基本概念

AMQP

AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

ConnectionFactory、Connection、Channel

Connection是RabbitMQ的socket链接(TCP连接),它封装了socket协议相关部分逻辑。ConnectionFactory为Connection的制造工厂。

Channel是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。

一个Connection支持建立多个Channel,而我们的指令,就是通过channel来告知到RabbitMQServer的。一般情况下,程序起始,第一步就是建立Connection,第二步就是建立Channel(当然关闭的时候,需要先关闭channel,再关闭Connection)。

Vhost

虚拟主机。一个RabbitMQ物理服务器上可以支持有多个vhost。不同的vhost可以认为都是一个RabbitMQ Server。逻辑上隔离不同的实例,包含自己的exchange、queue、binding,可以避免exchange和queue的命名冲突,权限控制也是以vhost为单位,可以给用户指定一个或多个vhost。

Queue

Queue(队列)是RabbitMQ的内部对象,用于存储消息,用下图表示。
RabbitMQ基础概念详细介绍

RabbitMQ中的消息都只能存储在Queue中,生产者(下图中的P)生产消息并最终投递到Queue中,消费者(下图中的C)可以从Queue中获取消息并消费。

RabbitMQ基础概念详细介绍

多个消费者可以订阅同一个Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。

RabbitMQ基础概念详细介绍

Message acknowledgment(ACK)

 在实际应用中,可能会发生消费者收到Queue中的消息,但没有处理完成就宕机(或出现其他意外)的情况,这种情况下就可能会导致消息丢失。为了避免这种情况发生,我们可以要求消费者在消费完消息后发送一个回执给RabbitMQ,RabbitMQ收到消息回执(Message acknowledgment)后才将该消息从Queue中移除;如果RabbitMQ没有收到回执并检测到消费者的RabbitMQ连接断开,则RabbitMQ会将该消息发送给其他消费者(如果存在多个消费者)进行处理。

Message durability

如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。

消息持久化必须满足三个条件:

  1. exchange声明为持久化(durable=true)
  2. 队列声明为持久化(durable=true)
  3. 消息投递模式声明为持久化(delivery_mode=2)

如果只是设置了exchange持久化和queue持久化,中间传递的消息会丢失。

启用持久化会损失性能,吞吐量甚至可能降低10倍

RabbitMQ集群中的队列并没有备份,而是均匀分布在各个节点,如果队列所在节点挂掉,在节点恢复重建队列前消息会丢失

如果在持久化到磁盘前崩溃,消息仍然会丢失。

Prefetch count

前面我们讲到如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。

RabbitMQ基础概念详细介绍

Exchange

由Exchange将消息路由到一个或多个Queue中(或者丢弃)。

生产者将消息发送到Exchange(交换器,下图中的X),由Exchange将消息路由到一个或多个Queue中(或者丢弃)。

 RabbitMQ基础概念详细介绍

routing key

生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能最终生效。
在Exchange Type与binding key固定的情况下(在正常使用时一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定routing key来决定消息流向哪里。
RabbitMQ为routing key设定的长度限制为255 bytes。 

Binding

RabbitMQ中通过Binding将Exchange与Queue关联起来,这样RabbitMQ就知道如何正确地将消息路由到指定的Queue了。

在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;消费者将消息发送给Exchange时,一般会指定一个routing key;当binding key与routing key相匹配时,消息将会被路由到对应的Queue中。

  • 在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的binding key。
  • binding key 并不是在所有情况下都生效,它依赖于Exchange Type,比如fanout类型的Exchange就会无视binding key,而是将消息路由到所有绑定到该Exchange的Queue。

Exchange type

RabbitMQ常用的Exchange Type有fanout、direct、topic、headers这四种。

direct

direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。

RabbitMQ基础概念详细介绍

以上图的配置为例,我们以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…);如果我们以routingKey=”info”或routingKey=”warning”来发送消息,则消息只会路由到Queue2。如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。

fanout

fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。
RabbitMQ基础概念详细介绍

上图中,生产者(P)发送到Exchange(X)的所有消息都会路由到图中的两个Queue,并最终被两个消费者(C1与C2)消费。

topic

前面讲到direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:

  • routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
  • binding key与routing key一样也是句点号“. ”分隔的字符串
  • binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)

RabbitMQ基础概念详细介绍

以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。

headers

headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。

headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。
在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。

 

RPC

MQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败。但实际的应用场景中,我们很可能需要一些同步处理,需要同步等待服务端将我的消息处理完成后再进行下一步处理。这相当于RPC(Remote Procedure Call,远程过程调用)。在RabbitMQ中也支持RPC。

RabbitMQ基础概念详细介绍
RabbitMQ中实现RPC的机制是:

  • 客户端发送请求(消息)时,在消息的属性(MessageProperties,在AMQP协议中定义了14中properties,这些属性会随着消息一起发送)中设置两个值replyTo(一个Queue名称,用于告诉服务器处理完成后将通知我的消息发送到这个Queue中)和correlationId(此次请求的标识号,服务器处理完成后需要将此属性返还,客户端将根据这个id了解哪条请求被成功执行了或执行失败)
  • 服务器端收到消息并处理
  • 服务器端处理完消息后,将生成一条应答消息到replyTo指定的Queue,同时带上correlationId属性
  • 客户端之前已订阅replyTo指定的Queue,从中收到服务器的应答消息后,根据其中的correlationId属性分析哪条请求被执行了,根据执行结果进行后续业务处理

推拉模式

对于MQ来说,有两种经典的消费模式:推、拉。部分MQ只支持推模式或者拉模式。而RabbitMQ同时支持两种模式。

推和拉两种模式都是针对消费者而言的。

拉消息模式

拉模式的话,就是消费者根据自己的意愿,主动从MQ中获取消息。MQ将不会主动将消息告知到消费者。

核心方法:GetResponse basicGet(String queue, Boolean autoACK);

示例代码如下:

public class ReceiverByGet {
    public final static String QUEUE_NAME = "hello";
    public static void main(String[] args) {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
 
 
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
 
 
        while(true) {
            // 这里才是重点
            GetResponse response = channel.basicGet(QUEUE_NAME, true);
            if (response = null) {
                System.out.println("Get Nothing");
                TimeUnit.MILLISECONDS.sleep(1000);
            } else {
                String message = new String(response.getBody(), "UTF-8");
                System.out.println(message);
                TimeUnit.MILLISECONDS.sleep(500);
            }
        }
    }
}

推模式

顾名思义,推模式情况下, MQ主导什么情况下向消费者发送消息,以及每次发送多少消息。 而由于是MQ进行主导,在没有任何控制的情况下,很有可能会对消费者产生压力。可以通过设置prefatchCount(默认为1)

显而易见,使用推模式的话,必须谨慎的设置prefatchCount字段,不然要么有可能会导致消费者消费能力不足导致被压垮,要么因为prefatchCount字段过小导致系统处理消息的能力受限。

但是,使用拉模式的话,消费者可以根据自己的能力来决定消费速率,但是在消费迟缓的时候,很容易造成消息的堆积。并且,有可能会导致消息从生产到消费的时间过长,在处理存在有效期的消息时,会造成一定的影响。

消息的确认机制

生产侧的消息确认

消息从生产者产生,交给RabbitMQ,但是由于网络的不确定性,消息是有可能交付失败的,所以RabbitMQ提供了两种模式来保证生产者侧的消息可靠性投递。

事务模式

这里的事务有三个核心方法:

channel.txSelect(); // 开启事务

channel.txCommint(); // 事务提交

channel.txRollback(); //事务回滚
//示例
try {
    channel.txSelect();
    channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
    channel.txCommint();
} catch(Exception ex) {
    ex.printStackTrace();
    channel.txRollback();
}

 生产者confirm模式

 

生产者将信道设置成confirm模式,一旦信道设置成confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了。如果消息和队列是持久化的,那么确认消息会在消息写入磁盘后再发出。

broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号。此外,broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。

confirm模式最大的好处在于它可以是异步的,一旦发布一条消息,生产者的应用程序就可以在等信道确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息。

同时,confirm模式和事务模式不允许同时存在。

confirm方式又分为三种:单条confirm、批量confirm、异步confirm

单条confirm

又称普通confirm模式。每发送一条消息,调用waitForConfirms()方法,等待MQ服务器端confirm。这实际上算是一种串行confirm了。

如果服务器端返回false或者超时未返回,生产者会对这条消息进行重传。

代码示例

channel.basicPublish(ConfirmConfig.exchangeName, confirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
if (!channel.waitForConfirms()) {
    System.out.println("send message failed");
}

批量confirm

每发送一批消息后,调用waitForConfirms()方法,等待服务器端confirm。

批量confirm比单条confirm稍微复杂一些。生产者程序需要定期(每隔多少秒)或者定量(达到多少条)或者两者结合起来publish消息,然后等待服务器端进行confirm。相比于单条confirm模式,批量confirm极大地提升了confirm效率。但是问题在于,一旦出现confirm返回false或者超时的情况,生产者需要将这一批次的消息全部重发,这会明显会带来消息重复。并且,在confirm消息经常丢失的情况下,批量confirm的性能应该是不升反降的。

channel.confrimSelect();
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了一条或者多条消息后client端会回调这个方法。

异步confirm模式的编程实现最为复杂。Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Channel发出的消息序号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publish一条消息,集合中元素加1;每回调一次handleAck方法,unconfirm集合删掉相应的一条(multiple = false)或多条(multiple = true)记录。从程序运行效率上看,这个unconfirm集合最好采用有序集合SortedSet存储结构。实际上,SDK中的waitForConfirms()方法也是通过SortedSet维护消息序号的。

关键代码:

SortedSet<Long> confirmSet = Collections.SynchronizedSortedSet(new TreeSet<Long>());
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener(){
    public void handleAck(long deliveryTag, boolean multiple) throws IOException {
        if (multiple) {
            confirm.headSet(deliveryTag + 1).clear();
        } else {
            confirmSet.remove(deliveryTag);
        }
    }
    public void handleNack(long deliveryTag, boolean multiple) throws IOException {
        System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple);
        if (multiple) {
            confirmSet.headSet(deliveryTag + 1).clear();
        } else {
            confirmSet.remove(deliveryTag);
        }
    }
});
while(true) {
    long nextSeqNo = channel.getNextPublishSeqNo();
    channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
    confirmSet.add(nextSeqNo);
}

事务模式 < 单条confirm << 批量confirm < 异步confirm

使用事务模式,性能是最差的。单条confirm模式性能会比事务稍微好一些。但是和批量confirm模式还有异步confirm模式相比,就差的远了。批量confirm模式的问题在于confirm频繁返回false进行大量消息重发时会使性能降低,异步confirm模式(async)编程模型较为复杂。

消费者confirm模式

为了保证消息成功到达消费者进行消费,RabbitMQ同样对消费侧存在消息的确认机制。

消费者在声明队列时,可以指定noAck参数,当noAck = false时,RabbitMQ会等待消费者显式发回ack信号才会从内存(和磁盘,如果是持久化消息的话)中移除消息。否则,RabbitMQ会在队列中消息被消费后立即进行删除。

  • AcknowledgeMode.NONE:无需确认。在消息发送到消费者时,就进行ack,也就认为消费成功了。这种情况下,只有负责队列的进程出现了异常,才会进行nack,其他情况都是ack。
  • AcknowledgeMode.AUTO:根据情况ack。如果消费过程中没有抛出异常,就认为消费成功了,也就进行ack。这里有几个异常会存在特殊处理:AmqpRejectAndDontRequeueException,会认为消息失败,拒绝消息,并且requeue = false(也就是不再重新投递);ImmediateAcknowledgeAmqpException,会进行ack;其他的异常均进行nack,同时requeue = true(也就是进行重新投递)。
  • AcknowledgeMode.MANUAL:手动ack。上面两种都是不需要关心ack方法调用的,但是使用手动ack时,必须要手动调用ack方法。不过,手动ack是支持批量处理的(可以设置是否进行批量ack),这样可以减小IO消耗。不过它有着与批量confirm相同的问题。

消息持久化

持久化的作用就是,把指定的内容保存到磁盘上,这样即使在MQ服务器崩溃的时候,重启之后也能重新从磁盘中读取出来,恢复之前的内容。

  1. exchange、queue、消息这三部分的持久化没什么关联,谁声明为持久化,服务器重启之后,就会对谁进行重新构建。不过,如果想恢复整个的流程,最好对这三者都进行持久化(当然,持久化是会效率有损耗的)。
  2. Queue和exchange都可以在创建的时候直接声明为持久化的(declare方法中的一个参数)。而消息是在发送的时候设置为持久化的(basicPublish的一个参数)。
  3. 不需要为Binding持久化,只要对exchange和queue设置为持久化的,在恢复的时候,binding会自动恢复(其实可以理解为binding本身只是一个虚拟概念,实际是依仗于exchange的模式以及queue的routing key设置才建立的)。
  4. RabbitMQ不会允许一个非持久化的exchange和一个持久化的queue进行绑定,反之亦然。想要成功绑定,exchange和queue必须都是持久化的,或者都是非持久化的。
  5. 一旦创建了exchange或者queue之后,是不允许进行修改的,要想修改只能删除重建。所以,要将一个非持久化的队列变成持久化的,唯一的办法只能删除这个队列之后重新建立。
  6. 在不设置持久化的情况下,消息是会放在内存中的,不会落到磁盘上(所以重启时会丢消息)。
  7. 如果生产者这一侧开启了事务模式,消息是一定会被持久化的。只有在消息刷到磁盘上,才会告知成功。

持久化的问题

  1. 启用持久化会损失性能,吞吐量甚至可能降低10倍

  2. RabbitMQ集群中的队列并没有备份,而是均匀分布在各个节点,如果队列所在节点挂掉,在节点恢复重建队列前消息会丢失。如果在持久化到磁盘前崩溃,消息仍然会丢失。

  3. 在生产者这一侧,如果没有采用事务模式,消息在进入RabbitMQ之后,并不是立刻落到磁盘上的,这里有一个刷盘时机的限制。如果这时服务器宕机了,那么这部分没有刷盘的消息自然会被丢了。如果设置了事务,只有在消息刷到磁盘上,才算是正常结束。RabbitMQ会有一个buffer,大小为1M,生产者产生的数据进入MQ的时候,会先进入这个buffer。只有当buffer写满之后,才会将buffer里面的内容写入文件中(这里仍不一定立即写入磁盘中)。
    此外,还有一个固定的刷盘周期:25ms,也就是不管buffer有没有满,每隔25ms,都会进行一次刷盘操作,将buffer里面的内容与未刷入磁盘的文件内容落到磁盘上。

负载均衡

在MQ集群中,往往不会只有一台机器,这样的话,每一个客户端要具体连接到那一台服务器就需要进行控制。举个例子,假设集群中三台机器,但是所有的连接都打到了其中的一台机器上,这样的话,整个系统的性能和可靠性显然是有问题的。

轮询法

将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。

代码示例:

 

public class RoundRobin {
    private static List<String> list = new ArrayList<String>() {{
        add("192.168.0.2");
        add("192.168.0.3");
        add("192.168.0.4");
    }};
    private static int pos = 0;
    private static final Object lock = new Object();
    public static String getConnectionAddress() {
        String ip = null;
        synchronized(lock) {
            ip = list.get(pos);
            if (++pos >= list.size()) {
                pos = 0;
            }
        }
        return ip;
    }
}

随机法

通过随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配(也就是轮询分配)。

public class RandomAccess {
    private static List<String> list = new ArrayList<String>(){{
        add("192.168.0.2");
        add("192.168.0.3");
        add("192.168.0.4");
    }};
    public static String getConnectionAccess() {
        Random random = new Random();
        int pos = random.nextInt(list.size());
        return list.get(pos);
    }
}

源地址哈希法

源地址哈希的思想是根据获取的客户端ip地址,通过哈希函数计算到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客户端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一ip地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。 

 

public class IpHash {
    private static List<String> list = new ArrayList<String>(){{
        add("192.168.0.2");
        add("192.168.0.3");
        add("192.168.0.4");
    }};
    public static String getConnectionAccess() throws UnknownHostException {
        int ipHashCode = InetAddress.getLocalHost().getHostAddress().hashCode();
        int pos = ipHashCode % list.size();
        return list.get(pos);
    }
}

加权轮询法

不同的服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请求;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载。加权轮询能够很好地处理这一问题,并将请求顺序且按照权重分配到后端。 

加权随机法

与加权轮询法一样,加权随机法也根据后端机器的配置、系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。

最小连接数法

最小连接数算法比较灵活和智能,由于服务器的配置不尽相同,对于请求的处理有快有慢,它是根据服务器当前的连接情况,动态地选择其中当前积压连接数最少的一台服务器来处理当前的请求,尽可能地提高服务器的利用效率,将负载合理地分流到每一台服务器。

集群

RabbitMQ是基于Erlang实现的,而Erlang天生是具有分布式特性的(基于Erlang的magic cookie),所以对于RabbitMQ而言,实现集群非常简单,并不像其他的MQ那样实现集群需要zk等。

在集群中,不同的节点会彼此同步四种类型的元数据:

  • 队列元数据:队列名称和它的属性
  • 交换器元数据:交换器名称、类型和属性
  • 绑定元数据:一张简单的表格展示了如何将消息路由到queue
  • vhost元数据:为vhost内的队列、交换器和绑定提供命名空间和安全属性。

在RabbitMQ集群中,会有多个节点,这些节点有内存节点和磁盘节点之分。顾名思义,内存节点,就是上面所说的同步的内容都放到了内存中,而磁盘节点就是这些信息都会放到磁盘中。当然,如果将exchange和queue设置为持久化的,即使是在内存节点上,这些信息也是会持久化到磁盘上的。

  1. 如果只有一个节点,则只能设置为磁盘节点。
  2. RabbitMQ集群中至少要有一个磁盘节点,所有其他的节点都可以是内存节点。在这种情况下,所有元数据有改动时,必须通知到磁盘节点,由其进行落盘处理。一旦磁盘节点挂掉,MQ可以正常运转,但是不会允许进行元数据的增删改查。
    1. 解决这种问题的一个思路是,使用双磁盘节点,那么只会在两个节点都挂掉的时候,才会出现这个问题,而出现这种情况的几率就比较低了。
    2. RabbitMQ节点启动时不指定的话,默认是磁盘节点。
    3. 其实RabbitMQ文档表示磁盘节点比内存节点性能差不了多少,建议都是用磁盘节点。

默认模式

下图展示了默认模式下的各节点示意图。各个节点之间只会复制队列元数据,不会同步队列的消息内容。

默认的集群模式下,对于exchenge的拷贝,是镜像拷贝,每个节点的内容完全一致,所有节点共享相同的exchange信息。此外,queue的元数据也会在各个节点之间进行同步,但是队列内的数据不会参与同步。

MQ在从生产者那里拿到消息之后,消息是会先进入exchange的,而exchange会立即将消息路由到queue里面去,如果消费者消费能力不足,消息就会堆积在queue里面。换句话说,MQ的消息缓存能力主要由queue来实现。而这里的默认模式,每个节点之间会同步所有的exchange信息,并且会复制队列元数据,所以每一个节点都知道exchange和queue的所有属性,因此,消息无论从哪个节点进入exchange,这个节点都会知道这个消息应该路由到哪一个queue里面去。但是,一个consumer往往不会与所有的节点建立连接,这样的话,未建立连接的节点需要将queue里面的消息转发到那些建立了连接的节点以供消费。 

特点:

  • 每个节点不用关心所有的消息内容,因此可以有更大的空间来缓存消息(因为消息只会存一份)。
  • 缺点是,如果一个节点挂掉,这个节点上所有未消费的消息都无法从其他节点获取,一旦消息没有做持久化,这些消息都会丢失。如果做了持久化,也只能等这个节点重启之后,才能再进行消费。 

镜像模式

镜像模式与默认模式的一个区别就是队列内消息的处理方式。在镜像模式下,每一个消息会在每一个节点主动进行拷贝,每个节点存储一份(默认模式下,只有当consumer需要消费数据时,才会对消息进行拷贝)。如下图所示:

样,我们可以看出,对于镜像模式而言,当某一个节点挂掉的时候,只要将链接切到另外一个节点下,就可以继续进行消息的正常消费,可靠性大为提升。但是,因为MQ内部会存在大量的消息路由,所以MQ的整体性能会受到影响(内部拷贝会占用大量的内部网络带宽),同时因为每一个节点都会保存所有的消息,所以对消息的缓存能力会有一定的影响(主要是跟默认模式进行比较)。

特殊queue/exchange

排他队列

如果一个队列声明为了排他队列,那么该队列仅对首次声明它的连接可见,并且在断开连接时自动进行删除。

  • 排他队列是基于连接可见的,同一个连接的不同channel是可以访问其他channel创建的排他队列的
  • 如果一个连接建立了排他队列,那么是不允许其他连接建立同名的排他队列的
  • 即使这个队列设置了持久化,一旦关闭连接(比如客户端退出),这个队列也会自动删除。所以,一旦机器宕机,这个队列是无法恢复的。同样,排他队列适用于一个客户端进行消息的发送和读取的应用场景。

 

临时队列

队列可以设置一个自动删除属性,如果队列没有任何订阅消费者时,会自动删除的。

优先级队列

RabbitMQ本身并没有实现优先级队列,不过有插件可以进行支持。

Dead Letter Exchange

可以对一个queue设置一个Dead Letter Exchange属性,当消息超时或者消息被nack/reject并且设置requeue=false时,会进入这个队列。

因为RabbitMQ没有延时队列和死信队列的设置,所以需要借助于Dead Letter Exchange来实现。RabbitMQ可以为queue或者消息添加一个超时属性,当超时未消费的话,会进行判断,如果有设置Dead Letter Exchange,会进入这个exchange里面,如果没有设置,会直接被丢弃掉。

Alternate Exchanges

生产者生产出来的消息,进入exchange之后,找不到合适的queue时,会落到这个队列中。

参考文档:

http://www.diggerplus.org/archives/3110 

https://blog.csdn.net/anzhsoft/article/details/19563091

https://blog.csdn.net/u013256816/article/details/77131753

http://www.rabbitmq.com/documentation.html

http://chyufly.github.io/blog/2016/04/10/rabbitmq-cluster/

https://www.cnblogs.com/sellsa/p/8056173.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值