RabbitMQ无疑是目前最流行的消息队列之一,对各种语言环境的支持也很丰富,作为一个.NET developer有必要学习和了解这一工具。消息队列的使用场景大概有3种:

1、系统集成,分布式系统的设计。各种子系统通过消息来对接,这种解决方案也逐步发展成一种架构风格,即“通过消息传递的架构”。

2、当系统中的同步处理方式严重影响了吞吐量,比如日志记录。假如需要记录系统中所有的用户行为日志,如果通过同步的方式记录日志势必会影响系统的响应速度,当我们将日志消息发送到消息队列,记录日志的子系统就会通过异步的方式去消费日志消息。

3、系统的高可用性,比如电商的秒杀场景。当某一时刻应用服务器或数据库服务器收到大量请求,将会出现系统宕机。如果能够将请求转发到消息队列,再有服务器去消费这些消息将会使得请求变得平稳,提高系统的可用性。

一、开始使用RabbitMQ

RabbitMQ官网提供了详细的安装步骤,另外官网还提供了RabbitMQ在六种场景的使用教程。其中教程1、3、6将覆盖99%的使用场景,所以正常来说只需要搞清楚这3个教程即可快速上手。

二、简单分析

我们以官方提供的教程1做个简单梳理:该教程展示了Producer如何向一个消息队列(message queue)发送一个消息(message),消息消费者(Consumer)收到该消息后消费该消息。

1、producer端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var factory = new ConnectionFactory() { HostName = "localhost" };
 using (var connection = factory.CreateConnection())
 {
     while (Console.ReadLine() != null)
     {
         using (var channel = connection.CreateModel())
         {
             //创建一个名叫"hello"的消息队列
             channel.QueueDeclare(queue: "hello",
                 durable: false,
                 exclusive: false,
                 autoDelete: false,
                 arguments: null);
 
             var message = "Hello World!";
             var body = Encoding.UTF8.GetBytes(message);
 
             //向该消息队列发送消息message
             channel.BasicPublish(exchange: "",
                 routingKey: "hello",
                 basicProperties: null,
                 body: body);
             Console.WriteLine(" [x] Sent {0}", message);
         }
     }
 }

该段代码非常简单,几乎到了无法精简的地步:创建了一个信道(channel)->创建一个队列->向该队列发送消息。

2、Consumer端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var factory = new ConnectionFactory() { HostName = "localhost" };
 using (var connection = factory.CreateConnection())
 {
     using (var channel = connection.CreateModel())
     {
         //创建一个名为"hello"的队列,防止producer端没有创建该队列
         channel.QueueDeclare(queue: "hello",
                              durable: false,
                              exclusive: false,
                              autoDelete: false,
                              arguments: null);
 
         //回调,当consumer收到消息后会执行该函数
         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);
         };
 
         //消费队列"hell"中的消息
         channel.BasicConsume(queue: "hello",
                              noAck: true,
                              consumer: consumer);
 
         Console.WriteLine(" Press [enter] to exit.");
         Console.ReadLine();
     }
 }

该段代码可以理解为:创建信道->创建队列->定义回调函数->消费消息。

该实例可以简单理解为1(producer) VS 1(consumer)的场景;

实例3则描述了publish/subscriber模式,即1(producer) VS 多个(consumer);

在以上两个示例中,producer只需要发送消息即可,并不关心consumer的返回结果。实例6则描述了一个RPC调用场景,producer发送消息后还要接收consumer的返回结果,这一场景看起来跟使用消息队列的目的有点相悖。因为使用消息队列的目的之一就是要异步,但是这一场景似乎又将异步变成了同步,不过这一场景也很有用,比如一个用户操作产生了一个消息,应用服务收到该消息后执行了一些逻辑并使得数据库发生了变化,UI会一直等待应用服务的返回结果才刷新页面。

三、 发现抽象

