RabbitMQ学习(四)——消息分发机制

在前面的一篇博文中,我们对RabbitMQ中的交换机有了大致的了解,同时结合Spring Boot的实例,让我们对RabbitMQ的用法有了更清晰的认识。如果忘记了可以去复习一下,RabbitMQ学习(三)——探索交换机(Exchange),结合SpringBoot实战。

今天我们将要对RabbitMQ的消息机制进行更详细的探究,在上一篇文章中其实也涉及到了消息机制的问题。只是没有深入探究,今天我们将要开始详细了解该机制的特点。

我们知道每个消息处理的时间是不同的,换句话说消息的复杂度是不同的,有些消息很复杂需要很久的时间,有些消息很简单,只需要耗时一会儿就可以完成。而在实际情况下如何考虑分配资源,让效率达到最大化,从而实现按能力分配任务,达到物尽其用。这就需要了解消息的分发机制。

在这里我们使用Thread.Sleep()方法来模拟耗时,时间越久,任务就越复杂。

这里我们结合前面的例子,在spring boot的代码架构下使用RabbitMQ来探究一下,分发机制。首先我们结合前面第三节文章的例子,在sender和receiver下新建DistributionSender.java和DistributionReceiver.java来模拟发送者和接收者。

DistributionSender.java:

@Component
public class DistributionSender {

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send(int i) {
        // 发送的消息
        String message = "This is a task, and the complexity is " + i + "。" + StringUtils.repeat(".", i);
        this.rabbitTemplate.convertAndSend("distribu", message);
    }

}

这里采用的是默认交换机,队列为“distribu”。需要在config包下面的RabbitConfig.java里面添加如下队列:

    /**
     * 申明distribu队列
     * 
     * @return
     */
    @Bean
    public Queue DistribuQueue() {
        return new Queue("distribu");
    }

DistributionReceiver.java:

@Component
public class DistributionReceiver {


    /**
     * 消费者A
     * 
     * @param msg
     */
    @SuppressWarnings("deprecation")
    @RabbitListener(queues = "distribu")
    public void processA(Message message) {
        String msg = new String(message.getBody());
        System.out.println(" DistributionReceiverA  : " + msg);
        SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
        System.out.println(" ProccessingA... at " + time.format(new Date()));

        try {
            for (char ch : msg.toCharArray()) {
                if (ch == '.') {
                    doWork(1000);
                }
            }
        } catch (InterruptedException e) {
        } finally {
            System.out.println(" A Done! at " + time.format(new Date()));
        }
    }

    private void doWork(long time) throws InterruptedException {
        Thread.sleep(time);
    }

}

然后在controller包的RabbitTest.java文件里添加Restful接口,模拟发送请求。

    @Autowired
    private DistributionSender distributionSender;

    /**
     * 分发机制消息发送测试
     */
    @GetMapping("/distribu")
    public void distribu() {
        distributionSender.send(3);
    }

运行程序,访问http://localhost:8080/rabbit/distribu,可以得到下面的打印的信息:

 DistributionReceiverA  : This is a task, and the complexity is 3...
 ProccessingA... at 2018-05-23 21:29:18:0628
 A Done! at 2018-05-23 21:29:21:0639

从打印的信息可以看出这里就模拟了完成任务需要3秒钟的时间任务实现。

下面我们更改发送的消息数量,在controller控制器里面进行更改,如下:

    /**
     * 分发机制消息发送测试
     */
    @GetMapping("/distribu")
    public void distribu() {
        for (int i = 0; i < 5; i++) {
            //发送任务复杂度都为1的消息
            distributionSender.send(1);
        }
    }

模拟发送5条消息,并且每条发送的消息的复杂度都是相同的,复杂度都为1。

然后再在receiver包中DistributionReceiver.java新增一个消费者B,如下:

    /**
     * 消费者B
     * 
     * @param msg
     */
    @SuppressWarnings("deprecation")
    @RabbitListener(queues = "distribu")
    public void processB(Message message) {
        String msg = new String(message.getBody());
        System.out.println(" DistributionReceiverB  : " + msg);
        SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
        System.out.println(" ProccessingB... at " + time.format(new Date()));

        try {
            for (char ch : msg.toCharArray()) {
                if (ch == '.') {
                    doWork(1000);
                }
            }
        } catch (InterruptedException e) {
        } finally {
            System.out.println(" B Done! at " + time.format(new Date()));
        }
    }

