原文:RabbitMQ入门教程(四):工作队列(Work Queues)

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。

分享一个朋友的人工智能教程。比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看。

工作队列

使用工作队列实现任务分发的功能,一个队列的优点就是很容易处理并行化的工作能力,但是如果我们积累了大量的工作,我们就需要更多的工作者来处理,这里就要采用分布机制了。

本示例主要演示显示的功能:

  • 定义交换机
  • 多个消费者同时订阅一个队列
  • 模式采用手动应答


生产者

public class Publisher {
@Test
public void testBasicPublish() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(AMQP.PROTOCOL.PORT);
factory.setUsername("mengday");
factory.setPassword("mengday"); Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); String QUEUE_NAME = "queue.work";
String ROUTING_KEY = "task";
String EXCHANGE_NAME = "amqp.rabbitmq.work";
// 声明队里
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 声明交换机:指定交换机的名称和类型(direct)
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY); // 循环发布多条消息
for (int i = 0; i < 10; i++){
String message = "Hello RabbitMQ " + i;
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, message.getBytes("UTF-8"));
} // 关闭资源
channel.close();
connection.close();
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

消费者

public class Consumer1 {
@Test
public void testBasicConsumer1() throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(AMQP.PROTOCOL.PORT); // 5672
factory.setUsername("mengday");
factory.setPassword("mengday"); Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
// 设置每次从队列获取消息的数量
channel.basicQos(1); // 声明一个队列
String QUEUE_NAME = "queue.work";
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
System.out.println("Consumer Wating Receive Message"); 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"); try {
doWork(message);
// 手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (Exception e){
e.printStackTrace();
}
}
}; // 订阅消息, false: 表示手动应答,需要手动调用basicAck()来应答
channel.basicConsume(QUEUE_NAME, false, consumer); // 睡眠是为了不让程序立即结束,这样还有机会获取第二条消息
Thread.sleep(1000000);
} private void doWork(String message) throws Exception{
System.out.println(" [C] Received '" + message + "', 处理业务中...");
// 模仿消费者处理业务的时间,也让其他消费者有机会获取到消息,实际开发中不需要,这里只是模拟
Thread.sleep(1000);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

Consumer2 的代码和Consumer1完全一样


运行结果

为了能让两个消费者均分消息,需要先启动消费者,最后再启动生产者


交换机的直连接类型direct

交换机的类型用于交换机如何将消息路由到哪些队列中去,在绑定队列时channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY)会将队列名、交换机名称、路由键作为一条记录插入到数据库中,当发布消息时channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, message.getBytes(“UTF-8”))会指定交换机名称和路由键,如果将交换机名+路由键 作为联合主键,根据这个联合组件就能查到队列名(EXCHANGE_NAME + ROUTING_KEY)—》QUEUE_NAME, 从而将消息发送到指定的队列,直连接的规则就是 绑定的路由键和发布消息的路由键必须完全相等,完全相等,完全相等,重要的事情说三遍, 才能路由到关联的队列上,用java代码表示 routingKeyForPublishMessage.equals(routingKeyForQueueBind),

直连接就是完全相等,像其他类型可以类似于正则表达式或是数据库中的like的模糊匹配,只要模糊匹配成功就可以将消息路由到指定的队列中。

适用场景:有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列。


临时队列(Temporary queues)

Java中我们可以使用queueDeclare()方法,不传递任何参数,来创建一个非持久的、唯一的、自动删除的队列且队列名称由服务器随机产生。

String queueName = channel.queueDeclare().getQueue();

一般情况这个名称与amq.gen-JzTY20BRgKO-HjmUJj0wLg 类似。

public AMQP.Queue.DeclareOk queueDeclare() throws IOException {
// 底层会随机产生一个队列名称
return queueDeclare("", false, true, true, null);
}
  • 1
  • 2
  • 3
  • 4

轮询分发(Round-robin)

在默认情况下,RabbitMQ将逐个发送消息到在序列中的下一个消费者(而不考虑每个任务的时长等等,且是提前一次性分配,并非一个一个的分配)。平均每个消费者获得相同数量的消息。这种方式分发消息机制称为Round-Robin(轮询)。

当消息进入队列,RabbitMQ就会分派消息。它不看消费者的应答的数目,也不关心消费者处理消息的能力,只是盲目的暴力的将第n条消息发给第n个消费者。


公平转发(Fair dispatch)

当工作队列中有两个消费者中,可以看到消费者1收到的消息都是偶数条,消费者1都是奇数条,假如偶数条的消息处理比较耗时,奇数条的消息处理很快耗时短,当有多条消息在队列中,队列一下子就把所有奇数条消息推送给消费者1,把所有偶数条消息推送给消费者2,由于消费者1处理的消息不叫耗时,消费者2处理比较快,很可能出现当消费者1才处理几条的时,消费者2就已经完全处理了,这样消费者2就处理空闲状态,而消费者1却忙的跟狗似的。为了解决这种现象,让干的快的干完了帮助干的慢的分担点任务,RabbitMQ采用限制消费者一次从队列中获取消息的条数,而不是一下子把满足条件的消息都推送个某个消费者,通过使用channel.basicQos(1)告诉消费者一次只能从队列中预先获取一条(预提取数量prefetchCount),处理完了再获取另一条,这样其他消息仍然在队列中,还没有被分发出去,这样就会造成处理消息慢的继续处理当前消息,处理消息的快的由于一次只能从队列中获取一条,处理完继续从队列中获取,这样就会出现能者多劳,大家谁都不会闲着。使用公平转发这种方式支持动态添加消费者,比如队列中的消息很多,两个消费者处理不过来,需要再增加消费者来处理,由于消息还在队列中,还没有被分发出去,这样再增加消费者,消费者就能马上从队列中获取消息,立即投入进来工作。

