RabbitMQ --- Work Queues(工作队列)
目录
RabbitMQ --- Hello Mr.Tua
RabbitMQ --- Publish/Subscribe(发布/订阅)
RabbitMQ --- Routing(路由)
前言

Work Queues 即工作队列,它表示一个 Producer 对应多个 Consumer,包括两种分发模式:轮循分发(Round-robin)和公平分发(Fair dispatch)。旨在为了避免立即执行任务时出现占用很多资源和时间却又必须等待完成的现象。
原理分析: Producer 把工作任务转化为消息先发送给 Exchange ,然后再由 Exchange 发送给队列,当后台有一个 Consumer 进程在运行时,它会不间断地从队列中取出消息来执行;当后台有多个 Consumer 进程在运行时,它们会不间断地从队列中取出消息采取并行执行的方式以提高效率。
轮循分发(Round-robin)
我修改了第一篇文章中的代码,用线程来模拟处理消息耗时的场景,分别在10个消息的末尾增加符号“>”,每个符号“>”表示该消息在线程中执行需要耗时1秒,每个消息处理完毕时以“OK”表示结束。
Producer 代码片段:
for (int m = ; m < ; m++)
{
string marks = string.Empty;
for (int n = ; n <= m; n++)
{
marks += ">";
}
string msg = "Mr.Tua" + marks + marks.Length + "s";
var body = Encoding.UTF8.GetBytes(msg);
channel.BasicPublish
(
exchange: string.Empty,
routingKey: "Tua",
basicProperties: null,
body: body
);
Console.WriteLine("Producer sent message: {0}", msg);
}
Consumer 代码片段:
consumer.Received += (sender, e) =>
{
var body = e.Body;
var msg = Encoding.UTF8.GetString(body);
int marks = msg.ToCharArray().Where(c => c.ToString() == ">").Count();
Console.WriteLine("Consumer received message: {0}", msg);
Thread.Sleep(marks * 1000);
Console.WriteLine("OK");
};
Producer 控制台(后启动运行):

Consumer 控制台(先启动运行 x 2 ):

Producer 先把消息发送给 Exchange ,然后再由 Exchange 按照顺序将每个消息发送给下一个 Consumer (一次性平均分配),每个 Consumer 得到相等数量的消息,当中不用考虑处理消息时需要耗费多少时间,也就是说不关心 Consumer 是否繁忙或空闲,这种默认的分发模式称为轮循分发(Round-robin)。
消息应答(Message acknowledgment)
如果某个 Consumer 在处理消息时由于各种原因挂了导致 Producer 没有收到消息处理完成时的应答,那么就会丢失 Consumer 正在处理和没有处理的消息。

两个 Consumer 同时运行的过程中我关闭了其中一个,可以看到下面的 Consumer 完成了第2个消息,丢失了第4(未处理完毕)、6、8、10个消息。
在这种情况下如何保证消息不丢失呢?
消息应答(Message acknowledgment):如果 Consumer 挂了没有发送应答,Producer 会重新转发给 Exchange,再由 Exchange 转发给其它的 Consumer 以保证不丢失消息。
修改 Consumer 代码:
var body = e.Body;
var msg = Encoding.UTF8.GetString(body);
int marks = msg.ToCharArray().Where(c => c.ToString() == ">").Count();
Console.WriteLine("Consumer received message: {0}", msg);
Thread.Sleep(marks * );
Console.WriteLine("OK");
//每个消息处理完毕时手动发送消息应答
channel.BasicAck
(
deliveryTag: e.DeliveryTag, //该消息的Index
multiple: false//是否批量应答,true:批量应答所有小于该deliveryTag的消息
);
channel.BasicConsume
(
queue: "Tua",
noAck: false,//手动应答
consumer: consumer
);

虽然下面的 Consumer 挂了,但是 Producer 会重新把消息发给 Exchange,再由 Exchange 发给上面的 Consumer 去处理。
消息持久化(Message durability)
现在已经知道当 Consumer 挂了不丢失消息的解决方案,可是 RabbitMQ 服务要是挂了会导致所有的队列和消息丢失,这种情况该怎么办呢?
消息持久化(Message durability):让所有的队列和消息都开启持久化功能,将队列和消息都保存在磁盘上以达到持久化的目的。
另外还有一种为了解决事务机制性能开销大(导致吞吐量下降)而提出的更强大的消息持久化的方式叫做 Publisher Confirm,这里不作讨论。
修改 Producer 代码:
channel.QueueDeclare
(
queue: "Tua",
durable: true,//开启队列持久化
exclusive: false,
autoDelete: false,
arguments: null
);
var basicProperties = channel.CreateBasicProperties();
basicProperties.Persistent = true;//开启消息持久化
channel.BasicPublish
(
exchange: string.Empty,
routingKey: "Tua",
basicProperties: basicProperties,
body: body
);
修改 Consumer 代码:
channel.QueueDeclare
(
queue: "Tua",
durable: true,//开启队列持久化
exclusive: false,
autoDelete: false,
arguments: null
);
运行程序时报出一个异常错误:

