转自 http://blog.csdn.net/xiaoxian8023/article/details/48681987

上一篇博文中简单介绍了一下RabbitMQ的基础知识,并写了一个经典语言入门程序——HelloWorld。本篇博文中我们将会创建一个工作队列用来在工作者(consumer)间分发耗时任务。同样是翻译的官网实例

工作队列

前一篇博文中,我们完成了一个简单的对声明的队列进行发送和接受消息程序。下面我们将创建一个工作队列,来向多个工作者(consumer)分发耗时任务。

工作队列(又名:任务队列)的主要任务是为了避免立即做一个资源密集型的却又必须等待完成的任务。相反的,我们进行任务调度:将任务封装为消息并发给队列。在后台运行的工作者(consumer)将其取出,然后最终执行。当你运行多个工作者(consumer),队列中的任务被工作进行共享执行。

这样的概念对于在一个HTTP短链接的请求窗口中处理复杂任务的web应用程序,是非常有用的。

准备

使用Thread.Sleep()方法来模拟耗时。采用小数点的数量来表示任务的复杂性。每一个点将住哪用1s的“工作”。例如,Hello... 处理完需要3s的时间。

发送端(生产者):NewTask.java

  1. public class NewTask {
  2. private final static String QUEUE_NAME = "hello";
  3. public static void main(String[] args) throws IOException {
  4. /**
  5. * 创建连接连接到MabbitMQ
  6. */
  7. ConnectionFactory factory = new ConnectionFactory();
  8. // 设置MabbitMQ所在主机ip或者主机名
  9. factory.setHost("127.0.0.1");
  10. // 创建一个连接
  11. Connection connection = factory.newConnection();
  12. // 创建一个频道
  13. Channel channel = connection.createChannel();
  14. // 指定一个队列
  15. channel.queueDeclare(QUEUE_NAME, false, false, false, null);
  16. // 发送的消息
  17. String message = "Hello World...";
  18. // 往队列中发出一条消息
  19. channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
  20. System.out.println(" [x] Sent '" + message + "'");
  21. // 关闭频道和连接
  22. channel.close();
  23. connection.close();
  24. }
  25. }

工作者(消费者)Worker.java

  1. public class Worker {
  2. private final static String QUEUE_NAME = "hello";
  3. public static void main(String[] argv) throws IOException, InterruptedException {
  4. ConnectionFactory factory = new ConnectionFactory();
  5. factory.setHost("127.0.0.1");
  6. // 打开连接和创建频道,与发送端一样
  7. Connection connection = factory.newConnection();
  8. Channel channel = connection.createChannel();
  9. // 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。
  10. channel.queueDeclare(QUEUE_NAME, false, false, false, null);
  11. System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
  12. // 创建队列消费者
  13. final Consumer consumer = new DefaultConsumer(channel) {
  14. @Override
  15. public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
  16. String message = new String(body, "UTF-8");
  17. System.out.println(" [x] Received '" + message + "'");
  18. System.out.println(" [x] Proccessing... at " +new Date().toLocaleString());
  19. try {
  20. for (char ch: message.toCharArray()) {
  21. if (ch == '.') {
  22. Thread.sleep(1000);
  23. }
  24. }
  25. } catch (InterruptedException e) {
  26. } finally {
  27. System.out.println(" [x] Done! at " +new Date().toLocaleString());
  28. }
  29. }
  30. };
  31. channel.basicConsume(QUEUE_NAME, true, consumer);
  32. }
  33. }

运行结果如下:


任务分发机制

正主来了。。。下面开始介绍各种任务分发机制。

Round-robin(轮询分发)

使用任务队列的优点之一就是可以轻易的并行工作。如果我们积压了好多工作,我们可以通过增加工作者(消费者)来解决这一问题,使得系统的伸缩性更加容易。

修改一下NewTask,使用for循环模拟多次发送消息的过程:

  1. for (int i = 0; i < 5; i++) {
  2. // 发送的消息
  3. String message = "Hello World"+Strings.repeat(".", i);
  4. // 往队列中发出一条消息
  5. channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
  6. System.out.println(" [x] Sent '" + message + "'");
  7. }
 

我们先启动1个生产者实例,2个工作者实例,看一下如何执行:

