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

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

本例中使用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. 节能减排到底如何----google earth engine 告诉你!!

    (First,再次严谨说明,本人成果未经允许,切勿发表到相关学术期刊,如果有技术交流,qq1044625113,顺便打个广告,兼职GEE开发,欢迎联系!) 终于过了严寒的冬天,2017年的冬天中国南方 ...

  2. 智能小程序关于Filter过滤器的简单使用

    <filter module="swan"> export default { imgurl: (imgUrl) => { var imgurlprefix = ...

  3. ZooKeeper学习之路(五)—— ACL权限控制

    一.前言 为了避免存储在Zookeeper上的数据被其他程序或者人为误修改,Zookeeper提供了ACL(Access Control Lists)进行权限控制.只有拥有对应权限的用户才可以对节点进 ...

  4. 系统学习 Java IO (十二)----数据流和对象流

    目录:系统学习 Java IO---- 目录,概览 DataInputStream/DataOutputStream 允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型. 要想使用 ...

  5. vscode+vagrant+xdebug调试

    配置: { "name": "Listen for XDebug", "type": "php", "requ ...

  6. javaweb中Servlet配置到Tomcat

    1.tomcat容器来运行Servlet程序 在javase中,都是在控制台中运行java代码,而且提供了一个main方法,代码运行的入口.在javaee中,想要运行java代码,不是通过控制台程序来 ...

  7. Centos7.6 双网卡,修改默认路由

    centos修改默认路由 需求:服务器有两个网卡,一个是内网网卡,一个是外网网卡,但是默认的路由是内网: route -n 列出所有路由: 删除默认的路由: route del default gw ...

  8. 驰骋工作流引擎-CCMobile与安卓、IOS集成过程中的问题与解决方案

    CCMobile与安卓.IOS集成过程中的问题与解决方案 前言: CCMobile(2019版本)是CCFlow&JFlow 的一款移动端审批的产品.系统基于mui框架开发,是一款可以兼容An ...

  9. scrapy基础知识之 Scrapy 和 scrapy-redis的区别:

    Scrapy 和 scrapy-redis的区别 Scrapy 是一个通用的爬虫框架,但是不支持分布式,Scrapy-redis是为了更方便地实现Scrapy分布式爬取,而提供了一些以redis为基础 ...

  10. Python 的8个关键要素

    1.数据类型 2.对象引用 3.组合数据类型 4.逻辑操作符 5.控制流语句 6.算术操作符 7.输入/输出 8.函数的创建与调用