一、Work Queues(using the Java Client) 走起

 	在第上一个教程中我们写程序从一个命名队列发送和接收消息。在这一次我们将创建一个工作队列,将用于分发耗时的任务在多个工作者(worker)之间。
背后的主要思想工作队列(又名:任务队列)是为了避免立即做一个资源密集型任务,不得不等待它完成。相反,我们安排的任务要做。我们封装任务作为消息并将其发送到一个队列。工作进程在后台运行将流行的任务和最终执行的工作。当您运行许多worker的任务将在他们之间共享。这个概念是特别有用的web应用程序中处理复杂的任务是不可能在一个短的HTTP请求窗口。
为了避免等待一些占用大量资源、时间的操作。当我们把任务(Task)当作消息发送到队列中,一个运行在后台的工作者(worker)进程就会取出任务然后处理。当你运行多个工作者(workers),任务就会在它们之间共享。

二、Preparation(预备)

	在RabbitMQ系列教程中,在前一章《柯南君:看大数据时代下的IT架构(4)消息队列之RabbitMQ--案例(Helloword起航)》中,我们发送了一个包含“Hello World”的消息。现在我们将发送的字符串(代表非常复杂的任务),而不是上次的简单的字符串。我们没有真实的任务,比如:图像的自适应、文件的渲染等,那么现在假装我们很忙(通过使用thread.sleep()函数)。我们将字符串中的点的数量作为其复杂性;每一个点都占一秒钟“工作”。例如,一个假的任务描述 "hello world ! "  需要三秒钟。
  • 我们稍微改一下send.java 代码(我们上一章的事例中有源代码),允许任意从命令文件发送消息,这个程序将我们的工作计划放在任务队列中,现在让我先做个类NewTask.java

String message = getMessage(argv);
channel.basicPublish("", "hello", null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");

  从命令行参数,得到一些帮助信息:

private static String getMessage(String[] strings){
if (strings.length < 1)
return "Hello World!";
return joinStrings(strings, " ");
} private static String joinStrings(String[] strings, String delimiter) {
int length = strings.length;
if (length == 0) return "";
StringBuilder words = new StringBuilder(strings[0]);
for (int i = 1; i < length; i++) {
words.append(delimiter).append(strings[i]);
}
return words.toString();
}

我们的老的Recv.java程序也需要一些变化:
  • 它需要假的第二个消息体的每一个点的工作。
  • 它将从队列中发布消息并执行任务,所以我们称之为Worker.java:

while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Received '" + message + "'");
doWork(message);
System.out.println(" [x] Done");
}

我们的假任务模拟执行时间

private static void doWork(String task) throws InterruptedException {
for (char ch: task.toCharArray()) {
if (ch == '.') Thread.sleep(1000);
}
}
编译它们 (工作目录中的jar文件):

$ javac -cp rabbitmq-client.jar NewTask.java Worker.java

三、Round-robin dispatching

	使用一个任务队列的优点之一是能够轻易平行的工作。如果我们建立一个积压的工作,我们可以添加更多的任务(worker),这样,很容易。
首先,让我们试着两个任务(worker)实例同时运行。他们都将从队列中获取消息,但究竟如何?让我们来看看。你需要三个主机开放。
两人将运行工计划。这些游戏机将我们两个消费者- C1和C2。
shell1$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar
Worker
[*] Waiting for messages. To exit press CTRL+C
shell2$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar
Worker
[*] Waiting for messages. To exit press CTRL+C

在第三个我们将发布新的任务。一旦你开始了消费者可以发布几条消息:

shell3$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar
NewTask First message.
shell3$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar
NewTask Second message..
shell3$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar
NewTask Third message...
shell3$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar
NewTask Fourth message....
shell3$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar
NewTask Fifth message.....

   让我们看看我们的workers都传输什么:
shell1$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar
Worker
[*] Waiting for messages. To exit press CTRL+C
[x] Received 'First message.'
[x] Received 'Third message...'
[x] Received 'Fifth message.....'
java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar
Worker
[*] Waiting for messages. To exit press CTRL+C
[x] Received 'Second message..'
[x] Received 'Fourth message....'

默认情况下,RabbitMQ将每个消息发送到下一个消费者,在序列。平均每个消费者将获得相同数量的信息。这种方式称为循环分配消息。试试这三个或更多的worker。

四、Message acknowledgment

	做一个任务可能需要几秒钟。如果一个消费者开始漫长的任务而死,只有部分完成,你可能想知道到底发生什么事情了?
在我们当前的代码情况下,一旦RabbitMQ向客户传递一个消息立即从内存中删除。在这种情况下,如果你kill(杀)了一个任务(worker),
我们将失去刚刚处理的消息。我们也会失去所有的消息被派往这个特殊的工人,但尚未处理。 
但是我们不想失去任何任务。如果一个工作者(worker)死亡,我们想要交付的任务到另一个worker。
为了确保消息是从来都不会迷失的,RabbitMQ支持消息应答。发送ack(knowledgement)从消费者告诉RabbitMQ特定的消息已经收到,
处理和RabbitMQ是免费的,删除它。如果一个消费者停止没有发送ack,RabbitMQ会明白一个消息
QueueingConsumer consumer = new QueueingConsumer(channel);
boolean autoAck = false;
channel.basicConsume("hello", autoAck, consumer); while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
//...
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}