我桌子上放着一本RabbitMQ in Action,另外官网提供的文档也很详细,我感觉在一个月内我就能精通RabbitMQ,到时候简历上又可以写上“精通…”,感觉有点小得意呢... ,但是我知道这并不是使用RabbitMQ的最佳方式。

我们知道抽象可以帮我们隐藏掉一些技术细节,让我们将重心放在核心业务上,比如一个人问你:“大雁塔如何走?”你的回答可能是“小寨往东,一直走两站,右手边”,如果你回答:“右转45度,向前走100米,再转90度…”,对方就会迷失在这些细节中。

消息队列的使用过程中实际隐藏着一种抽象——服务总线(Service Bus)。

我们在回头看第一个例子,这个例子隐含的业务是:ClientA发送一个指令,ClientB收到该指令后做出反应。如果是这样,我们为什么要关心如何创建channel,如何创建一个queue? 我仅仅是要发送一个消息而已。另外这个例子写的其实不够健壮:

没有重试机制:如果ClientB第一次没有执行成功如何对该消息处理?

没有错误处理机制:如果ClientB在重试了N次之后还是异常如何处理该消息?

没有熔断机制;

如何对ClientA做一个schedule(计划安排),比如定时发送等;

没有消息审计机制;

无法对消息的各个状态做追踪;

事物处理等。

服务总线正是这种场景的抽象,并且为我们提供了这些机制,让我们赶快来看个究竟吧。

四、初视MassTransit

MassTransit是.NET平台下的一款开源免费的ESB产品,官网:http://masstransit-project.com/,GitHub 700 star,500 Fork,类似的产品还有NServiceBus,之所以要选用MassTransit是因为他要比NServiceBus轻量级,另外在MassTransit开发之初就选用了RabbitMQ作为消息传输组建;同时我想拿他跟NServiceBus做个比较,看看他们到底有哪些侧重点。

1、新建控制台应用程序:Masstransit.RabbitMQ.GreetingClient

使用MassTransit可以从Nuget中安装:

1
Install-Package MassTransit.RabbitMQ

2、创建服务总线,发送一个命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void Main(string[] args)
{
    Console.WriteLine("Press 'Enter' to send a message.To exit, Ctrl + C");
 
    var bus = BusCreator.CreateBus();
    var sendToUri = new Uri($"{RabbitMqConstants.RabbitMqUri}{RabbitMqConstants.GreetingQueue}");
 
    while (Console.ReadLine()!=null)
    {
        Task.Run(() => SendCommand(bus, sendToUri)).Wait();
    }
 
    Console.ReadLine();
}
 
private static async void SendCommand(IBusControl bus,Uri sendToUri)
{
    var endPoint =await bus.GetSendEndpoint(sendToUri);
    var command = new GreetingCommand()
    {
        Id = Guid.NewGuid(),
        DateTime = DateTime.Now
    };
 
    await endPoint.Send(command);
 
    Console.WriteLine($"send command:id={command.Id},{command.DateTime}");
}

这一段代码隐藏了众多关于消息队列的细节,将我们的注意力集中在发送消息上,同时ServiceBus提供的API也更接近业务,我们虽然发送的是一个消息,但是在这种场景下体现出来是一个命令,Send(command)这一API描述了我们的意图。

3、服务端接收这一命令

新建一个命令台控制程序:Masstransit.RabbitMQ.GreetingServer

1
2
3
4
5
6
7
8
var bus = BusCreator.CreateBus((cfg, host) =>
{
    cfg.ReceiveEndpoint(host, RabbitMqConstants.GreetingQueue, e =>
    {
        e.Consumer<GreetingConsumer>();
 
    });
});

这一代码可以理解为服务端在监听消息,我们在服务端注册了一个名为“GreetingConsumer”的消费者,GreetingConsumer的定义:

1
2
3
4
5
6
7
8
public class GreetingConsumer :IConsumer<GreetingCommand>
{
    public async Task Consume(ConsumeContext<GreetingCommand> context)
    {
 
        await Console.Out.WriteLineAsync($"receive greeting commmand: {context.Message.Id},{context.Message.DateTime}");
    }
}