再次运行程序,访问接口,结果如下:

 DistributionReceiverA  : This is a task, and the complexity is 1。.
 ProccessingA... at 2018-05-22 23:23:43:0014
 DistributionReceiverB  : This is a task, and the complexity is 1。.
 ProccessingB... at 2018-05-22 23:23:43:0014
 A Done! at 2018-05-22 23:23:44:0017
 B Done! at 2018-05-22 23:23:44:0017
 DistributionReceiverA  : This is a task, and the complexity is 1。.
 DistributionReceiverB  : This is a task, and the complexity is 1。.
 ProccessingA... at 2018-05-22 23:23:44:0093
 ProccessingB... at 2018-05-22 23:23:44:0093
 A Done! at 2018-05-22 23:23:45:0095
 B Done! at 2018-05-22 23:23:45:0095
 DistributionReceiverB  : This is a task, and the complexity is 1。.
 ProccessingB... at 2018-05-22 23:23:45:0143
 B Done! at 2018-05-22 23:23:46:0148

在消息相同,A、B处理能力一样情况下,我们可以发现A、B几乎是同时处理消息,消息发送顺序为A->B->A->B->B。可以看出这里并没有实现A与B平均轮询的情况,在最后的情况B执行了两次。

接着现在我们把A处理能力更改为每个点要Thread.sleep(4000), B为Thread.sleep(1000),就是B的处理能力是A的四倍。运行一下,我们看一下打印的结果:

 DistributionReceiverB  : This is a task, and the complexity is 1。.
 ProccessingB... at 2018-05-22 23:24:48:0623
 DistributionReceiverA  : This is a task, and the complexity is 1。.
 ProccessingA... at 2018-05-22 23:24:48:0623
 B Done! at 2018-05-22 23:24:49:0624
 DistributionReceiverB  : This is a task, and the complexity is 1。.
 ProccessingB... at 2018-05-22 23:24:49:0663
 B Done! at 2018-05-22 23:24:50:0664
 DistributionReceiverB  : This is a task, and the complexity is 1。.
 ProccessingB... at 2018-05-22 23:24:50:0704
 B Done! at 2018-05-22 23:24:51:0709
 DistributionReceiverB  : This is a task, and the complexity is 1。.
 ProccessingB... at 2018-05-22 23:24:51:0748
 A Done! at 2018-05-22 23:24:52:0629
 B Done! at 2018-05-22 23:24:52:0749

现在我们可以清晰地看到在这里B处理了4条消息,而A只处理了1条消息。这里就是按公平分发的机制来发送消息的,即按消费者处理能力来分发消息。

注意现在重点来了,RabbitMQ的分发机制

1、Round-robin dispatch(轮询分发)

这个是RabbitMQ默认的消息分发机制,使用任务队列的优点之一就是可以轻易的并行工作。如果我们有很多要分发的消息,可以通过增加工作者(消费者)来解决这种状况,使得系统的伸缩性更加容易扩展。

在默认情况下,RabbitMQ不会顾虑消息者处理消息的能力,即使其中有的消费者闲置有的消费者高负荷。RabbitMQ会逐个发送消息到在序列中的下一个消费者(而不考虑每个任务的时长等等,且是提前一次性分配,并非一个一个分配)。平均每个消费者获得相同数量的消息,这种方式分发消息机制称为Round-Robin(轮询)。

2、Fair dispatch (公平分发)

而公平分发,则是根据消费者的处理能力来进行分发处理的。这里主要是通过设置prefetchCount 参数来实现的。这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理规定的数量级个数的Message。换句话说,在接收到该Consumer的ack前,它不会将新的Message分发给它。 比如prefetchCount=1,则在同一时间下,每个Consumer在同一个时间点最多处理1个Message,同时在收到Consumer的ack前,它不会将新的Message分发给它。

