RabbitMQ学习之Work Queues(2)
目录:
轮询调度(Round-robin dispatching):即依次分配分配任务给worker。
消息答复(Message acknowledgement):在consumer处理完之后,进行消息答复。避免杀掉worker后,message消息。
消息持久化(Message durability):在RabbitMQ server停止后,确保message不会丢失。需要持久化queue和message
公平调度(Fair dispatch):为了使worker不会出现有的一直在busy,而有的一致很闲的状态。使用的是 channel.BasicQos(0, 1, false) ,确保worker确认完成上一个任务后,才会分配下一个。
代码
简述
在第一个教程中,我们讲了在一个指定的queue中发送和接收message. 下面我们讲一个用于在多个worker之间分配费时任务的工作队列(Work Queue).
Work Queue的主要思想就是避免立即做一个资源集中型任务并且还必须等待它完成。
我们把任务封装成一个message,并且发送到队列。这里面的worker实际就是consumer,之后会由它们执行这些任务。
我们会统计字符串中的 . 来使程序sleep。即使用Thread.Sleep()。例如Hello...会花费3秒。
在这里我们的producer叫做NewTask。而我们的consumer叫做worker。
它们可以在上一节的基础上做一些修改得到
发送消息代码修改
var message = GetMessage(args);var body = Encoding.UTF8.GetBytes(message);
var properties = channel.CreateBasicProperties();
properties.Persistent = true; channel.BasicPublish(exchange: "",
routingKey: "task_queue",
basicProperties: properties,
body: body);
private static string GetMessage(string[] args)
{
return ((args.Length > ) ? string.Join(" ", args) : "Hello World!");
}
接收消息代码修改
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message); int dots = message.Split('.').Length - ;
Thread.Sleep(dots * ); Console.WriteLine(" [x] Done");
};
channel.BasicConsume(queue: "task_queue", autoAck: true, consumer: consumer);
这个里面的消息依然是自动答复的
上面的修改是为了模拟真实耗时任务。
轮询调度(Round-robin dispatching)
使用队列任务的一个好处就是能很容易的进行并发任务。
首先,我们尝试同时运行两个worker。它们可以同时从队列中取到message。那么具体是怎样呢?
你需要打开三个控制台程序,两个运行worker程序。
# shell 1 cd Worker
dotnet run# => [*] Waiting for messages. To exit press CTRL+C
# shell 2 cd Worker
dotnet run# => [*] Waiting for messages. To exit press CTRL+C
第三个用来发布new tasks.
# shell 3 cd NewTask
dotnet run "First message."
dotnet run "Second message.."
dotnet run "Third message..."
dotnet run "Fourth message...."
dotnet run "Fifth message....."
我们看下两个worker中的结果:
默认情况下,RabbitMQ会轮询发送每个message。所以,平均来说,每个consumer会得到相同数量的messages . 这种分发message的方式叫做轮询。
同时,注意到,queue中的message只能发到一个worker里,即两个worker里的task不会重复,即这是一种点对点的方式。
消息答复(Message acknowledgement)
你想下,如果一个consumer正在进行一个长任务(long task),并且就完成了一部分就死掉了。那么会发生什么呢?在我们当前的代码里,一旦RabbitMQ发送了一个message到一个consumer,那么RabbitMQ里的message立刻就会被标记为删除(deletion)。 在这种情况下,如果你杀死一个worker,我们将会丢失这个worker正在处理的message,我们也会丢失所有已经分配到这个worker但还没处理的messages。
注意:默认情况下,并不是会等每个task在consumer中执行完才会分发下一个message,也有可能是一下分发好多条。具体可以通过设置。
但是,我们不想丢失任务tasks。如果一个worker死掉了,我们想要task会被发送到另一个worker。
为了message不再丢失,RabbitMQ引入了message acknowledge。一个ack 会在一个message被接收,处理后被consumer发送回来,并且RabbitMQ把它标记为删除。
如果一个consumer还没发送一个ack就死掉了。RabbitMQ会认为它没被完全处理,并且re-queue 它。如果线上同时还有其他的consumer,那么RabbitMQ会很快的把它发送到另一个consumer。这样即使worker突然死了,也没有message会丢失了。
消息过时是不存在的。RabbitMQ将重发这个message,当consumer死掉时。即使在处理message时花费很长时间,也没有关系(因为不存在过时)
Manual message acknowledgment(手动消息答复) 默认是开启的。在前一个例子中,我们通过设置autoAck为true把它关闭了。
现在,我们把手动消息答复打开(即autoAck设置为false),并且,一旦我们做完了一个task,我们就发送一个确认(a acknowledgment).
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message); int dots = message.Split('.').Length - ;
Thread.Sleep(dots * ); Console.WriteLine(" [x] Done"); channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); //消息答复
};
channel.BasicConsume(queue: "task_queue", autoAck: false, consumer: consumer);//设置手动消息答复开启
这样,即使我们杀死了一个worker,我们的message也不会丢失了。
Acknowledgement必须和接收message的通道是同一个。否则会报 channel-level protocol exception。
那么,如果我们忘记发acknowledgement会怎么样呢?
忘记BasicAck是一个常发生的错误,但是后果却很严重。当你的client退出后,messages也会被重发。但是RabbitMQ会吃掉(消耗)越来越多的内存,随着它无法释放任何unacked messages.
你可以通过message_unacknowledged打印出没确认的message
sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged
Windows上
rabbitmqctl.bat list_queues name messages_ready messages_unacknowledged
消息持久化(Message durability)
我们已经学了当consumer被杀死时,使task不丢失。但是如果我们的RabbitMQ server停止了,我们的task依然会丢失。
想要server停止时,messages不丢失,需要标记queue和message是持久化的(durable)。
首先,我们标记queue是durable
channel.QueueDeclare(queue: "hello",
durable: true, //标记queue为durable
exclusive: false,
autoDelete: false,
arguments: null);
虽然上面的代码本身是正确的,但是在目前却不会生效。因为我们之前已经定义过了一个hello的queue,它是not durable。RabbitMQ不会允许你使用不同的参数重新定义一个已经存在的queue,并且会报错。
这里,我们先直接声明一个不同名称的queue。如下
channel.QueueDeclare(queue: "task_queue",
durable: true, //标记queue为durable
exclusive: false,
autoDelete: false,
arguments: null);
其中,QueueDeclare需要被应用到producer和consumer的代码里。
现在,我们标记messages为persistent(永恒的)。通过设置IBasicProperties.SetPersistent为true.
var properties = channel.CreateBasicProperties();
properties.Persistent = true; //设置message是persistent
公平调度(Fair dispatch)
你可能已经注意到,上面的调度仍然不能按我们想要的工作。它可能出现两个worker一个一直很忙,一个一直很闲(任务执行时间不一样)。
这是因为RabbitMQ会被分发,当message输入一个queue。它不会看一个consumer未完成的queue , 它仅仅盲目的分发第几个到第几个consumer.
为了改变行为,我们可以使用BasicQos,并且prefetchCount=1。这个会告诉RabbityMQ每次给只会给worker一个message。或者说,RabbitRQ在worker处理并且确认之前不会分发一个新的message。也可以说,RabbitMQ会分发给下一个不忙的worker。
channel.BasicQos(, , false);
注意queue的大小
如果你的所有worker都是busy的,说明你的queue已经满了。你应该对此保持关注,并且或者你可以增加更多的worker或者有一些其他策略。
代码
NewTask.cs
using System;using RabbitMQ.Client;using System.Text;
class NewTask
{
public static void Main(string[] args)
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using(var connection = factory.CreateConnection()) //创建连接
using(var channel = connection.CreateModel()) //创建channel
{
channel.QueueDeclare(queue: "task_queue", //声明一个durable的queue
durable: true,
exclusive: false,
autoDelete: false,
arguments: null); var message = GetMessage(args); //取得message
var body = Encoding.UTF8.GetBytes(message); var properties = channel.CreateBasicProperties(); //设置message是persistent
properties.Persistent = true; channel.BasicPublish(exchange: "", //发送
routingKey: "task_queue",
basicProperties: properties,
body: body);
Console.WriteLine(" [x] Sent {0}", message);
} Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
} private static string GetMessage(string[] args)
{
return ((args.Length > ) ? string.Join(" ", args) : "Hello World!");
}
}
Worker.cs
using System;using RabbitMQ.Client;using RabbitMQ.Client.Events;using System.Text;using System.Threading;
class Worker
{
public static void Main()
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using(var connection = factory.CreateConnection()) //建立连接
using(var channel = connection.CreateModel()) //建立通道(channel)
{
channel.QueueDeclare(queue: "task_queue", //声明queue是durable
durable: true,
exclusive: false,
autoDelete: false,
arguments: null); channel.BasicQos(prefetchSize: , prefetchCount: , global: false); //设置公平的调度策略(fair dispatch) Console.WriteLine(" [*] Waiting for messages."); var consumer = new EventingBasicConsumer(channel); //回调函数
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(" [x] Received {0}", message); int dots = message.Split('.').Length - ; //模拟真实业务花费一些时间
Thread.Sleep(dots * ); Console.WriteLine(" [x] Done"); channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); //消息答复
};
channel.BasicConsume(queue: "task_queue", //发送并且设置手动消息答复开启
autoAck: false,
consumer: consumer); Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}
}
参考网址:RabbitMQ
RabbitMQ学习之Work Queues(2)的更多相关文章
- RabbitMQ学习总结 第三篇:工作队列Work Queue
目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...
- Redis总结(五)缓存雪崩和缓存穿透等问题 Web API系列(三)统一异常处理 C#总结(一)AutoResetEvent的使用介绍(用AutoResetEvent实现同步) C#总结(二)事件Event 介绍总结 C#总结(三)DataGridView增加全选列 Web API系列(二)接口安全和参数校验 RabbitMQ学习系列(六): RabbitMQ 高可用集群
Redis总结(五)缓存雪崩和缓存穿透等问题 前面讲过一些redis 缓存的使用和数据持久化.感兴趣的朋友可以看看之前的文章,http://www.cnblogs.com/zhangweizhon ...
- RabbitMQ学习之:(六)Direct Exchange (转贴+我的评论)
From: http://lostechies.com/derekgreer/2012/04/02/rabbitmq-for-windows-direct-exchanges/ RabbitMQ fo ...
- 官网英文版学习——RabbitMQ学习笔记(十)RabbitMQ集群
在第二节我们进行了RabbitMQ的安装,现在我们就RabbitMQ进行集群的搭建进行学习,参考官网地址是:http://www.rabbitmq.com/clustering.html 首先我们来看 ...
- 官网英文版学习——RabbitMQ学习笔记(一)认识RabbitMQ
鉴于目前中文的RabbitMQ教程很缺,本博主虽然买了一本rabbitMQ的书,遗憾的是该书的代码用的不是java语言,看起来也有些不爽,且网友们不同人学习所写不同,本博主看的有些地方不太理想,为此本 ...
- RabbitMQ学习笔记五:RabbitMQ之优先级消息队列
RabbitMQ优先级队列注意点: 1.只有当消费者不足,不能及时进行消费的情况下,优先级队列才会生效 2.RabbitMQ3.5以后才支持优先级队列 代码在博客:RabbitMQ学习笔记三:Java ...
- RabbitMQ学习系列(四): 几种Exchange 模式
上一篇,讲了RabbitMQ的具体用法,可以看看这篇文章:RabbitMQ学习系列(三): C# 如何使用 RabbitMQ.今天说些理论的东西,Exchange 的几种模式. AMQP协议中的核心思 ...
- RabbitMQ学习系列(三): C# 如何使用 RabbitMQ
上一篇已经讲了Rabbitmq如何在Windows平台安装,还不了解如何安装的朋友,请看我前面几篇文章:RabbitMQ学习系列一:windows下安装RabbitMQ服务 , 今天就来聊聊 C# 实 ...
- RabbitMQ学习总结 第一篇:理论篇
目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...
随机推荐
- JavaEE JDBC 读写LOB大对象
JDBC 读写LOB大对象 @author ixenos LOB 除了数字.字符串和日期之外,许多数据库还可以存储大对象,例如图片或其他数据, 在SQL中,二进制(字节型)大对象称为BLOB,字符型大 ...
- Android BGABadgeView:BGABadgeLinearLayout以整体线性布局作为BadgeView(3)
Android BGABadgeView:BGABadgeLinearLayout以整体线性布局作为BadgeView(3) Android BGABadgeView不仅可以把某个View作为B ...
- CodeForces 159E
题目大意: 给定一堆带颜色和高度的魔方 用两种颜色的魔方,一种颜色接一种颜色向上拼接搭建成一个高塔,求高塔的最长高度,以及将拼接的过程中对应的编号顺序输出 多种情况成立输出任意一种即可 这里首先要对颜 ...
- PHP $_SERVER的使用
常常会用到php的$_SERVER变量,可是好多常用的参数又不熟每次都去查手册.为了记住一些常用的,写个日志吧.前导:网站根目录:/www/domain.com/访问Url:http://www.do ...
- 静态区间第k大(划分树)
POJ 2104为例[经典划分树问题] 思想: 利用快速排序思想, 建树时将区间内的值与区间中值相比,小于则放入左子树,大于则放入右子树,如果相等则放入左子树直到放满区间一半. 查询时,在建树过程中利 ...
- SQL SERVER示例:修改自定义数据类型精度
/*--修改自定义数据类型精度的示例 自定义数据类型一旦被引用,就不能再修改和删除,如果要修改数据的精度,就非常麻烦,下面的示例演示了如何修改 假设要修改的自定义变量名为aa -- ...
- Ubuntu 16.04设置Redis为开机自动启动服务
继上一篇文章http://www.cnblogs.com/EasonJim/p/7599941.html安装好Redis后,假设文件已经安装到/usr/local/redis目录下.假设我安装的版本为 ...
- Ubuntu 16.04使用sudo apt-get -f install解决依赖时的注意事项(重点)
注意:在觉得软件依赖时,一般使用sudo apt-get -f install,但是也是非常危险的,尤其时一些软件需要删除某些依赖时,会导致原有安装的软件全部卸载.所以使用此命令时要时刻注意输出的这条 ...
- spring依赖注入中获取JavaBean
一.这个接口有什么用? 当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean.换句话说,就是这个类可以 ...
- 彻底来理解下hashmap吧
1.什么叫hashmap? 答:首先是一种map集合,其次呢,它是一种利用hash表来存储的数据结构.所以叫hashmap. 2.hashmap的特点是什么? 答:hashmap的特点是key值不能重 ...