A. 多线程消费同一队列

参考资料:https://www.rabbitmq.com/tutorials/tutorial-two-java.html

消费一条消息往往比产生一条消息慢很多,为了防止消息积压,一般需要开启多个工作线程同时消费消息。在 RabbitMQ 中,我们可以创建多个 Consumer 消费同一队列。示意图如下:

gordon.study.rabbitmq.workqueue.Sender.java

public class Sender {

    private static final String QUEUE_NAME = "tasks";

    private String name;

    public Sender(String name) {
this.name = name;
} public void work() throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null); for (int i = 0; i < 10;) {
String message = "NO. " + ++i;
TimeUnit.MILLISECONDS.sleep(100);
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
System.out.printf("(%1$s)[===>%2$s ] %3$s\n", name, ":" + QUEUE_NAME, message);
} channel.close();
connection.close();
}
}

gordon.study.rabbitmq.workqueue.Receiver.java

public class Receiver {

    private static final String QUEUE_NAME = "tasks";

    private String name;

    private int sleepTime;

    public Receiver(String name, int sleepTime) {
this.name = name;
this.sleepTime = sleepTime;
} public void work() throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null); 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.printf(" [ %2$s<===](%1$s) %3$s\n", name, QUEUE_NAME, message);
try {
TimeUnit.MILLISECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
}
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}

gordon.study.rabbitmq.workqueue.Test01.java

public class Test01 {

    public static void main(String[] args) throws Exception {
Receiver recv1 = new Receiver("A", 200);
recv1.work();
Receiver recv2 = new Receiver("B", 200);
recv2.work();
Sender sender = new Sender("S");
sender.work();
}
}

运行 Test01,发现 A、B 两个消费者轮流获取 S 发送的消息。

RabbitMQ 默认将消息顺序发送给下一个消费者,这样,每个消费者会得到相同数量的消息。即,轮询(round-robin)分发消息。

轮询很好,可是如果两个消费者消费能力不一样呢?

gordon.study.rabbitmq.workqueue.Test02SlowConsumer.java

public class Test02SlowConsumer {

    public static void main(String[] args) throws Exception {
Receiver recv1 = new Receiver("A", 200);
recv1.work();
Receiver recv2 = new Receiver("B", 800);
recv2.work();
Sender sender = new Sender("S");
sender.work();
}
}

将消费者B 的消费时间提高到800毫秒,问题就出现了:B 依然分到了一半消息,需要运行很久才能处理完。

B. 公平分发(fair dispatch)

怎样才能做到按照每个消费者的能力分配消息呢?联合使用 Qos 和 Acknowledge 就可以做到。

gordon.study.rabbitmq.workqueue.QosAcknowledgeReceiver.java

public class QosAcknowledgeReceiver {

    private static final String QUEUE_NAME = "tasks";

    private String name;

    private int sleepTime;

    public QosAcknowledgeReceiver(String name, int sleepTime) {
this.name = name;
this.sleepTime = sleepTime;
} public void work() throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, 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.printf(" [ %2$s<===](%1$s) %3$s\n", name, QUEUE_NAME, message);
try {
TimeUnit.MILLISECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
}
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}

代码第22行的 basicQos 方法设置了当前信道最大预获取(prefetch)消息数量为1。消息从队列异步推送给消费者,消费者的 ack 也是异步发送给队列,从队列的视角去看,总是会有一批消息已推送但尚未获得 ack 确认,Qos 的 prefetchCount 参数就是用来限制这批未确认消息数量的。设为1时,队列只有在收到消费者发回的上一条消息 ack 确认后,才会向该消费者发送下一条消息。prefetchCount 的默认值为0,即没有限制,队列会将所有消息尽快发给消费者。

