生产者发送多个消息到队列,由多个消费者消费。
 

如果一个消费者需要处理一个耗时的任务,那么队列中其他的任务将被迫等待这个消费者处理完成,所以为了避免这样的情况,可以建立对个消费者进行工作。

本例中使用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(二)的更多相关文章

  1. 【小程序分享篇 二 】web在线踢人小程序,维持用户只能在一个台电脑持登录状态

    最近离职了, 突然记起来还一个小功能没做, 想想也挺简单,留下代码和思路给同事做个参考. 换工作心里挺忐忑, 对未来也充满了憧憬与担忧.(虽然已是老人, 换了N次工作了,但每次心里都和忐忑). 写写代 ...

  2. 前端开发中SEO的十二条总结

    一. 合理使用title, description, keywords二. 合理使用h1 - h6, h1标签的权重很高, 注意使用频率三. 列表代码使用ul, 重要文字使用strong标签四. 图片 ...

  3. 【疯狂造轮子-iOS】JSON转Model系列之二

    [疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  5. 谈谈一些有趣的CSS题目(十二)-- 你该知道的字体 font-family

    开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...

  6. MIP改造常见问题二十问

    在MIP推出后,我们收到了很多站长的疑问和顾虑.我们将所有疑问和顾虑归纳为以下二十个问题,希望对大家理解 MIP 有帮助. 1.MIP 化后对其他搜索引擎抓取收录以及 SEO 的影响如何? 答:在原页 ...

  7. 如何一步一步用DDD设计一个电商网站(二)—— 项目架构

    阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...

  8. ASP.NET Core 之 Identity 入门(二)

    前言 在 上篇文章 中讲了关于 Identity 需要了解的单词以及相对应的几个知识点,并且知道了Identity处在整个登入流程中的位置,本篇主要是在 .NET 整个认证系统中比较重要的一个环节,就 ...

  9. MVVM模式和在WPF中的实现(二)数据绑定

    MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

随机推荐

  1. 如何使用Vue.js来搭建一个后台管理系统

    目录 使用的技术 基础但不好版 1.初始化项目 2.实现初始页内容自定义 3.使用路由 原始代码 自建页面 修改路由 4.测试路由跳转 补充 子路由版 嵌套router-view 定义子路由 修改菜单 ...

  2. 深入V8引擎-AST(1)

    没办法了,开坑吧,接下来的几篇会讲述JavaScript字符串源码在v8中转换成AST(抽象语法树)的过程. JS代码在V8的解析只有简单的几步,其中第一步就是将源字符串转换为抽象语法树,非常类似于v ...

  3. MCtalk对话学吧课堂:真正的K12在线教育才刚刚开始

    课堂之外的在线教育已经被大部分家庭所熟知,既涌现出了VIPKID等行业独角兽,也有大量致力于科技改变教育的新兴机构获得了快速成长.成立于2014年的学吧课堂就是专注在K12在线教育领域的创新机构,他们 ...

  4. java8 异步api、循环、日期

    java8 异步api.循环.日期 转载请注明出处:https://www.cnblogs.com/funnyzpc/p/10801470.html 异步api 对于多任务耗时的业务场景,一般我们会用 ...

  5. 透视BlueStore存储结构:如何根据文件名从裸盘提取文件内容

    在FileStore下,用户文件经过切分对象块后最终存放在了单机文件系统(xfs .ext4等)中,我们可以较容易地找到这些对象块对应的文件,然后提取这些对象块文件后组装成用户文件.然而,BlueSt ...

  6. python数据库-MySQL数据库的增删改查基本操作(49)

    一.数据库基础 表 table:数据是保存在表内,保存在一个表内的数据,应该具有相同的数据格式 行:行用于记录数据 记录:行内的数据 列:列用于规定数据格式 字段:数据的某个列 主键:唯一地标识表中的 ...

  7. 2018.12.1 万圣节的小L

    我回来啦 试题描述 今天是万圣节,小L同学开始了一年一度的讨要糖果游戏,但是在刚刚过去的比赛中小有成就的他打算给自己增加一点难度:如果没有讨到每一家的糖果就算输. 已知小L共有n(n不大于10000) ...

  8. 如何使用 Docker 安装 Jenkins

    说在前面 本篇内容非常简单,仅讲述了如何快速在 Docker 上部署一个 Jenkins 实例,不涉及其他. 本文实验环境: 操作系统:Centos 7.5 Docker Version:18.09. ...

  9. 深入理解C#的装箱和拆箱

    个人理解(本质): 封箱是把值类型转换为引用类型 拆箱是把引用类型转换为值类型 封箱是把值类型转换为System.Object类型,或者转换为由值类型实现的接口类型: 例如: struct Mystr ...

  10. Django rest framework(3)----节流

    目录 Django组件库之(一) APIView源码 Django restframework (1) ----认证 Django rest framework(2)----权限 Django res ...