将上面示例的代码消费者2中doWork的睡眠时间改为500毫秒,Thread.sleep(500);这样消费者1处理消息的时间是1000毫秒,比消费者2慢了一半,消费者1就处理的条数就比消费者2的就少


消息持久化(Message durability)

默认队列和消息都是放在内存中的,当RabbitMQ退出或者崩溃,将会丢失队列和消息。为了保证即使RabbitMQ崩溃也不会丢失消息,我们必须把“队列”和“消息”设为持久化,当队列和消息持久化以后即使RabbitMQ崩溃,消息还存在数据库中,当RabbitMQ再次启动的时候,队列和消息仍然还在。

// 队列持久化
boolean durable = true;
channel.queueDeclare("hello", durable, false, false, null); // 消息持久化 方式一
channel.basicPublish("", "key", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8")); // 消息持久化 方式二
AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder();
properties.deliveryMode(2); // 设置消息是否持久化,1: 非持久化 2:持久化
channel.basicPublish("", "key", properties.build(), message.getBytes("UTF-8"));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

什么时候使用持久化?首先你需要分析并测试性能需求,通常关键的消息一般都使用持久化。


分享一个朋友的人工智能教程。比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看。

我的微信公众号:

RabbitMQ入门教程(四):工作队列(Work Queues)的更多相关文章

  1. RabbitMQ入门教程(十四):RabbitMQ单机集群搭建

    原文:RabbitMQ入门教程(十四):RabbitMQ单机集群搭建 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://b ...

  2. RabbitMQ入门教程(十六):RabbitMQ与Spring集成

    原文:RabbitMQ入门教程(十六):RabbitMQ与Spring集成 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https: ...

  3. RabbitMQ入门教程(三):Hello World

    原文:RabbitMQ入门教程(三):Hello World 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog. ...

  4. RabbitMQ入门教程(十七):消息队列的应用场景和常见的消息队列之间的比较

    原文:RabbitMQ入门教程(十七):消息队列的应用场景和常见的消息队列之间的比较 分享一个朋友的人工智能教程.比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看. 这是网上的一篇教程写的很好,不知原作 ...

  5. RabbitMQ入门教程(十):队列声明queueDeclare

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

  6. RabbitMQ入门教程(一):安装和常用命令

    原文:RabbitMQ入门教程(一):安装和常用命令 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn ...

  7. 无废话ExtJs 入门教程四[表单:FormPanel]

    无废话ExtJs 入门教程四[表单:FormPanel] extjs技术交流,欢迎加群(201926085) 继上一节内容,我们在窗体里加了个表单.如下所示代码区的第28行位置,items:form. ...

  8. PySide——Python图形化界面入门教程(四)

    PySide——Python图形化界面入门教程(四) ——创建自己的信号槽 ——Creating Your Own Signals and Slots 翻译自:http://pythoncentral ...

  9. Elasticsearch入门教程(四):Elasticsearch文档CURD

    原文:Elasticsearch入门教程(四):Elasticsearch文档CURD 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接: ...

随机推荐

  1. jQuery 全选和反选demo

    前段时间做了一个全选和反选的功能,最近不忙了,做了一个简化版的demo. 全部代码如下: <!DOCTYPE html> <html> <head> <tit ...

  2. 关于Tomcat配置相关总结

    特别提示:本人博客部分有参考网络其他博客,但均是本人亲手编写过并验证通过.如发现博客有错误,请及时提出以免误导其他人,谢谢!欢迎转载,但记得标明文章出处:http://www.cnblogs.com/ ...

  3. 动态数组C语言实现

    /* * DynamicArray.h * * Created on: 2019年7月22日 * Author: Jarvis */ #ifndef SRC_DYNAMICARRAY_H_ #defi ...

  4. 全排列函数next_permutation(a,a+n)

    #include<iostream> #include<algorithm> using namespace std; int main(){ ]; int n; cin> ...

  5. jquery实现对象数组 筛选出每条记录中的特定属性字段 及根据某个属性值筛选出指定的元素

    jquery实现对象数组 筛选出每条记录中的特定属性字段 直接上图: 源码: /** * 对后端返回的数据,筛选出符合报表的列项,多余的列项去除 */ function filterParams(da ...

  6. PHP CI 框架简单使用(二)

    我们简单认识一下CI框架的MVC.示例代码如下 //CI控制器文件Home.php <?php defined('BASEPATH') OR exit('No direct script acc ...

  7. Java -- 通过 URLConnection 进行http请求中文乱码

    对writer和reader指定字符集 out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream(), "utf-8 ...

  8. flutter 添加全局环境变量

    flutter安装好了之后 要添加全局环境变量才可以在终端通过flutter命令来操作 安装flutter环境变量 vim ~/.bash_profile (不存在就创建,添加下面一行命令) expo ...

  9. New Date API介绍

    一.新的Date API介绍 LocalDateLocalTimeInstantDurationPeriod formatterparsejdk以前的java.util.Date存在的问题 1)比如n ...

  10. Selenium 2自动化测试实战19(下载文件)

    一.下载文件 webDriver允许设置默认的文件下载路径,也就是说,文件会自动下载并且存放到设置的目录中.下面以火狐浏览器为例,执行文件的下载. #downfile.py # -*- coding: ...