注意:使用公平分发,必须关闭自动应答,改为手动应答。

说完了概念,我们再来思考一下,前面我们的实例。在使用Spring Boot结合RabbitMQ时,我们并没有手动去应答,那么这为啥是采用的公平分发机制?

这个是因为Spring Boot封装的RabbitMQ方法,默认ACK机制是使用手工应答机制,当@RabbitListener修饰的方法被调用且没有抛出异常时, Spring Boot会为我们自动应答。

我们可以在@RabbitListener源码的注解里看到,

RabbitMQ学习(四)——消息分发机制_第1张图片

如果没有指定containerFactory,将采用默认的containerFactory。然后我们在RabbitListenerContainerFactory中查看到这个接口与MessageListenerContainer有关,
RabbitMQ学习(四)——消息分发机制_第2张图片

接着查看MessageListenerContainer,这是一个接口,我们查看实现了该接口的类,在源码包了提供了一个SimpleMessageListenerContainer的类,在里面我们找到了DEFAULT_PREFETCH_COUNT,这下就清晰明了了。
RabbitMQ学习(四)——消息分发机制_第3张图片

默认情况下,Spring Boot中的RabbitMQ采用手动确认机制,只要如果不是程序员编程实现应答,框架就会为我们自动去确认。并且prefetchCount=1,这下就可以解释为啥上面的实例出现的结果了。

下面我们再通过原生的RabbitMQ客户端java代码来讲解一下消息分发机制的情况:

轮询分发

还是在上面的实例里,我们在sender包下,新建文件DistributionSender2.java,代码如下:

DistributionSender2.java :

package net.anumbrella.rabbitmq.sender;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import org.apache.commons.lang.StringUtils;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class DistributionSender2 {

    private final static String QUEUE_NAME = "test";

    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * 创建连接连接到MabbitMQ
         */
        ConnectionFactory factory = new ConnectionFactory();

        // 设置MabbitMQ所在主机ip或者主机名
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setHost("127.0.0.1");
        factory.setVirtualHost("/");
        factory.setPort(5672);

        // 创建一个连接
        Connection connection = factory.newConnection();

        // 创建一个频道
        Channel channel = connection.createChannel();

        // 指定一个队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        for (int i = 0; i < 8; i++) {
            // 发送的消息
            String message = "This is a task, and the complexity is " + i + "。" + StringUtils.repeat(".", i);
            // 往队列中发出一条消息
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

            System.out.println(" DistributionSender2 Sent '" + message + "'");
        }
        // 关闭频道和连接
        channel.close();
        connection.close();
    }

}

模拟发送8条消息,然后再receiver包下,新建DistributionReceiver2.java和DistributionReceiver3.java,代码如下:

DistributionReceiver2.java:

package net.anumbrella.rabbitmq.receiver;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

public class DistributionReceiver2 {

    private final static String QUEUE_NAME = "test";

    public static void main(String[] argv) throws IOException, InterruptedException, TimeoutException {

        ConnectionFactory factory = new ConnectionFactory();

        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setHost("127.0.0.1");
        factory.setVirtualHost("/");
        factory.setPort(5672);
        // 打开连接和创建频道,与发送端一样

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        // 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println("Receiver2 waiting for messages. To exit press CTRL+C");

        // 创建队列消费者
        final Consumer consumer = new DefaultConsumer(channel) {
              @Override
              public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");

                System.out.println(" DistributionReceiver2  : " + message);
                SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
                System.out.println(" Proccessing2... at " + time.format(new Date()));
                try {
                    for (char ch: message.toCharArray()) {
                        if (ch == '.') {
                            doWork(1000);
                        }
                    }
                } catch (InterruptedException e) {
                } finally {
                  System.out.println(" DistributionReceiver2 Done! at " +time.format(new Date()));
                }
              }
            };
            channel.basicConsume(QUEUE_NAME, true, consumer);
    }


    private static void doWork(long time) throws InterruptedException {
        Thread.sleep(time);
    }

}

