rabbitMQ_workQueue(二)

如果一个消费者需要处理一个耗时的任务,那么队列中其他的任务将被迫等待这个消费者处理完成,所以为了避免这样的情况,可以建立对个消费者进行工作。
本例中使用Thread.sleep()方法来假装消费者在处理一个耗时的任务。我们将把字符串中的点的个数作为其复杂度; 每个点都将占“工作”的一秒钟。例如,由Hello ...描述的假任务 将需要三秒钟。我们在启动这个程序的时候,设置java参数,如 java NewTask hello ...
定义一个NewTask.java:
package com.rabbitMQ; import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class NewTask { private final static String QUEUE_NAME = "work"; public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接rabbitMQ服务器的ip
factory.setHost("localhost");
// factory.setPort(5672);
// 创建一个连接到服务器的链接
Connection connection = factory.newConnection();
// 创建连接通道
Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null); String message = getMessage(args); channel.basicPublish("", "hello", null, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); channel.close(); connection.close();
} private static String getMessage(String[] strings) {
if (strings.length < )
return "Hello World!";
return joinStrings(strings, " ");
} private static String joinStrings(String[] strings, String delimiter) {
int length = strings.length;
if (length == )
return "";
StringBuilder words = new StringBuilder(strings[]);
for (int i = ; i < length; i++) {
words.append(delimiter).append(strings[i]);
}
return words.toString();
} }
定义一个消费工作者Worker.java:
package com.rabbitMQ; import java.io.IOException; 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; /**
* @author may
*
*/
public class Worker { private final static String QUEUE_NAME = "work"; public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
/**
* queue the name of the queue durable true if we are declaring a
* durable queue (the queue will survive a server restart) exclusive
* true if we are declaring an exclusive queue (restricted to this
* connection) autoDelete true if we are declaring an autodelete queue
* (server will delete it when no longer in use) arguments other
* properties (construction arguments) for the queue
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] 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(" [x] Received '" + message + "'");
try {
doWork(message);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
System.out.println(" [x] Done");
}
}
};
// 异步
/**
* queue the name of the queue 队列名 autoAck true if the server should
* consider messages acknowledged once delivered; false if the server
* should expect explicit acknowledgements callback an interface to the
* consumer object
* 可以通过以下命令去查看队列中没有返回ack的消息个数
* rabbitmqctl list_queues name messages_ready messages_unacknowledged
*/
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME, autoAck, consumer); // rabbitmqctl.bat list_queues 可以列出当前有多少个队列
} private static void doWork(String task) throws InterruptedException {
for (char ch : task.toCharArray()) {
if (ch == '.')
Thread.sleep();
}
} }
第70行的doWork方法对收到的消息字符串进行遍历,有多少个.就会休眠多少秒。以此来模拟耗时任务。
循环调度
启动两个work,然后多次启动NewTask,每次发送的字符串消息不同
在Linux环境下
export CP=.:amqp-client-4.0.2.jar:slf4j-api-1.7.21.jar:slf4j-simple-1.7.22.jar
# shell 3
java -cp $CP NewTask
# => First message.
java -cp $CP NewTask
# => Second message..
java -cp $CP NewTask
# => Third message...
java -cp $CP NewTask
# => Fourth message....
java -cp $CP NewTask
# => Fifth message.....
查看两个work的输出情况
java -cp $CP Worker
# => [*] Waiting for messages. To exit press CTRL+C
# => [x] Received 'First message.'
# => [x] Received 'Third message...'
# => [x] Received 'Fifth message.....'
java -cp $CP Worker
# => [*] Waiting for messages. To exit press CTRL+C
# => [x] Received 'Second message..'
# => [x] Received 'Fourth message....'
如果是在windows环境下,那么使用以下的命令
set CP=.;amqp-client-4.0.2.jar;slf4j-api-1.7.21.jar;slf4j-simple-1.7.22.jar
java -cp %CP% NewTask
....(把$CP改成%CP%,其他一样)
eclipse环境下右键run as 选择run configurations...

