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 工作队列(又称:任务队列)背后的主要思想 ...
随机推荐
- SVG制作简单的图形
圆形 circle <svg width="200" height="200" > <circle cx="100" cy ...
- jquery让页面滚动条top,滚动条 顶部
jquery让页面滚动条top,滚动条 顶部$(document).scrollTop(0);
- iOS中UIWebView执行JS代码(UIWebView)
iOS中UIWebView执行JS代码(UIWebView) 有时候iOS开发过程中使用 UIWebView 经常需要加载网页,但是网页中有很多明显的标记让人一眼就能看出来是加载的网页,而我们又不想被 ...
- 一张图告诉你angular2所有知识点
忙活了半年,从angular2.0到现在angular4.2.从没AOT到有AOT.我想说,angular2的学习曲线真的有点陡峭.只能说,angular2是一个比较完整的框架,框架就是这样,一大堆条 ...
- Vue.js 基本功能了解
一.写在前面 隔了这么久才来出Vue的第二篇文章,真是堕落了,自己先惩罚下/(ㄒoㄒ)/~~ 回过头看自己第一篇相关文章<初试 Vue.js>(http://www.cnblogs.com ...
- Vue.js学习笔记(二) - 常用模板语法
本篇将在上一篇的基础上记录文本渲染.表达式.过滤器以及常用指令的简单用法. 一.文本渲染 Vue支持动态渲染文本,即在修改属性的同时,实时渲染文本内容.同时为了提高渲染效率,也支持只渲染一次,即第一次 ...
- Java 容器在实际项目中的应用
前言:在java开发中我们离不开集合数组等,在java中有个专有名词:"容器" ,下面会结合Thinking in Java的知识和实际开发中业务场景讲述一下容器在Web项目中的用 ...
- java编译出错信息汇总(更新)
1. java.lang.ClassNotFoundException: com.mysql.jdbc.Driver 原因:缺包mysql-connector-java-5.1.7-bin.jar 2 ...
- IE浏览器兼容问题(上)——html和css的兼容写法
用户使用的浏览器五花八门,我们要保证每一种浏览器都能兼容我们的代码,不能要求用户去改变浏览器,那么就得在我们的代码上下功夫.此时我们要用到hack. HACK就是针对不同的浏览器写不同的HTML.CS ...
- 每天来点Java面试题(一)
(1)java 中的 static 字段的 使用: 什么是 static ? 它是 java 的修饰符,定义静态变量和静态方法. 什么时候用? 通常用在工具类里面,修饰静态方法,然后供其它类的方法使 ...