从上述的结果中,我们可以得知,在默认情况下,RabbitMQ将逐个发送消息到在序列中的下一个消费者(而不考虑每个任务的时长等等,且是提前一次性分配,并非一个一个分配)。平均每个消费者获得相同数量的消息。这种方式分发消息机制称为Round-Robin(轮询)。

Fair dispatch(公平分发)

您可能已经注意到,任务分发仍然没有完全按照我们想要的那样。比如:现在有2个消费者,所有的奇数的消息都是繁忙的,而偶数则是轻松的。按照轮询的方式,奇数的任务交给了第一个消费者,所以一直在忙个不停。偶数的任务交给另一个消费者,则立即完成任务,然后闲得不行。而RabbitMQ则是不了解这些的。

这是因为当消息进入队列,RabbitMQ就会分派消息。它不看消费者为应答的数目,只是盲目的将第n条消息发给第n个消费者。

为了解决这个问题,我们使用basicQos( prefetchCount = 1)方法,来限制RabbitMQ只发不超过1条的消息给同一个消费者。当消息处理完毕后,有了反馈,才会进行第二次发送。

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

注:如果所有的工作者都处于繁忙状态,你的队列有可能被填充满。你可能会观察队列的使用情况,然后增加工作者,或者使用别的什么策略。
       还有一点需要注意,使用公平分发,必须关闭自动应答,改为手动应答。这些内容会在下篇博文中讲述。

整体代码如下:生产者NewTask.java

  1. public class NewTask {
  2. private final static String QUEUE_NAME = "hello";
  3. public static void main(String[] args) throws IOException {
  4. /**
  5. * 创建连接连接到MabbitMQ
  6. */
  7. ConnectionFactory factory = new ConnectionFactory();
  8. // 设置MabbitMQ所在主机ip或者主机名
  9. factory.setHost("127.0.0.1");
  10. // 创建一个连接
  11. Connection connection = factory.newConnection();
  12. // 创建一个频道
  13. Channel channel = connection.createChannel();
  14. // 指定一个队列
  15. channel.queueDeclare(QUEUE_NAME, false, false, false, null);
  16. int prefetchCount = 1;
  17. //限制发给同一个消费者不得超过1条消息
  18. channel.basicQos(prefetchCount);
  19. for (int i = 0; i < 5; i++) {
  20. // 发送的消息
  21. String message = "Hello World"+Strings.repeat(".",5-i)+(5-i);
  22. // 往队列中发出一条消息
  23. channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
  24. System.out.println(" [x] Sent '" + message + "'");
  25. }
  26. // 关闭频道和连接
  27. channel.close();
  28. connection.close();
  29. }
  30. }

消费者Worker.java

  1. public class Worker {
  2. private final static String QUEUE_NAME = "hello";
  3. public static void main(String[] argv) throws IOException, InterruptedException {
  4. ConnectionFactory factory = new ConnectionFactory();
  5. factory.setHost("127.0.0.1");
  6. // 打开连接和创建频道,与发送端一样
  7. Connection connection = factory.newConnection();
  8. final Channel channel = connection.createChannel();
  9. // 声明队列,主要为了防止消息接收者先运行此程序,队列还不存在时创建队列。
  10. channel.queueDeclare(QUEUE_NAME, false, false, false, null);
  11. System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
  12. channel.basicQos(1);//保证一次只分发一个
  13. // 创建队列消费者
  14. final Consumer consumer = new DefaultConsumer(channel) {
  15. @Override
  16. public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
  17. String message = new String(body, "UTF-8");
  18. System.out.println(" [x] Received '" + message + "'");
  19. try {
  20. for (char ch: message.toCharArray()) {
  21. if (ch == '.') {
  22. Thread.sleep(1000);
  23. }
  24. }
  25. } catch (InterruptedException e) {
  26. } finally {
  27. System.out.println(" [x] Done! at " +new Date().toLocaleString());
  28. channel.basicAck(envelope.getDeliveryTag(), false);
  29. }
  30. }
  31. };
  32. channel.basicConsume(QUEUE_NAME, false, consumer);
  33. }
  34. }

运行结果如下:

轻松搞定RabbitMQ(二)——工作队列之消息分发机制的更多相关文章

  1. 轻松搞定RabbitMQ(三)——消息应答与消息持久化

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48710653 这个官网的第二个例子中的消息应答和消息持久化部分.我把它摘出来作为单独的一块 ...

  2. 轻松搞定RabbitMQ(六)——主题

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48806871 翻译地址:http://www.rabbitmq.com/tutorials ...

  3. RabbitMQ中交换机的消息分发机制

    RabbitMQ是一个消息代理,它接受和转发消息,是一个由 Erlang 语言开发的遵循AMQP协议的开源实现.在RabbitMQ中生产者不会将消息直接发送到队列当中,而是将消息直接发送到交换机(ex ...

  4. 轻松搞定RabbitMQ(四)——发布/订阅

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48729479 翻译地址:http://www.rabbitmq.com/tutorials ...

  5. 轻松搞定RabbitMQ(一)——RabbitMQ基础知识+HelloWorld

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48679609 本文是简单介绍一下RabbitMQ,参考官网上的教程.同时加入了一些自己的理 ...

  6. 轻松搞定RabbitMQ(五)——路由选择

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48733249 翻译地址:http://www.rabbitmq.com/tutorials ...

  7. 【微服务】之二:从零开始,轻松搞定SpringCloud微服务系列--注册中心(一)

    微服务体系,有效解决项目庞大.互相依赖的问题.目前SpringCloud体系有强大的一整套针对微服务的解决方案.本文中,重点对微服务体系中的服务发现注册中心进行详细说明.本篇中的注册中心,采用Netf ...

  8. Webcast / 技术小视频制作方法——自己动手录制video轻松搞定

    Webcast / 技术小视频制作方法——自己动手录制video轻松搞定 http://blog.sina.com.cn/s/blog_67d387490100wdnh.html 最近申请加入MSP的 ...

  9. 【微服务】之三:从零开始,轻松搞定SpringCloud微服务-配置中心

    在整个微服务体系中,除了注册中心具有非常重要的意义之外,还有一个注册中心.注册中心作为管理在整个项目群的配置文件及动态参数的重要载体服务.Spring Cloud体系的子项目中,Spring Clou ...

随机推荐

  1. 【Luogu】P3746组合数问题(矩阵)

    题目链接 哇我一个活人的智商被题目碾压了 可以把问题转化为有nk个物品,问拿i件物品的方案数有多少种,其中i%k=r. 然后矩阵乘法加速DP即可. #include<cstdio> #in ...

  2. ACM程序设计选修课——1024: 末位零(求末尾0的方法+可有可无的快速幂)

    1024: 末位零 Time Limit: 1 Sec  Memory Limit: 32 MB Submit: 60  Solved: 11 [Submit][Status][Web Board] ...

  3. 【hihocoder】欧拉路径 并查集判连通

    #include<iostream> #include<cstdio> #include<string> #include<cstring> #incl ...

  4. 【bzoj3270】博物馆

    同样是高斯消元,我写的版本就受到了歧视 我怎么又犯把 $j$ 打成 $i$ 这种 $sb$ 错误 题意 一张无向图,两个人分别从 $s_1$ 号点和 $s2$ 号点开始,每轮两人都会同时进行一次以下操 ...

  5. Java NIO系列教程(三-十二) Buffer

    原文链接     作者:Jakob Jenkov     译者:airu     校对:丁一 Java NIO中的Buffer用于和NIO通道进行交互.如你所知,数据是从通道读入缓冲区,从缓冲区写入到 ...

  6. JSR310 时间类型的相互转换

    参数申明: final Date date = new Date(); final Timestamp timestamp = new Timestamp(date.getTime()); final ...

  7. Codevs 1148 == 洛谷 P1057 传球游戏

    1148 传球游戏 2008年NOIP全国联赛普及组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 白银 Silver 题目描述 Description 上体育课的时候,小蛮的老师 ...

  8. 标准C程序设计七---66

    Linux应用             编程深入            语言编程 标准C程序设计七---经典C11程序设计    以下内容为阅读:    <标准C程序设计>(第7版) 作者 ...

  9. linux内核之进程的基本概念(进程,进程组,会话关系)

    进程是操作系统的一个核心概念.每个进程都有自己唯一的标识:进程ID,也有自己的生命周期.一个典型的进程的生命周期如图4-1所示. 进程都有父进程,父进程也有父进程,这就形成了一个以init进程为根的家 ...

  10. 快充 IC BQ25896 的 Dynamic Power Management

    Spec 更正: 上面紅色框框應該還要再增加一個 ILIM pin 硬體所設定的 input current limit, 也就是說 input current limit 最多可以從這 3 個 IL ...