使用这段代码,我们可以确信,即使你杀了一个工作者(worker)使用CTRL + C处理消息时,没有将丢失。工作者(worker)死亡后不久所有未得到确认的消息将被发送。
  问题:
   消息响应
  • 我们不想丢失任何任务消息。如果一个工作者(worker)挂掉了,我们希望任务会重新发送给其他的工作者(worker)。
  • 为了防止消息丢失,RabbitMQ提供了消息[i]响应(acknowledgments)[/i]。消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ就会释放并删除这条消息。
  • 如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,及时工作者(workers)偶尔的挂掉,也不会丢失消息。
  • 消息是没有超时这个概念的;当工作者与它断开连的时候,RabbitMQ会重新发送消息。这样在处理一个耗时非常长的消息任务的时候就不会出问题了。
  • 消息响应默认是开启的。之前的例子中我们可以使用no_ack=True标识把它关闭。是时候移除这个标识了,当工作者(worker)完成了任务,就发送一个响应。

五、Message durability

	我们已经学会了如何确保即使消费者死亡,任务也不会丢失。但是如果RabbitMQ服务器停止我们的任务仍将失去。
RabbitMQ退出或崩溃时它会忘记队列和消息,除非你告诉它不要。两件事必须确保消息不会丢失:我们需要两个队列和消息标记为耐用。
首先,我们需要确保RabbitMQ永远不会失去我们的队列。为了这样做,我们需要声明它经久耐用:
channel.queueDeclare("hello", durable, false, false, null);
尽管这个命令本身是正确的,它不会在我们目前的设置工作。这是因为我们已经定义了一个名为hello的队列不耐用。RabbitMQ不允许您重新定义现有队列具有不同参数并返回一个错误的任何程序,试图这样做。但有一个快速解决方案——让我们声明一个队列具有不同名称,例如task_queue:
channel.queueDeclare("task_queue", durable, false, false, null);
这queueDeclare改变需要应用于生产者和消费者的代码。在这一点上我们确信task_queue队列不会丢失,即使RabbitMQ重启。现在我们需要我们的消息标记为持久性——通过设置MessageProperties PERSISTENT_TEXT_PLAIN(实现BasicProperties)的价值。
channel.basicPublish("", "task_queue",
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
 问题:
消息标记为持久性并不能完全保证信息不会丢失。虽然它告诉RabbitMQ将消息保存到磁盘,还有一个短的时间窗口当RabbitMQ已经接受消息和尚未保存它。
RabbitMQ也不做fsync(2)为每个消息——它可能只是保存到缓存和不写入磁盘。持久性保证不强,但它是足够为我们简单的任务队列。
如果你需要一个更强的保证,那么你可以使用发布者证实。

boolean durable = true;

boolean durable = true;
import com.rabbitmq.client.MessageProperties;

六、Fair
dispatch

	您可能已经注意到,调度仍然不会完全按照我们想要的工作。例如在两名工人(worker)的情况,当所有奇怪的消息是沉重的,甚至消息是光,一名工人将不停地忙,另一个几乎不做任何工作。RabbitMQ并不了解,仍将均匀调度信息。这仅仅是因为RabbitMQ分派消息进入队列的消息的时候。它不看看消费者未得到确认的消息的数量。只是盲目地分派每n个消息到n个消费者。


为了打败,我们可以使用basicQos prefetchCount = 1设置方法。这告诉RabbitMQ不给多个消息到一个工人。或者,换句话说,不要派遣工人的新消息,直到处理和承认了前一个。相反,它会分派到下一个工人,不是仍然很忙。
channel.basicQos(prefetchCount);

int prefetchCount = 1;

问题:
如果所有的工人们正忙着,你的队列可以填满。你要留意,也许添加更多的工人,或有其他一些策略。

七、Putting
it all together

	Final code of our NewTask.java class:
import java.io.IOException;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties; public class NewTask { private static final String TASK_QUEUE_NAME = "task_queue"; public static void main(String[] argv)
throws java.io.IOException { ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); String message = getMessage(argv); channel.basicPublish( "", TASK_QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
System.out.println(" [x] Sent '" + message + "'"); channel.close();
connection.close();
}
//...
}

(NewTask.java source)

And our Worker.java:

import java.io.IOException;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.QueueingConsumer; public class Worker { private static final String TASK_QUEUE_NAME = "task_queue"; public static void main(String[] argv)
throws java.io.IOException,
java.lang.InterruptedException { ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); channel.basicQos(1); QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(TASK_QUEUE_NAME, false, consumer); while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody()); System.out.println(" [x] Received '" + message + "'");
doWork(message);
System.out.println(" [x] Done" ); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
//...
}