DistributionReceiver3.java:

package net.anumbrella.rabbitmq.receiver;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

public class DistributionReceiver3 {

    private final static String QUEUE_NAME = "test";

    public static void main(String[] argv) throws IOException, InterruptedException, TimeoutException {

        ConnectionFactory factory = new ConnectionFactory();

        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setHost("127.0.0.1");
        factory.setVirtualHost("/");
        factory.setPort(5672);
        // 打开连接和创建频道,与发送端一样

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        // 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println("Receiver3 waiting for messages. To exit press CTRL+C");

        // 创建队列消费者
        final Consumer consumer = new DefaultConsumer(channel) {
              @Override
              public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");

                System.out.println(" DistributionReceiver3  : " + message);
                SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
                System.out.println(" Proccessing3... at " + time.format(new Date()));
                try {
                    for (char ch: message.toCharArray()) {
                        if (ch == '.') {
                            doWork(4000);
                        }
                    }
                } catch (InterruptedException e) {
                } finally {
                  System.out.println(" DistributionReceiver3 Done! at " +time.format(new Date()));
                }
              }
            };
            channel.basicConsume(QUEUE_NAME, true, consumer);
    }


    private static void doWork(long time) throws InterruptedException {
        Thread.sleep(time);
    }

}

我们可以看到DistributionReceiver2是DistributionReceiver3处理消息的4倍。

现在我们先启动DistributionReceiver2.java与DistributionReceiver3.java监听消息,然后再启动DistributionSender2.java发送消息,可以查看结果如下:

发送消息:

 DistributionSender2 Sent 'This is a task, and the complexity is 0。'
 DistributionSender2 Sent 'This is a task, and the complexity is 1。.'
 DistributionSender2 Sent 'This is a task, and the complexity is 2。..'
 DistributionSender2 Sent 'This is a task, and the complexity is 3。...'
 DistributionSender2 Sent 'This is a task, and the complexity is 4。....'
 DistributionSender2 Sent 'This is a task, and the complexity is 5。.....'
 DistributionSender2 Sent 'This is a task, and the complexity is 6。......'
 DistributionSender2 Sent 'This is a task, and the complexity is 7。.......'

DistributionReceiver2.java收到的消息:

Receiver2 waiting for messages. To exit press CTRL+C
 DistributionReceiver2  : This is a task, and the complexity is 0。
 Proccessing2... at 2018-05-23 23:12:50:0874
 DistributionReceiver2 Done! at 2018-05-23 23:12:50:0876
 DistributionReceiver2  : This is a task, and the complexity is 2。..
 Proccessing2... at 2018-05-23 23:12:50:0876
 DistributionReceiver2 Done! at 2018-05-23 23:12:52:0880
 DistributionReceiver2  : This is a task, and the complexity is 4。....
 Proccessing2... at 2018-05-23 23:12:52:0880
 DistributionReceiver2 Done! at 2018-05-23 23:12:56:0890
 DistributionReceiver2  : This is a task, and the complexity is 6。......
 Proccessing2... at 2018-05-23 23:12:56:0890
 DistributionReceiver2 Done! at 2018-05-23 23:13:02:0910

DistributionReceiver3.java收到的消息:

Receiver3 waiting for messages. To exit press CTRL+C
 DistributionReceiver3  : This is a task, and the complexity is 1。.
 Proccessing3... at 2018-05-23 23:12:50:0879
 DistributionReceiver3 Done! at 2018-05-23 23:12:54:0884
 DistributionReceiver3  : This is a task, and the complexity is 3。...
 Proccessing3... at 2018-05-23 23:12:54:0885
 DistributionReceiver3 Done! at 2018-05-23 23:13:06:0887
 DistributionReceiver3  : This is a task, and the complexity is 5。.....
 Proccessing3... at 2018-05-23 23:13:06:0888
 DistributionReceiver3 Done! at 2018-05-23 23:13:26:0903
 DistributionReceiver3  : This is a task, and the complexity is 7。.......
 Proccessing3... at 2018-05-23 23:13:26:0904
 DistributionReceiver3 Done! at 2018-05-23 23:13:54:0921

