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中 ...
随机推荐
- TCP使用注意事项总结
目录 发送或者接受数据过程中对端可能发生的情况汇总 本端TCP发送数据时对端进程已经崩溃 本端TCP发送数据时对端主机已经崩溃 本端TCP发送数据时对端主机已经关机 某个连接长时间没有数据流动 TCP ...
- vuejs切换导航条高亮路由高亮做法
我的GitHub前端经验总结,喜欢的话请点star✨✨Thanks.:https://github.com/liangfengbo/frontend-develop vuejs导航条高亮我的做法: 用 ...
- Keepalived双主模式配置流程
实验说明 1)keepalived 支持配置多个VRRP实例,每个实例对应一个业务 2)本次实验将实现 keepalived 的互为主备: 业务A:keepalived01为Master,keepal ...
- No.595-Big Countries-(LeetCode之Database篇)
数据库表 给出的数据库表如下,表名为World. +-----–+----+----+----–+-----+ | name | continent | area | ...
- Spring注解?啥玩意?
目录 基础概念:@Bean 和 @Configuration 使用AnnotationConfigApplicationContext 实例化Spring容器 简单的构造 使用register注册IO ...
- 美化Div的边框
CSS修饰Div边框 大部分时候,Div的边框真的做的太丑了,如果不用很多样式来修饰的话,它永远都是那么的突兀.作为一个后端开发,前端菜鸡,在没有设计和前端开发自己独自做项目的时候常常会遇到Div边框 ...
- 嵌入式物联网32 ARM linux 等创客学院学习视频共享给大家
大家手机号登录学习链接即可观看 有坛友说手机号登录不上 具体自测 http://www.makeru.com.cn/live/1392_303.html?s=60220走进嵌入式http:// ...
- 基于Dapper的开源Lambda扩展,且支持分库分表自动生成实体之基础介绍
LnskyDB LnskyDB是基于Dapper的Lambda扩展,支持按时间分库分表,也可以自定义分库分表方法.而且可以T4生成实体类免去手写实体类的烦恼. 文档地址: https://lining ...
- PAT L3-002: 堆栈(线段树)
https://www.patest.cn/contests/gplt/L3-002 题意:中文题意. 思路:因为所有数<=1e5,权值线段树维护每个数出现多少次,然后每次出栈入栈都更新权值就好 ...
- HDU 5791:Two(DP)
http://acm.hdu.edu.cn/showproblem.php?pid=5791 Two Problem Description Alice gets two sequences A ...