一、简单生产者-消费者(使用direct交换器)

1、生产者

var factory = new ConnectionFactory();//实例化一个工厂
factory.HostName = "localhost";
factory.UserName = "honnnnl";
factory.Password = "honnnnl"; using (var connection = factory.CreateConnection())//用工厂创建连接器
using (var channel = connection.CreateModel()) //创建信道
{
//在Rabbit服务上声明消息队列。如果不存在,自动创建。
channel.QueueDeclare(
queue: "test", //消息队列名称
durable: false,//消息队列是否持久化
exclusive: false,//消息队列是否被本次连接connection独享。(本次连接connection创建的信道可以共用).排外的queue在当前连接被断开的时候会自动消失(清除)无论是否设置了持久化.
autoDelete: false,//消息队列是否自动删除。也就是说queue会清理自己,但是是在最后一个connection断开的时候。
arguments: null);//参数对 //创建一条消息,并转为字节数组。
string message = "Hello World";
var body = Encoding.UTF8.GetBytes(message); //发布消息。。 交换器,路由键,
channel.BasicPublish("", "test", null, body);//注意路由键在用direct交换器时,要指定为队列名 Console.WriteLine($"发送: {message}"); }

2、消费者

var factory = new ConnectionFactory();//实例化连接器创建工厂
factory.HostName = "localhost";
factory.UserName = "honnnnl";
factory.Password = "honnnnl"; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("test", false, false, false, null); var consumer = new EventingBasicConsumer(channel);//实例化一个事件型消费者 //订阅消费者接收消息的事件
consumer.Received += (model, ea) =>
{
//获取并解析数据
var body = ea.Body;
var message = Encoding.UTF8.GetString(body); Console.WriteLine($"收到: {message}");
}; //信道开始消费。。 消息队列名称, 是否自动回复响应, 消费者
channel.BasicConsume(queue: "test", autoAck: true, consumer: consumer); }

二、简单任务队列

1、任务发布者

主要代码与第一节的生产者代码一样。。只不过需要将发给工作者执行的任务放到消息里。

2、工作者

主要代码与第一节的消费者代码一样。。只不过工作者要解析任务,执行任务。

默认RabbitMQ会将每个消息按照顺序依次分发给下一个消费者(工作者)。所以每个消费者接收到的消息个数大致是平均的。 这种消息分发的方式称之为轮询(round-robin)。

使用工作队列的一个好处就是它能够并行处理队列。如果任务发布的快工作者处理的慢,堆积了很多任务,我们只需要添加更多的工作者(workers)——再打开几个工作者进程就可以了,扩展很简单。

四、消息响应

1、为什么、如何进行消息确认

当处理一个比较耗时得任务的时候,也许想知道消费者(consumers)是否运行到一半就挂掉。在上面的例子中,当RabbitMQ将消息发送给消费者(consumers)之后,马上就会将该消息从队列中移除。此时,如果把处理这个消息的工作者(worker)停掉,正在处理的这条消息就会丢失。同时,所有发送到这个工作者的还没有处理的消息都会丢失。

我们不想丢失任何任务消息。如果一个工作者(worker)挂掉了,我们希望该消息会重新发送给其他的工作者(worker)。

为了防止消息丢失,RabbitMQ提供了消息响应(acknowledgments)机制。也就是说消费者接收到的每一条消息都必须进行确认。要么消费者调用BasicAck()方法显式地向RabbitMQ发送一个确认,要么当初在调用BasicConsume()开始消费消息队列时,将autoAct参数设置为true。总之消费者通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ才会释放并删除这条消息。

  方式(1)写代码调用BasicAck()方法

//在工作者代码,处理消息后
channel.BasicAck(ea.DeliveryTag, false);//响应给RabbitMQ服务:收到并处理了消息。

  方式(2)调用BasicConsume()时,将autoAct参数设置为true

  当autoAct设置为true时,一旦消费者接收到消息,RabbitMQ自动视为其完成消息确认。

如果消费者(consumer)挂掉前没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,即使工作者(workers)偶尔的挂掉,也不会丢失消息。

消息是没有超时这个概念的;当工作者与它断开连的时候,RabbitMQ会重新发送消息。这样在处理一个耗时非常长的消息任务的时候就不会出问题了。

消息响应默认是开启的。在之前的例子中使用了autoAck=True标识把它关闭。现在是时候移除这个标识了,当工作者(worker)完成了任务,就发送一个响应。

1 //以下在工作者代码中
2 //信道开始消费。。 消息队列名称, 是否自动回复响应, 消费者
3 channel.BasicConsume(queue: "test", autoAck: false, consumer: consumer);