该consumer可以消费类型为GreetingCommand的消息。这一实例几乎隐藏了有关RabbitMQ的技术细节,将代码中心放在了业务中,将这两个控制台应用跑起来试试:

五、实现Publish/Subscribe模式

发布/订阅模式使得基于消息传递的软件架构成为可能,这一能力表现为ClientA发送消息X,ClientB和ClientC都可以订阅消息X。

1、我们在上面的例子中改造一下,当GreetingConsumer收到GreetingCommand后发送一个GreetingEvent:

1
2
3
4
5
6
7
var greetingEvent = new GreetingEvent()
 {
     Id = context.Message.Id,
     DateTime = DateTime.Now
 };
 
 await context.Publish(greetingEvent);

2、新建控制台程序Masstransit.RabbitMQ.GreetingEvent.SubscriberA用来订阅GreetingEvent消息:

1
2
3
4
5
6
7
8
9
var bus = BusCreator.CreateBus((cfg, host) =>
 {
     cfg.ReceiveEndpoint(host, RabbitMqConstants.GreetingEventSubscriberAQueue, e =>
     {
         e.Consumer<GreetingEventConsumer>();
     });
 });
 
 bus.Start();

定义GreetingEventConsumer:

1
2
3
4
5
6
7
public class GreetingEventConsumer:IConsumer<Greeting.Message.GreetingEvent>
 {
     public async Task Consume(ConsumeContext<Greeting.Message.GreetingEvent> context)
     {
         await Console.Out.WriteLineAsync($"receive greeting event: id {context.Message.Id}");
     }
 }

这一代码跟Masstransit.RabbitMQ.GreetingServer接受一个命令几乎一模一样,唯一的区别在于:

在Send/Receive模式中Client首先要获得对方的终结点(endpoint),直接向该终结点发送命令。Server方监听自己的终结点并消费命令。

而Publish/Subscribe模式中Client publish一个事件,SubscriberA在自己的终结点(endpointA)监听事件,SubscriberB在自己的终结点(endpointB)监听事件。

3、根据上面的分析再定义一个Masstransit.RabbitMQ.GreetingEvent.SubscriberB

4、将4个控制台应用程序跑起来看看

六、实现RPC模式

这一模式在Masstransit中被称作Request/Response模式,通过IRequestClient<ISimpleRequest, ISimpleResponse> 接口来实现相关操作。一个相关的例子在官方的github

结束语:本篇文章分析了如何使用Masstransit来抽象业务,避免直接使用具体的消息队列,当然本文提到的众多服务总线机制,如“重试、熔断等”并没有在该文中出现,需要大家进一步去了解该项目。

通过对Masstransit的一些试用和NServiceBus的对比,Masstransit在实际项目中很容易上手并且免费,各种API定义的也非常清晰,但是官方的文档有点过于简单,实际使用中还需要去做深入的研究。作为.NET平台下为数不多的ESB开源产品,其关注程度还是不够,期待大家为开源项目做出贡献。

原文https://blog.csdn.net/fhzh520/article/details/52688336