这是因为修改了代码 durable: true,开启了队列的持久化,然而 RabbitMQ 是不允许使用不同的参数重新定义一个已有的同名队列。
两种方法可以解决:
1.重新定义一个不同名的队列;
2.删除已有的同名队列。
第一种方法没有什么好说的,这里说第二种方法,打开 RabbitMQ 管理平台删除已有的同名队列:

测试步骤:首先启动 Producer ---> 关闭 RabbitMQ 服务 ---> 启动 RabbitMQ 服务 ---> 最后启动 Consumer。
测试结果:队列和消息都木有丢失,这里就不再上图了。
公平分发(Fair dispatch)
在介绍轮循分发(Round-robin)时有提到它是不关心 Consumer 是否繁忙或空闲的,但是这样很可能就会出现有的 Consumer 劳累过度赶脚身体被掏空,而有的 Consumer 悠闲自得赶脚无用武之地的问题,那该怎么办呢?
公平分发(Fair dispatch):不会同时给一个 Consumer 发送多个新消息,只有在 Consumer 空闲的时候才会给它发送一个新消息。
修改 Consumer 代码:
//请求服务的特殊设置
channel.BasicQos
(
prefetchSize: ,//服务传送消息的最大容量,0表示无限制
prefetchCount: ,//服务传送消息的最大数量,0表示无限制
global: false//false:将以上的设置应用于Consumer级别,true:将以上的设置应用于Channel级别
);
为了便于演示,我把 Producer 发送消息的顺序改为从10到1。