我们知道DistributionReceiver2处理速度是DistributionReceiver3处理速度的4倍,但是结果两者收到的消息数是相同的。
DistributionReceiver2:0,2,4,6
DistributionReceiver3:1,3,5,7
这里就是采用的轮询分发,与前面讲解的刚刚符合。

公平分发

为了使用公平分发,我们需要通过basicQos( prefetchCount = 1)方法,来设置prefetchCount参数,同时更改确认方式为手动确认。

int prefetchCount = 1;
channel.basicQos(prefetchCount);

同理我们在sender包下新建DistributionSender3.java,代码如下:

package net.anumbrella.rabbitmq.sender;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import org.apache.commons.lang.StringUtils;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class DistributionSender3 {

    private final static String QUEUE_NAME = "test";

    public static void main(String[] args) throws IOException, TimeoutException {
        /**
         * 创建连接连接到MabbitMQ
         */
        ConnectionFactory factory = new ConnectionFactory();

        // 设置MabbitMQ所在主机ip或者主机名
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setHost("127.0.0.1");
        factory.setVirtualHost("/");
        factory.setPort(5672);

        // 创建一个连接
        Connection connection = factory.newConnection();

        // 创建一个频道
        Channel channel = connection.createChannel();

        // 指定一个队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        int prefetchCount = 1;
        // 限制发给同一个消费者不得超过1条消息
        channel.basicQos(prefetchCount);

        for (int i = 0; i < 8; i++) {
            // 发送的消息
            String message = "This is a task, and the complexity is " + i + "。" + StringUtils.repeat(".", 1);
            // 往队列中发出一条消息
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

            System.out.println(" DistributionSender3 Sent '" + message + "'");
        }
        // 关闭频道和连接
        channel.close();
        connection.close();
    }

}

更改为手动确认需要添加如下代码,basicAck 方法的第二个参数 multiple 取值为 false 时,表示通知 RabbitMQ 当前消息被确认;如果为 true,则额外将比第一个参数指定的 delivery tag 小的消息一并确认。(在RabbitMQ的每个信道中,每条消息的 Delivery Tag 从 1 开始递增,这里的批量处理是指同一信道中的消息):

channel.basicAck(envelope.getDeliveryTag(), false);

通过更改auto为false,即更改自动确认为false。如下:

channel.basicConsume(QUEUE_NAME, false, consumer);

DistributionReceiver4.java:

package net.anumbrella.rabbitmq.receiver;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

public class DistributionReceiver4 {

    private final static String QUEUE_NAME = "test";

    public static void main(String[] argv) throws IOException, InterruptedException, TimeoutException {

        ConnectionFactory factory = new ConnectionFactory();


        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setHost("127.0.0.1");
        factory.setVirtualHost("/");
        factory.setPort(5672);
        // 打开连接和创建频道,与发送端一样

        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        // 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println("Receiver4 waiting for messages. To exit press CTRL+C");

        //保证一次只分发一个
        channel.basicQos(1);

        // 创建队列消费者
        final Consumer consumer = new DefaultConsumer(channel) {
              @Override
              public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");

                System.out.println(" DistributionReceiver4  : " + message);
                SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
                System.out.println(" Proccessing4... at " + time.format(new Date()));
                try {
                    for (char ch: message.toCharArray()) {
                        if (ch == '.') {
                            doWork(1000);
                        }
                    }
                } catch (InterruptedException e) {
                } finally {
                  System.out.println(" DistributionReceiver4 Done! at " +time.format(new Date()));
                  channel.basicAck(envelope.getDeliveryTag(), false);
                }
              }
            };
            channel.basicConsume(QUEUE_NAME, false, consumer);
    }


    private static void doWork(long time) throws InterruptedException {
        Thread.sleep(time);
    }

}

DistributionReceiver5.java:

package net.anumbrella.rabbitmq.receiver;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

public class DistributionReceiver5 {

    private final static String QUEUE_NAME = "test";

