RabbitMQ学习第二记:工作队列的两种分发方式,轮询分发(Round-robin)和 公平分发(Fair dispatch)
1、什么是RabbitMQ工作队列
我们在应用程序使用消息系统时,一般情况下生产者往队列里插入数据时速度是比较快的,但是消费者消费数据往往涉及到一些业务逻辑处理导致速度跟不上生产者生产数据。因此如果一个生产者对应一个消费者的话,很容易导致很多消息堆积在队列里。这时,就得使用工作队列了。一个队列有多个消费者同时消费数据。
下图取自于官方网站(RabbitMQ)的工作队列的图例

P:消息的生产者
C1:消息的消费者1
C2:消息的消费者2
红色:队列
生产者将消息发送到队列,多个消费者同时从队列中获取消息。
工作队列有两种分发数据的方式:轮询分发(Round-robin)和 公平分发(Fair dispatch)。轮询分发:队列给每一个消费者发送数量一样的数据。公平分发:消费者设置每次从队列中取一条数据,并且消费完后手动应答,继续从队列取下一个数据。下面分别是两种分发方式不同的写法。
2、轮询分发(Round-robin)
生产者(Send)生产10条数据,消费者1(Receive1)接收数据并假设处理业务逻辑1s,消费者2(Receive2)接收数据并假设处理业务逻辑2s(生产者先运行,两个消费者同时运行)。
2.1、生产者(Send)代码
public class Send
{
//队列名称
private static final String QUEUE_NAME = "test_work_round_robin_queue"; public static void main(String[] args)
{
try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 10; i++)
{
String message = "this is work_round_robin queue message" + i;
System.out.println("[send]:" + message);
//发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("utf-8"));
Thread.sleep(20 * i);
}
channel.close();
connection.close();
}
catch (IOException | TimeoutException | InterruptedException e)
{
e.printStackTrace();
}
}
} 运行结果:
[send]:this is work_round_robin queue message0
[send]:this is work_round_robin queue message1
[send]:this is work_round_robin queue message2
[send]:this is work_round_robin queue message3
[send]:this is work_round_robin queue message4
[send]:this is work_round_robin queue message5
[send]:this is work_round_robin queue message6
[send]:this is work_round_robin queue message7
[send]:this is work_round_robin queue message8
[send]:this is work_round_robin queue message9
2.2、消费者1(Receive1)代码
public class Receive1
{
//队列名称
private static final String QUEUE_NAME = "test_work_round_robin_queue"; public static void main(String[] args)
{
try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel)
{
//当消息到达时执行回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException
{
String message = new String(body, "utf-8");
System.out.println("[1] Receive message:" + message);
try
{
//消费者休息1s处理业务
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println("[1] done");
}
}
};
//监听队列
channel.basicConsume(QUEUE_NAME, true, consumer);
}
catch (IOException e)
{
e.printStackTrace();
} }
} 运行结果:
[1] Receive message:this is work_round_robin queue message0
[1] done
[1] Receive message:this is work_round_robin queue message2
[1] done
[1] Receive message:this is work_round_robin queue message4
[1] done
[1] Receive message:this is work_round_robin queue message6
[1] done
[1] Receive message:this is work_round_robin queue message8
[1] done
2.3、消费者2(Receive2)代码
public class Receive2
{
//队列名称
private static final String QUEUE_NAME = "test_work_round_robin_queue"; public static void main(String[] args)
{ try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel)
{
//当消息到达时执行回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException
{
String message = new String(body, "utf-8");
System.out.println("[2] Receive message:" + message);
try
{
//消费者休息2s处理业务
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println("[2] done");
}
}
};
//监听队列
channel.basicConsume(QUEUE_NAME, true, consumer);
}
catch (IOException e)
{
e.printStackTrace();
} }
} 运行结果:
[2] Receive message:this is work_round_robin queue message1
[2] done
[2] Receive message:this is work_round_robin queue message3
[2] done
[2] Receive message:this is work_round_robin queue message5
[2] done
[2] Receive message:this is work_round_robin queue message7
[2] done
[2] Receive message:this is work_round_robin queue message9
[2] done
总结:两个消费者得到的数据量一样的。从运行时可以看到消费者1会先执行完,消费者2会后执行完。并不会因为两个消费者处理数据速度不一样使得两个消费者取得不一样数量的数据。并且当队列数量大的时候通过观察RabbitMQ的管理后台,可以看到管理界面队列中的数据很快就没了,但是这个时候两个消费者其实并没有消费完数据。这种分发方式存在着很大的隐患。
3、公平分发(Fair dispatch)
生产者(Send)生产10条数据,消费者1(Receive1)接收数据并假设处理业务逻辑1s,消费者2(Receive2)接收数据并假设处理业务逻辑2s(生产者先运行,两个消费者同时运行)。
消费者设置每次从队列里取一条数据,并且关闭自动回复机制,每次取完一条数据后,手动回复并继续取下一条数据。
3.1、生产者(Send)代码
public class Send
{
//队列名称
private static final String QUEUE_NAME = "test_work_fair_dispatch_queue"; public static void main(String[] args)
{
try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 10; i++)
{
String message = "this is work_fair_dispatch queue message" + i;
System.out.println("[send]:" + message);
//发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("utf-8"));
Thread.sleep(20 * i);
}
channel.close();
connection.close();
}
catch (IOException | TimeoutException | InterruptedException e)
{
e.printStackTrace();
}
}
} 运行结果:
[send]:this is work_fair_dispatch queue message0
[send]:this is work_fair_dispatch queue message1
[send]:this is work_fair_dispatch queue message2
[send]:this is work_fair_dispatch queue message3
[send]:this is work_fair_dispatch queue message4
[send]:this is work_fair_dispatch queue message5
[send]:this is work_fair_dispatch queue message6
[send]:this is work_fair_dispatch queue message7
[send]:this is work_fair_dispatch queue message8
[send]:this is work_fair_dispatch queue message9
3.2、消费者1(Receive1)代码
public class Receive1
{
//队列名称
private static final String QUEUE_NAME = "test_work_fair_dispatch_queue"; public static void main(String[] args)
{ try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//设置每次从队列里取一条数据
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel)
{
//当消息到达时执行回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException
{
String message = new String(body, "utf-8");
System.out.println("[1] Receive message:" + message);
try
{
//消费者休息1s处理业务
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println("[1] done");
//手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//设置手动应答
boolean autoAck = false;
//监听队列
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
catch (IOException e)
{
e.printStackTrace();
} }
} 运行结果:
[1] Receive message:this is work_fair_dispatch queue message1
[1] done
[1] Receive message:this is work_fair_dispatch queue message2
[1] done
[1] Receive message:this is work_fair_dispatch queue message4
[1] done
[1] Receive message:this is work_fair_dispatch queue message5
[1] done
[1] Receive message:this is work_fair_dispatch queue message7
[1] done
[1] Receive message:this is work_fair_dispatch queue message8
[1] done
3.3、消费者2(Receive2)代码
public class Receive2
{
//队列名称
private static final String QUEUE_NAME = "test_work_fair_dispatch_queue"; public static void main(String[] args)
{ try
{
//获取连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
final Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//保证一次只分发一个
int prefetchCount = 1;
channel.basicQos(prefetchCount);
//定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel)
{
//当消息到达时执行回调方法
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException
{
String message = new String(body, "utf-8");
System.out.println("[2] Receive message:" + message);
try
{
//消费者休息2s处理业务
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println("[2] done");
//手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//设置手动应答
boolean autoAck = false;
//监听队列
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
catch (IOException e)
{
e.printStackTrace();
} }
} 运行结果:
[2] Receive message:this is work_fair_dispatch queue message0
[2] done
[2] Receive message:this is work_fair_dispatch queue message3
[2] done
[2] Receive message:this is work_fair_dispatch queue message6
[2] done
[2] Receive message:this is work_fair_dispatch queue message9
[2] done
总结:消费者1处理了6条数据,消费者2处理了4条数据
与轮询分发不同的是,当每个消费都设置了每次只会从队列取一条数据时,并且关闭自动应答,在每次处理完数据后手动给队列发送确认收到数据。这样队列就会公平给每个消息费者发送数据,消费一条再发第二条,而且可以在管理界面中看到数据是一条条随着消费者消费完从而减少的,并不是一下子全部分发完了。显然公平分发更符合系统设计。
注意:本文仅代表个人理解和看法哟!和本人所在公司和团体无任何关系!
RabbitMQ学习第二记:工作队列的两种分发方式,轮询分发(Round-robin)和 公平分发(Fair dispatch)的更多相关文章
- 记Bootstrap Table两种渲染方式
这里主要区别两种Bootstrap Table的数据渲染方式,一.属性渲染方式,二.JS渲染方式 工作直接接手前人的,之前都直接在table标签上渲染属性,后面因为项目需要,同一页面的表格,需要申请不 ...
- Android学习笔记(36):Android的两种事件处理方式
Android提供了两种事件处理的方式:基于回调的事件处理 和 基于监听的事件处理. 我们来说的easy理解一点: (1)基于回调的事件处理就是继承GUI组件,并重写该组件的事件处理方法.除了一些特定 ...
- POJ 3225 线段树区间更新(两种更新方式)
http://blog.csdn.net/niuox/article/details/9664487 这道题明显是线段树,根据题意可以知道: (用0和1表示是否包含区间,-1表示该区间内既有包含又有不 ...
- Java学习-014-文本文件写入实例源代码(两种写入方式)
此文源码主要为应用 Java 读取文本文件内容实例的源代码.若有不足之处,敬请大神指正,不胜感激! 第一种:文本文件写入,若文件存在则删除原文件,并重新创建文件.源代码如下所示: /** * @fun ...
- Spring学习之Spring与Mybatis的两种整合方式
本机使用IDEA 2020.1.MySql 8.0.19,通过Maven进行构建 环境准备 导入maven依赖包 <dependencies> <dependency> < ...
- ElasticSearch 学习记录之Text keyword 两种基本类型区别
ElasticSearch 系列文章 1 ES 入门之一 安装ElasticSearcha 2 ES 记录之如何创建一个索引映射 3 ElasticSearch 学习记录之Text keyword 两 ...
- 【原】iOS学习之XML与JSON两种数据结构比较和各自底层实现
1.XML与JSON两种数据结构的优缺点 1> XML 优点: 格式统一, 符合标准 容易与其他系统进行远程交互, 数据共享比较方便 缺点: XML文件格式文件庞大, 格式复杂, 传输占 ...
- struts2+spring的两种整合方式
也许有些人会因为学习了struts1,会以为struts2.struts1与spring的整合也是一样的,其实这两者相差甚远.下面就来讲解一下struts2与spring的整合两种方案.(部分转载,里 ...
- 串的两种模式匹配方式(BF/KMP算法)
前言 串,又称作字符串,它是由0个或者多个字符所组成的有限序列,串同样可以采用顺序存储和链式存储两种方式进行存储,在主串中查找定位子串问题(模式匹配)是串中最重要的操作之一,而不同的算法实现有着不同的 ...
随机推荐
- base64图片下载
下面这种写法有些chrome不起作用 downLoadCanvas (data, filename = '活动.png') { var saveLink = document.createElemen ...
- Java jar文件
JAR(Java Archive)是基于ZIP文件格式的文件格式. 它用于捆绑Java应用程序或小程序的资源,类文件,声音文件,图像等. 它还提供数据压缩.一个JAR文件作为一种特殊类型的ZIP文件. ...
- USACO 2001 OPEN earthquake /// 最优比例生成树
题目大意: https://www.cnblogs.com/forever97/p/3603572.html 讲解:https://www.jianshu.com/p/d40a740a527e 题解: ...
- 驾驶心率和呼吸,疲劳检测系统,通过安全带,坐垫等内置sensor
HARKEN - Heart and respiration in car embedded nonintrusive sensors 2 https://www.youtube.com/watch? ...
- exports和module.exports的区别——学习笔记
一开始,exports和module.exports都指向空对象(同一内存块),exports是引用 module.exports的值.module.exports 被改变的时候,exports不会被 ...
- C# 模拟http请求网页数据 [网页爬虫]
using System; using System.Collections.Specialized; using System.IO; using System.Linq; using System ...
- WPF RichTextBox 当前光标后一个字符是文档的第几个字符
WPF RichTextBox 当前光标后一个字符是文档的第几个字符 运行环境:Win10 x64, NetFrameWork 4.8, 作者:乌龙哈里,日期:2019-05-05 参考: TextP ...
- Java集合中的Map接口怎么使用?
Map(双列集合框架) 1.Map接口及实现类概述 Map 接口提供三种collection 视图,允许以键集.值集或键-值映射关系集的形式查看某个映射的内容.映射顺序 定义为迭代器在映射的 coll ...
- Interview - 面试题汇总目录
参考 java 入门面试题 https://blog.csdn.net/meism5/article/details/89021536 一.Java 基础 1.JDK 和 JRE 有什么区别? 2.= ...
- delphi Copy函数 和 Pos函数
copy(a,b,c); a:就是copy源,就是一个字符串,表示你将要从a里copy一些东西, b:从a中的第b位开始copy(包含第1位), c:copy从第b位开始后的c个字符, 例如: m:= ...