当 Consumer 空闲的时候才会给它发送一个新消息,而且在公平分发(Fair dispatch)模式下支持动态增加 Consumer ,使得新加的 Consumer 可以立即处理还没有发送出去的消息。
反观在默认的轮循分发(Round-robin)模式下已经将消息一次性平均分配完毕,就算是动态增加了 Consumer 也然并卵。。。
示例代码
using RabbitMQ.Client;
using System;
using System.Text; namespace WorkQueuesProducer
{
class Program
{
static void Main(string[] args)
{
var factory = new ConnectionFactory
{
HostName = "192.168.31.212",
UserName = "Tua",
Password = "Tua",
Port =
};
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare
(
queue: "Tua",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null
);
for (int m = ; m < ; m++)
{
string marks = string.Empty;
for (int n = ; n <= m; n++)
{
marks += ">";
}
string msg = "Mr.Tua" + marks + marks.Length + "s";
var body = Encoding.UTF8.GetBytes(msg);
var basicProperties = channel.CreateBasicProperties();
basicProperties.Persistent = true;
channel.BasicPublish
(
exchange: string.Empty,
routingKey: "Tua",
basicProperties: basicProperties,
body: body
);
Console.WriteLine("Producer sent message: {0}", msg);
}
Console.ReadLine();
}
}
}
}
}
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Linq;
using System.Text;
using System.Threading; namespace WorkQueueConsumer
{
class Program
{
static void Main(string[] args)
{
var factory = new ConnectionFactory
{
HostName = "localhost"
};
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare
(
queue: "Tua",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null
);
channel.BasicQos
(
prefetchSize: ,
prefetchCount: ,
global: false
);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (sender, e) =>
{
var body = e.Body;
var msg = Encoding.UTF8.GetString(body);
int marks = msg.ToCharArray().Where(c => c.ToString() == ">").Count();
Console.WriteLine("Consumer received message: {0}", msg);
Thread.Sleep(marks * );
Console.WriteLine("OK");
channel.BasicAck
(
deliveryTag: e.DeliveryTag,
multiple: false
);
};
channel.BasicConsume
(
queue: "Tua",
noAck: false,
consumer: consumer
);
Console.ReadLine();
}
}
}
}
}
RabbitMQ --- Work Queues(工作队列)的更多相关文章
- 3、RabbitMQ-work queues 工作队列
work queues 工作队列 1.模型图: 为什么会出现 work queues? 前提:使用 simple 队列的时候 我们应用程序在是使用消息系统的时候,一般生产者 P 生产消息是毫不费力的( ...
- 译:2. RabbitMQ Java Client 之 Work Queues (工作队列)
在上篇揭开RabbitMQ的神秘面纱一文中,我们编写了程序来发送和接收来自命名队列的消息. 本篇我们将创建一个工作队列,工作队列背后的假设是每个任务都交付给一个工作者 本篇是译文,英文原文请移步:ht ...
- rabbitmq消息队列——"工作队列"
二."工作队列" 在第一节中我们发送接收消息直接从队列中进行.这节中我们会创建一个工作队列来分发处理多个工作者中的耗时性任务. 工作队列主要是为了避免进行一些必须同步等待的资源密集 ...
- RabbitMQ入门教程——工作队列
什么是工作队列 工作队列是为了避免等待一些占用大量资源或时间操作的一种处理方式.我们把任务封装为消息发送到队列中,消费者在后台不停的取出任务并且执行.当运行了多个消费者工作进程时,队列中的任务将会在每 ...
- RabbitMQ入门:工作队列(Work Queue)
在上一篇博客<RabbitMQ入门:Hello RabbitMQ 代码实例>中,我们通过指定的队列发送和接收消息,代码还算是比较简单的. 假设有这一些比较耗时的任务,按照上一次的那种方式, ...
- RabbitMQ入门(2)——工作队列
前面介绍了队列接收和发送消息,这篇将学习如何创建一个工作队列来处理在多个消费者之间分配耗时的任务.工作队列(work queue),又称任务队列(task queue). 工作队列的目的是为了避免立刻 ...
- RabbitMQ 之 WorkQueues工作队列
模型图 为什么会出现 work queues? 前提:使用 simple 队列的时候 (上一篇博客)我们应用程序在是使用消息系统的时候,一般生产者 P 生产消息是毫不费力的(发送消息即可),而消费者接 ...
- (转) RabbitMQ学习之工作队列(java)
http://blog.csdn.net/zhu_tianwei/article/details/40887717 参考:http://blog.csdn.NET/lmj623565791/artic ...
- 译: 2. RabbitMQ Spring AMQP 之 Work Queues
在上一篇博文中,我们写了程序来发送和接受消息从一个队列中. 在这篇博文中我们将创建一个工作队列,用于在多个工作人员之间分配耗时的任务. Work Queues 工作队列(又称:任务队列)背后的主要思想 ...
随机推荐
- Akka(10): 分布式运算:集群-Cluster
Akka-Cluster可以在一部物理机或一组网络连接的服务器上搭建部署.用Akka开发同一版本的分布式程序可以在任何硬件环境中运行,这样我们就可以确定以Akka分布式程序作为标准的编程方式了. 在上 ...
- SQL检测开始日期 结束日期 是否存在交叉
检测开始日期 结束日期 是否存在交叉 "+tj+" and ((starttime>="+starttime+" and starttime<=&q ...
- [leetcode-521-Longest Uncommon Subsequence I]
Given a group of two strings, you need to find the longest uncommon subsequence of this group of two ...
- 用Eclipse的snippets功能实现代码重用
snippets功能实现代码重用 Snippets 代码片段是Eclipse的一个插件. 很多时候可以通过这个功能,重复使用常用的代码片段,加快开发效率. 创建一个代码段的步骤: 在Eclipse的e ...
- 4.如何实现用MTQQ通过服务器实现订阅者和发布者的通讯
1.本例子意在用moquette服务器来作为消息转发,通过订阅者订阅消息,发布者发布消息,然后发布者的消息可以通过服务器转发给订阅者 服务器例子: https://github.com/andsel/ ...
- phpstorm,webstorm取消自动保存并标识修改的文件为星星标记
a.取消自动保存是去掉一下两个勾选. b.标记星星要勾选下面的选项.
- net 中web.config单一解决方法 (其他配置引入方式)
近期一个项目需要写许多的配置项,发现在单个web.config里面写的话会很乱也难于查找 所以搜了一下解决了,记录下来 一. webconfig提供了引入其他config的方式 <conne ...
- c++数组易错点总结
c++数组 1.只有在定义数组是才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组 int cards[4] = { 3 , 6 , 8 , 10}; //ok int hands[4] ...
- Eclipse 修改 创建的Jsp的默认格式
Eclipse 的jsp模板修改 打开 eclipse 选择 Window -- Preferences
- Go学习笔记(一)Let's 干吧
加 Golang学习 QQ群共同学习进步成家立业 ^-^ 群号:96933959 简介 Go是Google开发的一种 静态强类型.编译型,并发型,并具有垃圾回收功能的编程语言.为了方便搜索和识 ...