查看 basicQos 重载方法,发现几个有趣的特性(参考https://www.rabbitmq.com/consumer-prefetch.html):

  • basicQos 中 prefetchSize 参数通过消息的总字节数来限制队列推送消息的速度
  • prefetchSize 与 prefetchCount 可以同时设置,达到任何一个限制,则队列暂停推送消息
  • global 参数表示前两个参数的作用域,true 表示限制是针对信道的,false 表示限制是针对消费者的(我还没试过一个信道支持多个消费者的例子,样例代码见下方)
  • 可以对同一个信道同时设置 global 为 true 和 false 的 Qos,表示队列要考虑每个消费者的限制,同时还要考虑整个信道的限制
  • 看起来API注释是错的,因为 global 默认是 false,所以第22行代码应该是把当前信道上每个消费者(当然,上面的例子中只有一个)的 prefetchCount 设为 1
Channel channel = ...;
Consumer consumer1 = ...;
Consumer consumer2 = ...;
channel.basicQos(10, false); // Per consumer limit
channel.basicQos(15, true); // Per channel limit
channel.basicConsume("my-queue1", false, consumer1);
channel.basicConsume("my-queue2", false, consumer2);

第37行代码将 autoAck 设为 false,向 Broker 发送 ack 响应的任务就交给开发人员了。

第34行代码在任务真正完成后,调用 basicAck 方法主动通知队列消息已成功消费。当队列收到 ack 确认后,会把下一条消息推送过来,并将该消息从队列中删除。

Qos 方案示意图如下:

gordon.study.rabbitmq.workqueue.Test03FairDispatch.java

public class Test03FairDispatch {

    public static void main(String[] args) throws Exception {
QosAcknowledgeReceiver recv1 = new QosAcknowledgeReceiver("A", 200);
recv1.work();
QosAcknowledgeReceiver recv2 = new QosAcknowledgeReceiver("B", 800);
recv2.work();
Sender sender = new Sender("S");
sender.work();
}
}

运行Test03,可以看到 RabbitMQ 按照消费者的实际能力分配消息。

RabbitMQ入门_05_多线程消费同一队列的更多相关文章

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

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

  2. RabbitMQ 入门教程(PHP版) 延迟队列,延迟任务

    延迟任务应用场景 场景一:物联网系统经常会遇到向终端下发命令,如果命令一段时间没有应答,就需要设置成超时. 场景二:订单下单之后30分钟后,如果用户没有付钱,则系统自动取消订单. 场景三:过1分钟给新 ...

  3. 2.RABBITMQ 入门 - WINDOWS - 生产和消费消息 一个完整案例

    关于安装和配置,见上一篇 1.RABBITMQ 入门 - WINDOWS - 获取,安装,配置 公司有需求,要求使用winform开发这个东西(消息中间件),另外还要求开发一个日志中间件,但是也是要求 ...

  4. rabbitmq 生产者 消费者(多个线程消费同一个队列里面的任务。) 一个通用rabbitmq消费确认,快速并发运行的框架。

    rabbitmq作为消息队列可以有消息消费确认机制,之前写个基于redis的通用生产者 消费者 并发框架,redis的list结构可以简单充当消息队列,但不具备消费确认机制,随意关停程序,会丢失一部分 ...

  5. .NET 环境中使用RabbitMQ RabbitMQ与Redis队列对比 RabbitMQ入门与使用篇

    .NET 环境中使用RabbitMQ   在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的 ...

  6. RabbitMQ .NET消息队列使用入门(二)【多个队列间消息传输】

    孤独将会是人生中遇见的最大困难. 实体类: DocumentType.cs public enum DocumentType { //日志 Journal = 1, //论文 Thesis = 2, ...

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

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

  8. RabbitMQ 入门系列:2、基础含义理解:链接、通道、队列、交换机

    系列目录 RabbitMQ 入门系列:1.MQ的应用场景的选择与RabbitMQ安装. RabbitMQ 入门系列:2.基础含义:链接.通道.队列.交换机. RabbitMQ 入门系列:3.基础含义: ...

  9. RabbitMQ 入门系列:7、保障消息不重复消费:产生消息的唯一ID。

    系列目录 RabbitMQ 入门系列:1.MQ的应用场景的选择与RabbitMQ安装. RabbitMQ 入门系列:2.基础含义:链接.通道.队列.交换机. RabbitMQ 入门系列:3.基础含义: ...

随机推荐

  1. sublime Text3在mac下设置窗口实现多标签

    打开Sublime Text3,点击左上角的Sublime Text3按钮,然后选择“Preferences”里面的“Settings-user” 在打开的配置文件里面,加入下面图中的这句代码即可&q ...

  2. 软件包管理:rpm命令管理-包命名与依赖性

    rpm包的管理主要有两种方法:一种是rpm命令管理另一种是yum在线管理 注意软件包没有扩展名,写上只是为了好看,便于识别而已. 注意区别包名,包全名.之所以要区分,就是因为有些命令十分挑剔,需要跟正 ...

  3. 总结C#获取当前路径的7种方法

    C#获取当前路径的方法如下: 1. System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName -获取模块的完整路径. 2. ...

  4. 使用PowerMockito 对静态类进行mock

    Mock的中文的意思就是模拟,Mockito是mock的扩展,但是Mockito并不支持对静态类的mock,所以我们引入PowerMockito实现对静态类的mock. 首先pom添加PowerMoc ...

  5. cda转MP3

    首先启动Windows Media Player播放器(依次单击“开始”——“所有程序”——“附件”——“娱乐”——“Windows Media Player”     在工具栏切换到“翻录”选项,这 ...

  6. tensorflow训练自己的数据集实现CNN图像分类1

    利用卷积神经网络训练图像数据分为以下几个步骤 读取图片文件 产生用于训练的批次 定义训练的模型(包括初始化参数,卷积.池化层等参数.网络) 训练 1 读取图片文件 def get_files(file ...

  7. Python: 从字典中提取子集--字典推导

    问题: 构造一个字典,它是另外一个字典的子集 answer: 最简单的方式是使用字典推导 eg1: 1. >>>prices = {'ACME': 45.23, 'AAPL': 61 ...

  8. linux基础命令---tmpwatch

    tmpwatch 删除最近一段时间没有访问的文件,时间以小时为单位,节省磁盘空间.tmpwatch递归删除给定时间未被访问的文件.通常,它用于清理用于临时保存空间(如/tmp)的目录.当更改目录时,t ...

  9. 如何安装nginx_lua_module模块,升级nginx,nginx-lua-fastdfs-GraphicsMagick动态生成缩略图,实现图片自动裁剪缩放

    如何安装nginx_lua_module模块,升级nginx,nginx-lua-fastdfs-GraphicsMagick动态生成缩略图,实现图片自动裁剪缩放 参考网站:nginx-lua-fas ...

  10. 计算两个集合的差集——第六期 Power8 算法挑战赛

    第六期Power8大赛 1.1 比赛题目 题目: 计算两个集合的差集: 详细说明: 分别有集合A和B两个大数集合,求解集合A与B的差集(A中有,但B中无的元素),并将结果保存在集合C中,要求集合C中的 ...