前面介绍了队列接收和发送消息,这篇将学习如何创建一个工作队列来处理在多个消费者之间分配耗时的任务。工作队列(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)——工作队列的更多相关文章

  1. RabbitMQ入门:工作队列(Work Queue)

    在上一篇博客<RabbitMQ入门:Hello RabbitMQ 代码实例>中,我们通过指定的队列发送和接收消息,代码还算是比较简单的. 假设有这一些比较耗时的任务,按照上一次的那种方式, ...

  2. RabbitMQ入门教程——工作队列

    什么是工作队列 工作队列是为了避免等待一些占用大量资源或时间操作的一种处理方式.我们把任务封装为消息发送到队列中,消费者在后台不停的取出任务并且执行.当运行了多个消费者工作进程时,队列中的任务将会在每 ...

  3. RabbitMQ入门:总结

    随着上一篇博文的发布,RabbitMQ的基础内容我也学习完了,RabbitMQ入门系列的博客跟着收官了,以后有机会的话再写一些在实战中的应用分享,多谢大家一直以来的支持和认可. RabbitMQ入门系 ...

  4. RabbitMQ入门:发布/订阅(Publish/Subscribe)

    在前面的两篇博客中 RabbitMQ入门:Hello RabbitMQ 代码实例 RabbitMQ入门:工作队列(Work Queue) 遇到的实例都是一个消息只发送给一个消费者(工作者),他们的消息 ...

  5. RabbitMQ入门(6)——远程过程调用(RPC)

    在RabbitMQ入门(2)--工作队列中,我们学习了如何使用工作队列处理在多个工作者之间分配耗时任务.如果我们需要运行远程主机上的某个方法并等待结果怎么办呢?这种模式就是常说的远程过程调用(Remo ...

  6. RabbitMQ入门(3)——发布/订阅(Publish/Subscribe)

    在上一篇RabbitMQ入门(2)--工作队列中,有一个默认的前提:每个任务都只发送到一个工作人员.这一篇将介绍发送一个消息到多个消费者.这种模式称为发布/订阅(Publish/Subscribe). ...

  7. RabbitMQ入门到进阶(Spring整合RabbitMQ&SpringBoot整合RabbitMQ)

    1.MQ简介 MQ 全称为 Message Queue,是在消息的传输过程中保存消息的容器.多用于分布式系统 之间进行通信. 2.为什么要用 MQ 1.流量消峰 没使用MQ 使用了MQ 2.应用解耦 ...

  8. RabbitMQ入门教程(四):工作队列(Work Queues)

    原文:RabbitMQ入门教程(四):工作队列(Work Queues) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https:/ ...

  9. RabbitMQ入门(二)工作队列

      在文章RabbitMQ入门(一)之Hello World,我们编写程序通过指定的队列来发送和接受消息.在本文中,我们将会创建工作队列(Work Queue),通过多个workers来分配耗时任务. ...

随机推荐

  1. jquery Chosen使用

    1,首先去http://harvesthq.github.io/chosen/下载插件. 2,在网页中加入下面的文件. <link rel="stylesheet" href ...

  2. idea中导入本地jar包

    idea中有时需要从本地导入jar包. 1:file>projectstructure 或者右键项目 open module setting, 选择librarys,然后点击+号,选择要导入的语 ...

  3. sap 动态字段的使用

    1.sap中的动态字段其实类似c#里面的指针,都是指向的地址. 只是写法不同,sap 中的field-symbols就是类似指针. , b TYPE i. FIELD-SYMBOLS <fs&g ...

  4. Latch free等待事件

    Latch free等待事件的三个参数:p1-latch的地址:p2-latch编号:p3-请求次数.从oracle10g起,latchfree不再包含所有的latch等待,有些latch等待可能表现 ...

  5. 20165324 2017-2018-2 《Java程序设计》课程总结

    20165324 2017-2018-2 <Java程序设计>课程总结 每周作业链接汇总 预备作业1:20165324 我期望的师生关系 预备作业2:20165324 学习基础与C语言学习 ...

  6. 20165324 Java实验一

    20165324 实验一 一.实验报告封面 课程:Java程序设计 班级:1653班 姓名:何春江 学号:20165324 指导教师:娄嘉鹏 实验日期:2018年4月2日 实验时间:13:45 - 1 ...

  7. The Cheap KD 10 is my best shoe yet

    10 years of anything is fairly huge Cheap KD 10, but adding something as great as Flyknit causes it ...

  8. mac 安装Sequel Pro

    安装命令如下 Install the App Press Command+Space and type Terminal and press enter/return key. Run in Term ...

  9. javascript 类型 内存 对象

    var box =0 function test() { alert(box) //全局 }

  10. hbase(一)

    1.hbase安装参考 http://blog.csdn.net/wild46cat/article/details/53214159 2.遇到的问题: ERROR: The node /hbase ...