    public static void main(String[] argv) throws IOException, InterruptedException, TimeoutException {

        ConnectionFactory factory = new ConnectionFactory();


        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setHost("127.0.0.1");
        factory.setVirtualHost("/");
        factory.setPort(5672);
        // 打开连接和创建频道,与发送端一样

        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        // 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println("Receiver5 waiting for messages. To exit press CTRL+C");

        //保证一次只分发一个
        channel.basicQos(1);

        // 创建队列消费者
        final Consumer consumer = new DefaultConsumer(channel) {
              @Override
              public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");

                System.out.println(" DistributionReceiver5  : " + message);
                SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
                System.out.println(" Proccessing5... at " + time.format(new Date()));
                try {
                    for (char ch: message.toCharArray()) {
                        if (ch == '.') {
                            doWork(4000);
                        }
                    }
                } catch (InterruptedException e) {
                } finally {
                  System.out.println(" DistributionReceiver5 Done! at " +time.format(new Date()));
                  channel.basicAck(envelope.getDeliveryTag(), false);
                }
              }
            };
            channel.basicConsume(QUEUE_NAME, false, consumer);
    }


    private static void doWork(long time) throws InterruptedException {
        Thread.sleep(time);
    }

}

一样的DistributionReceiver4.java是DistributionReceiver5.java处理速度的4倍。然后我们也把消息的复杂度更改为1,让所有消息任务的复杂度都相同。
同样的现在我们先启动DistributionReceiver4.java与DistributionReceiver5.java监听消息,然后再启动DistributionSender3.java发送消息,可以查看结果如下:

发送消息:

 DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
 DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
 DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
 DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
 DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
 DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
 DistributionSender3 Sent 'This is a task, and the complexity is 1。.'
 DistributionSender3 Sent 'This is a task, and the complexity is 1。.'

DistributionReceiver4.java接受者:

Receiver4 waiting for messages. To exit press CTRL+C
 DistributionReceiver4  : This is a task, and the complexity is 1。.
 Proccessing4... at 2018-05-23 23:48:40:0915
 DistributionReceiver4 Done! at 2018-05-23 23:48:41:0918
 DistributionReceiver4  : This is a task, and the complexity is 1。.
 Proccessing4... at 2018-05-23 23:48:41:0956
 DistributionReceiver4 Done! at 2018-05-23 23:48:42:0958
 DistributionReceiver4  : This is a task, and the complexity is 1。.
 Proccessing4... at 2018-05-23 23:48:42:0999
 DistributionReceiver4 Done! at 2018-05-23 23:48:44:0002
 DistributionReceiver4  : This is a task, and the complexity is 1。.
 Proccessing4... at 2018-05-23 23:48:44:0056
 DistributionReceiver4 Done! at 2018-05-23 23:48:45:0061
 DistributionReceiver4  : This is a task, and the complexity is 1。.
 Proccessing4... at 2018-05-23 23:48:45:0099
 DistributionReceiver4 Done! at 2018-05-23 23:48:46:0104
 DistributionReceiver4  : This is a task, and the complexity is 1。.
 Proccessing4... at 2018-05-23 23:48:46:0144
 DistributionReceiver4 Done! at 2018-05-23 23:48:47:0149

DistributionReceiver5.java接受者:

Receiver5 waiting for messages. To exit press CTRL+C
 DistributionReceiver5  : This is a task, and the complexity is 1。.
 Proccessing5... at 2018-05-23 23:48:40:0918
 DistributionReceiver5 Done! at 2018-05-23 23:48:44:0921
 DistributionReceiver5  : This is a task, and the complexity is 1。.
 Proccessing5... at 2018-05-23 23:48:44:0960
 DistributionReceiver5 Done! at 2018-05-23 23:48:48:0965

可以看到复杂度相同的8条消息,消费者5处理了2条,消费者4处理了6条。而消费者4是消费者5能力的4倍。完全是按能力分配消息的分发,这就是公平分发机制,也符合上面的定义。

好了,RabbitMQ的消息机制到此就完了,上面的demo还是放在了github上,可以结合运行来学习分析。rabbitmq-demo。

你可能感兴趣的:(RabbitMQ)