RabbitMQ入门(2)——工作队列
前面介绍了队列接收和发送消息,这篇将学习如何创建一个工作队列来处理在多个消费者之间分配耗时的任务。工作队列(work queue),又称任务队列(task queue)。
工作队列的目的是为了避免立刻执行资源密集型任务、减少等待时间。将消息发送到队列,工作进程在后台从队列取出任务并处理。
准备
通过Thread.sleep()来模拟耗时的任务,通过在消息的末尾添加"."来表示处理时间,例如,Hello...表示耗时3秒。
发送端
NewTask.java:
package com.xxyh.rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class NewTask {
private static final String WORK_NAME = "task_queue";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
boolean durable = true;
channel.queueDeclare(WORK_NAME, durable, false, false, null);
// 发送10条记录,每次在后面添加"."
for (int i = 0; i < 10; i++) {
String dot = "";
for (int j = 0; j <= i; j++) {
dot += ".";
}
String message = "work queue " + dot + dot.length();
channel.basicPublish("", WORK_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes("utf-8"));
System.out.println(Thread.currentThread().getName() + "发送消息:" + message);
}
channel.close();
connection.close();
}
}
接收端
Work.java:
package com.xxyh.rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Work {
private static final String WORK_QUEUE = "task_queue";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
boolean durable = true;
channel.queueDeclare(WORK_QUEUE, durable, false, false, null);
// 设置同一个消费者在同一时间只能消费一条消息
channel.basicQos(1);
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(Thread.currentThread().getName() + "接收消息:" + message);
try {
doWork(message);
System.out.println("消息接收完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 确认消息已经收到
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 取消autoAck
boolean autoAck = false;
channel.basicConsume(WORK_QUEUE, autoAck, consumer);
}
private static void doWork(String task) throws InterruptedException {
for (char ch : task.toCharArray()) {
if (ch == '.') {
Thread.sleep(1000);
}
}
}
}
Round-robin分发
使用任务队列的好处就是容易处理并发工作。如果积累了大量的工作,只需要增加工作者就可以了。对于上面的示例代码,可以启动两个Work,然后启动NewTask,执行结果如下:
Work1:
pool-1-thread-4接收消息:work queue .1
消息接收完毕
pool-1-thread-5接收消息:work queue ...3
消息接收完毕
pool-1-thread-6接收消息:work queue .....5
消息接收完毕
pool-1-thread-7接收消息:work queue .......7
消息接收完毕
pool-1-thread-8接收消息:work queue .........9
消息接收完毕
Work2:
pool-1-thread-4接收消息:work queue ..2
消息接收完毕
pool-1-thread-5接收消息:work queue ....4
消息接收完毕
pool-1-thread-6接收消息:work queue ......6
消息接收完毕
pool-1-thread-7接收消息:work queue ........8
消息接收完毕
pool-1-thread-8接收消息:work queue ..........10
消息接收完毕
NewTask:
main发送消息:work queue .1
main发送消息:work queue ..2
main发送消息:work queue ...3
main发送消息:work queue ....4
main发送消息:work queue .....5
main发送消息:work queue ......6
main发送消息:work queue .......7
main发送消息:work queue ........8
main发送消息:work queue .........9
main发送消息:work queue ..........10
默认情况下,RabbitMQ会依次发送消息到下一个队列。一般情况下,每个消费者会收到数量大致相同的消息。这种分发消息的方法称为round-robin。
消息确认(Message acknowledgment)
处理一个任务可能需要几秒钟的时间。如果一个消费者在执行过程中中断可能导致消息的丢失,显然这并不是我们希望的,RabbitMQ提供了消息确认机制,让消费者反馈已经收到消息的信息,然后RabbitMQ就可以自由产出这条消息了。
如果消费者中断了,没有发送确认消息,RabbitMQ会认为消息发送失败并将消息重新加入队列。如果同时有其他消费者在工作,它会马上重新发送该消息到另一个消费者。这种方式可以保证消息不丢失。
消息确认机制默认是开启的:
// 打开确认
boolean autoAck = false;
channel.basicConsume(queueName, autoAck, consumer);
消息持久化(Message durability)
上面的方式可以保证某个消费者中断时,消息不会丢失。然而,如果RabbitmqMQ中断或崩溃,它是不会记住队列和消息的。为了实现消息不丢失,需要将队列和消息持久化。首先将队列持久化:
boolean durable = true;
channel.queueDeclare("hello", durable, false, false, null);
如果一个已经存在的队列没有被设置持久化(durable=false)的,是不能更改它的参数的。另外,必须在消息的发送端、可接收端同时修改队列的声明。
上面的设置保证了即使RabbitMQ重启,队列也不丢失。但是并不能保证消息一定能发送到接收端,还需要将消息持久化:
channel.basicPublish("", QUEUE_NAME,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes("utf-8"));
公平分发
在某些情况下,分发的效果并不让人满意。例如,在两个消费者的情况下,奇数任务多而偶数任务少,可能一个消费队列一直处于繁忙状态,而另一个几乎处于闲置状态。RabbitMQ并不知晓这一情况,它只负责分发队列里的消息,不考虑消息未确认的情况。它只是盲目地将第n个消息发送给第n个消费者。
channel.basicQos(1);
这行代码的作用是告诉RabbitMQ,不要在同一时间给同一个消费者超过1条消息。
RabbitMQ入门(2)——工作队列的更多相关文章
- RabbitMQ入门:工作队列(Work Queue)
在上一篇博客<RabbitMQ入门:Hello RabbitMQ 代码实例>中,我们通过指定的队列发送和接收消息,代码还算是比较简单的. 假设有这一些比较耗时的任务,按照上一次的那种方式, ...
- RabbitMQ入门教程——工作队列
什么是工作队列 工作队列是为了避免等待一些占用大量资源或时间操作的一种处理方式.我们把任务封装为消息发送到队列中,消费者在后台不停的取出任务并且执行.当运行了多个消费者工作进程时,队列中的任务将会在每 ...
- RabbitMQ入门:总结
随着上一篇博文的发布,RabbitMQ的基础内容我也学习完了,RabbitMQ入门系列的博客跟着收官了,以后有机会的话再写一些在实战中的应用分享,多谢大家一直以来的支持和认可. RabbitMQ入门系 ...
- RabbitMQ入门:发布/订阅(Publish/Subscribe)
在前面的两篇博客中 RabbitMQ入门:Hello RabbitMQ 代码实例 RabbitMQ入门:工作队列(Work Queue) 遇到的实例都是一个消息只发送给一个消费者(工作者),他们的消息 ...
- RabbitMQ入门(6)——远程过程调用(RPC)
在RabbitMQ入门(2)--工作队列中,我们学习了如何使用工作队列处理在多个工作者之间分配耗时任务.如果我们需要运行远程主机上的某个方法并等待结果怎么办呢?这种模式就是常说的远程过程调用(Remo ...
- RabbitMQ入门(3)——发布/订阅(Publish/Subscribe)
在上一篇RabbitMQ入门(2)--工作队列中,有一个默认的前提:每个任务都只发送到一个工作人员.这一篇将介绍发送一个消息到多个消费者.这种模式称为发布/订阅(Publish/Subscribe). ...
- RabbitMQ入门到进阶(Spring整合RabbitMQ&SpringBoot整合RabbitMQ)
1.MQ简介 MQ 全称为 Message Queue,是在消息的传输过程中保存消息的容器.多用于分布式系统 之间进行通信. 2.为什么要用 MQ 1.流量消峰 没使用MQ 使用了MQ 2.应用解耦 ...
- RabbitMQ入门教程(四):工作队列(Work Queues)
原文:RabbitMQ入门教程(四):工作队列(Work Queues) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https:/ ...
- RabbitMQ入门(二)工作队列
在文章RabbitMQ入门(一)之Hello World,我们编写程序通过指定的队列来发送和接受消息.在本文中,我们将会创建工作队列(Work Queue),通过多个workers来分配耗时任务. ...
随机推荐
- Divisibility by Eight---cf550C(被8整除 暴力)
题目链接:http://codeforces.com/problemset/problem/550/C 题意是给你一个不操过100位的数,问删除m位之后,问剩下的数不改变顺序能被8整除的数存在不存在: ...
- 向git服务器添加shh公钥
步骤一,从客户端获得 SSH 公钥 为了使客户端可以向 Git 服务器提供 SSH 公钥,首先要确认客户端拥有公钥.SSH 的密钥存储在 ~/.ssh/ 目录下,下面我们查看一下这里面都有哪些文件: ...
- Linux内核调试技术——jprobe使用与实现
前一篇博文介绍了kprobes的原理与kprobe的使用与实现方式,本文介绍kprobes中的另外一种探測技术jprobe.它基于kprobe实现,不能在函数的任何位置插入探測点,仅仅能在函数的入口处 ...
- 深入理解Oracle调试事件:10046事件详解
10046事件是SQL_TRACE的扩展,被戏称为"吃了兴奋剂的SQL_TRACE" 有效的追踪级别: ① 0级:SQL_TRACE=FASL ...
- Ajax跨域请求 同源策略与Jsonp
同源策略 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响.可以说Web是构建在同源策略基础之上的 ...
- UVALive - 7740 Coding Contest 2016 青岛区域赛 (费用流)
题意:每个点i有\(s_i\)个人和\(b_i\)份食物,每个人都要找到一份食物.现在有M条有向边,从点i到点j,容量为c,第一次走过不要紧,从第二次开始就要承担\(p(0<p<1)\)的 ...
- Linux树莓派中/etc/rc.local不执行的问题
最近研究在树莓派中嵌入式开发java程序,并打算和Salesforce进行通信.需要开发一个java的web server,不想弄那么复杂,于是打算在linux系统中/etc/rc.local写想要执 ...
- 滑块控件CCControlSlider
#include "cocos-ext.h" //包含头文件 using namespace cocos2d::extension;//引用命名空间 /** * 创建CCContr ...
- Web前端页面的浏览器兼容性测试心得(一)搭建测试用本地静态服务器
自从实习以来,我体验了由一个人编程再到多人协作编程的转换.同时也接触了很多新鲜事物,学到了很多东西.工作时,常常需要多个人协同测试一张H5页面(PC.手机.平板等跨设备测试),也会经常把页面给PM或老 ...
- 使用.NET Core和Vue搭建WebSocket聊天室
博客地址是:https://qinyuanpei.github.io. WebSocket是HTML5标准中的一部分,从Socket这个字眼我们就可以知道,这是一种网络通信协议.WebSocket是 ...