(转)如何优雅的使用rabbit mq的更多相关文章

  1. 在 Windows 上安装Rabbit MQ 指南

    rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统.他遵循Mozilla Public License开源协议.采用 Erlang 实现的工业级的消息队列(MQ)服务器. Ra ...

  2. windows下的php rabbit mq安装、配置

    http://www.cnblogs.com/shanyou/p/4067250.html 这篇博文写的rabbit mq和erlang的安装以及rabbitmq可视化插件的一些操作 接下去开始安装P ...

  3. (转)在 Windows 上安装Rabbit MQ 指南

    rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统.他遵循Mozilla Public License开源协议.采用 Erlang 实现的工业级的消息队列(MQ)服务器. Ra ...

  4. celery rabbit mq 详解

    Celery介绍和基本使用 Celery 是一个 基于python开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理, 如果你的业务场景中需要用到异步任务,就可以考虑使用celery, ...

  5. Rabbit MQ 消息确认和持久化机制

    一:确认种类 RabbitMQ的消息确认有两种.一种是消息发送确认,用来确认生产者将消息发送给交换器,交换器传递给队列的过程中消息是否成功投递.发送确认分为两步,一是确认是否到达交换器,二是确认是否到 ...

  6. celery+Rabbit MQ实战记录

    基于以前的一篇文章,celery+Rabbit MQ的安装和使用, 本文更加详细的介绍如何安装和使用celey, Rabbit MQ. 并记录在使用celery时遇到的一些问题. 1.安装 Rabbi ...

  7. Rabbit MQ 入门指南

    rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统.他遵循Mozilla Public License开源协议.采用 Erlang 实现的工业级的消息队列(MQ)服务器. Ra ...

  8. .NET中使用Rabbit MQ

    1.通过Nuget 获取Rabbit MQ NET client bindings from NuGet: PM> Install-Package RabbitMQ.Client 2.发送者(生 ...

  9. Rabbit MQ config文件解析

    Rabbit MQ config文件解析 tcp_listeners:用于监听AMQP连接的端口或主机名/对(不带TLS),默认端口:5672 2.numtcpacceptors :将接受TCP侦听器 ...

随机推荐

  1. python网络编程TCP服务多客户端的服务端开发

    #服务多客户端TCP服务端开发 2 #方法说明 3 """ 4 bind(host,port)表示绑定端口号,host是ip地址,ip地址一般不进 行绑定,表示本机的任何 ...

  2. gsoap多wsdl集成

    gsoap常规用法: 通过wsdl文件创建头文件 //通过wsdl文件创建头文件 wsdl2h [options] -o file.h ... WSDL and XSD files or URLs t ...

  3. restful设计风格

    restful是一种软件设计风格,并不是标准,它只是提供了一组设计原则和约束条件. ① restful 提倡面向资源编程,url接口尽量要使用名词,不要使用动词 ② 在url中可以体现版本号 ③可以根 ...

  4. PAT (Advanced Level) Practice 1027 Colors in Mars (20 分) 凌宸1642

    PAT (Advanced Level) Practice 1027 Colors in Mars (20 分) 凌宸1642 题目描述: People in Mars represent the c ...

  5. 一文教你搞懂 Go 中栈操作

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/513 本文使用的go的源码15.7 知识点 LInux 进程在内存布 ...

  6. 启用reuse_port参数让Nginx性能提升3倍

    为什么启用 reuse_port 记得 2008 年做性能测试的时候,新进7台 lenovo 4核4G 服务器用于性能测试. 当时资源紧张,这7台服务器都装了双系统(Win2003/CentOS5)空 ...

  7. [Fundamental of Power Electronics]-PART I-4.开关实现-4.1 开关应用

    4.1 开关应用 4.1.1 单象限开关 理想的SPST(Single pole single throw)开关如图4.1所示.开关包含电源端子1和0,其电流和电压极性如图所示.在接通状态下,电压\( ...

  8. 北航OO第二单元作业总结(2.1~2.3)

    在经过第一单元初步认识面向对象编程思想后,本蒟蒻开始了第二单元--多线程部分的学习.本单元的作业是构造符合条件的"目的选层电梯"模型,自行设计调度算法,进行合理调度,完成所有乘客的 ...

  9. c++排序相关的参数“cmp“的用法及理解

    对sort函数(需要algorithm头文件),它的cmp可以是"函数",也可以是"对象" bool myfunction (int i,int j) { re ...

  10. Hadoop完整搭建过程(一):本地模式

    1 本地模式 本地模式是最简单的模式,所有模块都运行在一个JVM进程中,使用本地文件系统而不是HDFS. 本地模式主要是用于本地开发过程中的运行调试用,下载后的Hadoop不需要设置默认就是本地模式. ...