2、注意

(1)需要记住,消费者对消息的确认(消费者对RabbitMQ)和告诉生产者消息已经被接收(RabbitMQ对生产者),这两件事毫不相干。

(2)如果消费者收到一条消息,然后在确认之前从RabbitMQ断开连接(或者从队列上取消订阅),RabbitMQ会认为这条消息没有分发成功,然后重新分发给下一个订阅的消费者。如果你的应用程序(消费者)崩溃了,这种机制可以确保消息会被发送给另一个消费者进行处理。

  另一方面假如你的应用程序(消费者)存在bug,比如忘记确认消息,RabbitMQ将不会再向该消费者发送更多消息(但不影响发给其他消费者)。这是因为在上一条消息被确认前,RabbitMQ会认为这个消费者并没有准备好接收下一条消息。你可以好好利用这一点,如果处理消息内容非常耗时,则你的应用程序应该处理完成再确认。这样可以防止RabbitMQ持续不断的消息涌向你的应用程序(消费者)而导致过载。。。

3、拒绝消费消息

你的消费者在收到消息后,如果遇到某种问题无法处理消息,但仍希望其他消费者处理它。你可以调用BasicReject()方法拒收该消息,方法参数requeue设置为true,RabbitMQ会将消息重新发送给下一个订阅的消费者。如果设置为false,RabbitMQ立即从消息队列中移除它(成为死信),而不会把它发给新的消费者。

如果你的消费者收到一条消息,检测到消息格式错误(意味着其他消费者也不可能解析处理),实际上你应该直接BasicAck()确认,而不做处理(应该记录到错误日志中或者报错)。这么干(直接BasicAck()确认)确认比较节省大家时间、资源。(就不要再你拒收,再让MQ发给其他消费者,其他消费者也得拒收... 这不是浪费时间么。)

4、死信队列dead letter

当你的消费者调用BasicReject()方法拒收消息,且方法参数requeue设置为false时,RabbitMQ将消息从消息队列中移除,并存入死信队列(拒收或未送达的消息)。死信队列提供给消费者发现问题的途径。

五、消息持久化

前面已经搞定了即使消费者down掉,任务也不会丢失,但是,如果RabbitMQ Server停掉了,那么这些消息还是会丢失。当RabbitMQ Server 关闭或者崩溃,那么里面存储的队列和消息默认是不会保存下来的。

如果要让RabbitMQ保存住消息,需要在两个地方同时设置:需要保证队列和消息都是持久化的。

首先,要保证RabbitMQ不会丢失队列,所以要做如下设置:

bool durable = true;
channel.QueueDeclare("hello", durable, false, false, null);
虽然在语法上是正确的,但是在目前阶段是不正确的,因为我们之前已经定义了一个非持久化的hello队列。RabbitMQ不允许我们使用不同的参数重新定义一个已经存在的同名队列,如果这样做就会报错。现在,定义另外一个不同名称的队列:

bool durable = true;
channel.queueDeclare("task_queue", durable, false, false, null);
queueDeclare 这个改动需要在发送端和接收端同时设置。

现在保证了task_queue这个消息队列即使在RabbitMQ Server重启之后,队列不会丢失。 下面需要保证消息也是持久化的, 这可以通过设置IBasicProperties.SetPersistent 为true来实现:

var properties = channel.CreateBasicProperties();
properties.SetPersistent(true);
需要注意的是,将消息设置为持久化并不能完全保证消息不丢失。虽然他告诉RabbitMQ将消息保存到磁盘上,但是在RabbitMQ接收到消息和将其保存到磁盘上这之间仍然有一个小的时间窗口。 RabbitMQ 可能只是将消息保存到了缓存中,并没有将其写入到磁盘上。持久化是不能够一定保证的,但是对于一个简单任务队列来说已经足够。如果需要消息队列持久化的强保证,可以使用publisher confirms功能。

六、 公平分发

你可能也注意到了,分发机制不是那么优雅。默认状态下,RabbitMQ将第n个Message分发给第n个消费者。当然n是取余后的。它不管消费者是否还有unacked Message,只是按照这个默认机制进行分发。

那么如果有个消费者工作比较重,那么就会导致有的消费者基本没事可做,有的消费者却是毫无休息的机会。那么,RabbitMQ是如何处理这种问题呢?

通过 BasicQos 方法设置prefetchCount = 1。这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。Qos即服务质量 。设置方法如下:

channel.BasicQos(0, 1, false);