可以看出,默认情况下,RabbitMQ将按顺序将每条消息发送给下一个消费者。平均每个消费者将获得相同数量的消息。这种分发消息的方式叫做round-robin。
消息确认
执行任务可能需要几秒钟。你可能会想,如果一个消费者开始一个非常耗时的任务,并且只运行了一部分时间,就被异常终止了,比如down机。上面的代码,一旦RabbitMQ向客户发送消息,它立即将这个消息从内存中删除。在这种情况下,如果你杀死一个消费者,我们将丢失正在处理的消息。我们还会丢失所有发送给该特定消费者但尚未处理的消息。
但是我们不想失去任何任务。如果一个消费者终止,我们希望把这个任务交给另一个消费者。
为了确保消息永远不会丢失,RabbitMQ支持消息确认。从消费者发送一个确认信息(ack)告诉RabbitMQ已经收到,处理了特定的消息,并且RabbitMQ可以删除它。
如果消费者死机(其通道关闭,连接关闭或TCP连接丢失),而不发送确认信息,RabbitMQ将会知道消息未被完全处理需要重新排队。如果同时有其他消费者在线,则会迅速将其重新提供给另一个消费者。这样就可以确保没有消息丢失。
为了防止消费者意外终止造成消息的丢失,我们可以设置autoAck为false,禁止自动确认消息,我们应该在消息处理成功后手动确认消息。
channel.basicQos(1); // accept only one unack-ed message at a time (see below)
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(" [x] Received '" + message + "'");
try {
doWork(message);
} finally {
System.out.println(" [x] Done");
channel.basicAck(envelope.getDeliveryTag(), false);//任务处理完成后手动确认消息
}
}
};
boolean autoAck = false;
channel.basicConsume(TASK_QUEUE_NAME, autoAck, consumer);
使用这个代码,我们可以确定即使在处理消息时,使用CTRL + C杀死一个消费者,也不会丢失任何东西。消费者被杀死之后不久,所有未确认的消息将被重新发送。
忘记确认
如果忘记手动确认消息,那么这些消息将被堆积在队列中,会消耗内存。我们可以通过rabbitmqctl list_queues name messages_ready messages_unacknowledged命令来查看有多少消息未被确认的。第三个数字就表示相应队列中已被读取但未被正确处理的消息有多少个。
消息持久性
我们已经学会了如何确保即使消费者死亡,任务也不会丢失。但是如果RabbitMQ服务器停止,我们的任务仍然会丢失。
当RabbitMQ退出或崩溃时,它会忘记队列和消息,除非你不告诉它。需要两件事来确保消息不会丢失:我们需要将队列和消息标记为持久。
首先,我们需要确保RabbitMQ不会失去我们的队列。为了这样做,我们需要将其声明为持久的:
boolean durable = true ;
channel.queueDeclare(“hello”,durable,false,false,null);
虽然这个命令本身是正确的,但是在我们目前的设置中是不行的。这是因为我们已经定义了一个名为hello的非持久性队列。RabbitMQ不允许您重新定义具有不同参数的现有队列,并会向尝试执行此操作的任何程序返回错误。但是有一个快速的解决方法 - 让我们用不同的名称声明一个队列,例如task_queue:
boolean durable = true ;
channel.queueDeclare(“task_queue”,durable,false,false,null);
生产者和消费者的queueDeclare都要更改成持久性队列。
在这一点上,我们确信,即使RabbitMQ重新启动,task_queue队列也不会丢失。现在我们需要通过将MessageProperties(实现了BasicProperties)设置PERSISTENT_TEXT_PLAIN来标记我们的消息是哪种类型的持久化消息。
import com.rabbitmq.client.MessageProperties; channel.basicPublish(“”,“task_queue”,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
公平分派
前面代码实现的消息队列是平均地将任务分发给每个消费者,如果此时有其中一个消费者处理消息非常的耗时,而另外的一个消费者可以很快地处理完消息,这个时候就出问题了,如果队列中存在三条消息,rabbitMQ将第一条给了耗时的消费者,把第二条给了不耗时的消费者,最后把第三条给了耗时的消费者,这个时候,耗时的消费者一直在忙碌,而不耗时的消费者没事干。
这是因为当消息进入队列时,RabbitMQ只会盲目地平均分派消息,不会检查被分派任务的消费者是否已经将消息处理完成。

为了避免这种问题,在消费者的代码中设置以下代码。消费者告诉RabbitMQ不要一次性给我多个消息。或者换句话说,在处理并确认前一个消息之前,不要向我发送新消息,你应该将消息发给不忙的其他消费者。
int prefetchCount = 1 ;
channel.basicQos(prefetchCount);
注意队列大小
如果所有的消费者都忙,队列会被填满。这个时候你应该增加新的消费者或者其他的方式去消耗队列中的消息。
NewTask.java类的最终代码:
package com.rabbitMQ; import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties; public class NewTask_fairDispatch { private final static String QUEUE_NAME = "task_queue"; public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置连接rabbitMQ服务器的ip
factory.setHost("localhost");
// factory.setPort(5672);
// 创建一个连接到服务器的链接
Connection connection = factory.newConnection();
// 创建连接通道
Channel channel = connection.createChannel(); boolean durable = true;
channel.queueDeclare(QUEUE_NAME, durable, false, false, null); String message = getMessage(args); //将队列中的信息定义为可持久化的纯文本
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); channel.close(); connection.close();
} private static String getMessage(String[] strings) {
if (strings.length < )
return "Hello World!";
return joinStrings(strings, " ");
} private static String joinStrings(String[] strings, String delimiter) {
int length = strings.length;
if (length == )
return "";
StringBuilder words = new StringBuilder(strings[]);
for (int i = ; i < length; i++) {
words.append(delimiter).append(strings[i]);
}
return words.toString();
} }
Worker.java:
package com.rabbitMQ; import java.io.IOException; 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; /**
* @author may
*
*/
public class Worker_fairDispatch { private final static String QUEUE_NAME = "hello"; public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
int prefetchCount = ;
//服务传送的最大消息数量,0表示不限制
channel.basicQos(prefetchCount); boolean durable = true;
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
System.out.println(" [*] 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(" [x] Received '" + message + "'");
try {
doWork(message);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
System.out.println(" [x] Done");
//确认消息,表示任务已经处理完成
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
}; boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer); } private static void doWork(String task) throws InterruptedException {
for (char ch : task.toCharArray()) {
if (ch == '.')
Thread.sleep();
}
} }
rabbitMQ_workQueue(二)的更多相关文章
- 【小程序分享篇 二 】web在线踢人小程序,维持用户只能在一个台电脑持登录状态
最近离职了, 突然记起来还一个小功能没做, 想想也挺简单,留下代码和思路给同事做个参考. 换工作心里挺忐忑, 对未来也充满了憧憬与担忧.(虽然已是老人, 换了N次工作了,但每次心里都和忐忑). 写写代 ...
- 前端开发中SEO的十二条总结
一. 合理使用title, description, keywords二. 合理使用h1 - h6, h1标签的权重很高, 注意使用频率三. 列表代码使用ul, 重要文字使用strong标签四. 图片 ...
- 【疯狂造轮子-iOS】JSON转Model系列之二
[疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- 谈谈一些有趣的CSS题目(十二)-- 你该知道的字体 font-family
开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...
- MIP改造常见问题二十问
在MIP推出后,我们收到了很多站长的疑问和顾虑.我们将所有疑问和顾虑归纳为以下二十个问题,希望对大家理解 MIP 有帮助. 1.MIP 化后对其他搜索引擎抓取收录以及 SEO 的影响如何? 答:在原页 ...
- 如何一步一步用DDD设计一个电商网站(二)—— 项目架构
阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...
- ASP.NET Core 之 Identity 入门(二)
前言 在 上篇文章 中讲了关于 Identity 需要了解的单词以及相对应的几个知识点,并且知道了Identity处在整个登入流程中的位置,本篇主要是在 .NET 整个认证系统中比较重要的一个环节,就 ...
- MVVM模式和在WPF中的实现(二)数据绑定
MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...
随机推荐
- 《阿里巴巴Java开发手册》改名《Java开发手册》,涵盖史无前例的三大升级
2019.06.19 <阿里巴巴Java开发手册>时隔一年,发布更新1.5.0华山版.同时,将更名为<Java开发手册>,涵盖史无前例的三大升级 1)鉴于本手册是社区开发者集体 ...
- 写在Logg SAP项目上线之际
根据大环境大行业的惯用做法,公司建立Logg品牌是在意料之中.毫无意外的,Logg也要上到SAP系统中. 其实按它的业务模式来说上SAP系统并不困难,早在几年前就已经有做过了.无非就是接单不生产,外包 ...
- 关于C# 异步
关于C# 异步操作整理 按照个人的理解, 写一个接口用Task异步操作(态度:接受并且学习,您提出宝贵的经验与理解,我会认真学习): 在主线程中调用异步方法,如果主线程依赖异步方法的返回值那么你一定会 ...
- PATB 1018. 锤子剪刀布
时间限制 100 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 大家应该都会玩“锤子剪刀布”的游戏:两人同时给出手势,胜负规则如图 ...
- C# 死锁 TaskCompletionSource
在异步转同步时,使用不当容易造成死锁(程序卡死) 看如下案例: 有一个异步方法 private static async Task TestAsync() { Debug.WriteLine(&quo ...
- code forces 1176 D. Recover it!
原题链接:https://codeforces.com/contest/1176/problem/D 题目大意是 两个个数列 a , b 相同 ,如果 ai 是素数,那么b数列里添加上第ai个素数(2 ...
- 嵊州D1T3 睡美人航班
嵊州D1T3 睡美人航班 不知不觉中,我对她的爱意已经达到了 n. 是这样子的,第 1 分钟,我对她的爱意值是 (1, 1). 假如当第 x 分钟时我对她的爱意值是 (a, b),那么第 x + 1 ...
- python 的一些小项目
1.在线教育平台(视频播放) 2.仿微信网页版(语音.视频.文字聊天) 3.高德API + Python 解决租房问题 4.仿知乎 5.Django打造文件分享系统.文件管理.搜索引擎(仿云盘) 6. ...
- Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析
概述 在SpringMVC的使用时,往往会用到@RequestBody和@ResponseBody两个注解,尤其是处理ajax请求必然要使用@ResponseBody注解.这两个注解对应着Contro ...
- 视频4K技术的解读
前几年4K技术就已经有人提及,今年更是成了一个非常热门的词汇,而且4K技术已经普遍应用于各类终端,如电视机.机顶盒.手机等.那么如何来理解4K这个东东呢?今天博主就谈谈自己对4K技术的认识. 博主认为 ...