RabbitMQ学习总结(4)——分发任务在多个工作者之间实例教程的更多相关文章

  1. 官网英文版学习——RabbitMQ学习笔记(八)Remote procedure call (RPC)

    在第四篇学习笔记中,我们学习了如何使用工作队列在多个工作者之间分配耗时的任务.   但是,如果我们需要在远程计算机上运行一个函数并等待结果呢?这是另一回事.这种模式通常称为远程过程调用或RPC.   ...

  2. RabbitMQ学习系列(四): 几种Exchange 模式

    上一篇,讲了RabbitMQ的具体用法,可以看看这篇文章:RabbitMQ学习系列(三): C# 如何使用 RabbitMQ.今天说些理论的东西,Exchange 的几种模式. AMQP协议中的核心思 ...

  3. RabbitMQ学习总结 第三篇:工作队列Work Queue

    目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...

  4. RabbitMQ学习总结

    关于RabbitMQ是什么以及它的概念,不了解的可以先查看一下下面推荐的几篇博客 https://blog.csdn.net/whoamiyang/article/details/54954780 h ...

  5. RabbitMQ学习(一):RabbitMQ要点简介

    转载:http://blog.csdn.net/leixiaotao_java/article/details/78909760#t0 1.什么是RabbitMQ? RabbitMQ是由Erlang语 ...

  6. 官网英文版学习——RabbitMQ学习笔记(四)Work queues

    工作队列:把每个任务只发送给一个工作者. 上一篇我们是从一个指定的队列发送接收消息,在本文中,我们将创建一个工作队列,用于在多个工作者之间分配耗时的任务. 工作队列(即任务队列)背后的主要思想是避免立 ...

  7. 官网英文版学习——RabbitMQ学习笔记(一)认识RabbitMQ

    鉴于目前中文的RabbitMQ教程很缺,本博主虽然买了一本rabbitMQ的书,遗憾的是该书的代码用的不是java语言,看起来也有些不爽,且网友们不同人学习所写不同,本博主看的有些地方不太理想,为此本 ...

  8. RabbitMQ学习系列(三): C# 如何使用 RabbitMQ

    上一篇已经讲了Rabbitmq如何在Windows平台安装,还不了解如何安装的朋友,请看我前面几篇文章:RabbitMQ学习系列一:windows下安装RabbitMQ服务 , 今天就来聊聊 C# 实 ...

  9. RabbitMQ学习总结 第一篇:理论篇

    目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...

随机推荐

  1. 警告: The APR based Apache Tomcat Native library failed to load.

    警告: The APR based Apache Tomcat Native library failed to load. The error reported was [C:\apache-tom ...

  2. elasticsearch 分页查询实现方案

    1. from+size 实现分页 from表示从第几行开始,size表示查询多少条文档.from默认为0,size默认为10, 注意:size的大小不能超过index.max_result_wind ...

  3. Codeforces 570D TREE REQUESTS dfs序+树状数组

    链接 题解链接:点击打开链接 题意: 给定n个点的树.m个询问 以下n-1个数给出每一个点的父节点,1是root 每一个点有一个字母 以下n个小写字母给出每一个点的字母. 以下m行给出询问: 询问形如 ...

  4. JavaScript:让你彻底弄清offset

    ylbtech-JavaScript:让你彻底弄清offset 1.返回顶部 1. 很多初学者对于JavaScript中的offset.scroll.client一直弄不明白,虽然网上到处都可以看一张 ...

  5. T7315 yyy矩阵折叠(长)

    题目背景 全场基本暴零 题目描述 输入输出格式 输入格式: 如图 输出格式: 如图 输入输出样例 输入样例#1: 2 2 1 -2 3 -4 输出样例#1: 4 输入样例#2: 2 5 1 -2 -3 ...

  6. js通过经纬度计算两点之间的距离

    最近这几天在做地图的时候,获取到目的地经纬度和当前所在位置的经纬度,通过这几个参数,用js代码就能获取到这两点之间的直线距离: function (lat1, lng1, lat2, lng2) { ...

  7. Safari new Date() 兼容问题

    我的时间 var myTime = "2015-12-31 12:10:21"; 正常写法 var  newTime =  new Date(myTime); safari兼容写法 ...

  8. Javascript常用字符串判断函数

    [代码] 字符串,函数,Javascript,脚本100 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ...

  9. Ubuntu下快速配置Caffe

    Caffe安装 实际上在windows上安装过多次caffe了,无论是BLVC版本的还是Microsoft版本的,ubuntu的按照也进行过,这段时间在自己笔记本上 又折腾了下caffe安装,发现其实 ...

  10. markdown写作软件推荐

    最近发现了一款不错的编辑器,而且是全平台支持的.与其它一些 markdown 编辑器最大的不一样是——所见即所得,不再是一边源文件一遍预览的方式了. 总的说来 Typora 很赞,推荐一波. 点此前往 ...