注意,这种方法可能会导致queue满。当然,这种情况下你可能需要添加更多的消费者,或者创建更多的virtualHost来细化你的设计。

RabbitMQ及其.NET客户端——几个小例子的更多相关文章

  1. java即时通信小例子

    学习java一段时间了,今天写来一个即时通信的小例子练手在其过程中也学到了一些知识拿出来和大家分享,请路过的各位大神多多赐教... 好了下面讲一下基本的思路: 首先,编写服务器端的程序,简单点说吧就是 ...

  2. ASP.NET Cookie对象到底是毛啊?(简单小例子)

    记得刚接触asp.net的时候,就被几个概念搞的头痛不已,比如Request,Response,Session和Cookie.然后还各种在搜索引擎搜,各种问同事的,但是结果就是自己还是很懵的节奏. 那 ...

  3. C#写的客户端连接 php的服务器端的小例子

    C#写的客户端连接 php的服务器端的小例子 php的server 端 <?php // server.php set_time_limit( 0 ); ob_implicit_flush(); ...

  4. c/c++ 继承与多态 文本查询的小例子(智能指针版本)

    为了更好的理解继承和多态,做一个文本查询的小例子. 接口类:Query有2个方法. eval:查询,返回查询结果类QueryResult rep:得到要查询的文本 客户端程序的使用方法: //查询包含 ...

  5. RabbitMQ系列教程之七:RabbitMQ的 C# 客户端 API 的简介(转载)

    RabbitMQ系列教程之七:RabbitMQ的 C# 客户端 API 的简介 今天这篇博文是我翻译的RabbitMQ的最后一篇文章了,介绍一下RabbitMQ的C#开发的接口.好了,言归正传吧. N ...

  6. WebService小例子———

    WebService学习(刚开始) ———————————————————————————————————————————————————————————————————— WebService:跨平 ...

  7. springmvc入门的第一个小例子

    今天我们探讨一下springmvc,由于是初学,所以简单的了解一下 springmvc的流程,后续会持续更新... 由一个小例子来简单的了解一下 springmvc springmvc是spring框 ...

  8. Runtime的几个小例子(含Demo)

    一.什么是runtime(也就是所谓的“运行时”,因为是在运行时实现的.)           1.runtime是一套底层的c语言API(包括很多强大实用的c语言类型,c语言函数);  [runti ...

  9. bootstrap 模态 modal 小例子

    bootstrap 模态 modal  小例子 <html> <head> <meta charset="utf-8" /> <title ...

随机推荐

  1. linux终端下一些“风骚”的按键操作及Linux终端命令

    linux终端下一些"风骚"的按键操作 <backspace>  删除 <ctrl-l>     清空屏幕, 相当于clear tab            ...

  2. 每日技术总结:setInterval,setTimeout,文本溢出,小程序,wepy

    前言: 项目背景:vue,电商,商品详情页 1.倒计时,倒计到0秒时停止 data () { return { n: 10 } }, created () { let int = setInterva ...

  3. js面向对象的选项卡

    前言: 选项卡在项目中经常用到,也经常写,今天在github突然看到一个面向对象的写法,值得收藏和学习. 本文内容摘自github上的 helloforrestworld/javascriptLab  ...

  4. 关于IO重定向

    首先,Unix进程使用文件描述符0,1,2作为标准输入.输出和错误的通道. 其次,当进程请求一个新的文件描述符的时候,系统内核将最低可用的文件描述符赋给它. 第三,文件描述符集合通过exec调用传递, ...

  5. 一次性能优化将filter转换

    有一条SQL性能有问题,在运行计划中发现filter.遇到它要小心了,类似于nestloop.我曾经的blog对它有研究探索运行计划中filter的原理.用exists极易引起filter. 优化前: ...

  6. MySql的事务操作与演示样例

    事务就是一个逻辑工作单元的一系列步骤. 事务是用来保证数据操作的安全性 事务的特征: Atomicity(原子性) Consistency(稳定性,一致性) Isolation(隔离性) Durabi ...

  7. Html表单中遇到的问题

    原文 https://www.jianshu.com/p/4466b8294007 大纲 1.表单提交的方式GET和POST的区别 2.js无法对input的file类型的值进行赋值 3.js获取in ...

  8. 【44.10%】【codeforces 723B】Text Document Analysis

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  9. C#验证手机号

    using System.Text.RegularExpressions; private bool IsMobile(string phoneNo) { return Regex.IsMatch(p ...

  10. 【35.20%】【CF 706D】Vasiliy's Multiset

    time limit per test 4 seconds memory limit per test 256 megabytes input standard input output standa ...