开发中关键的Class和Interface有Channel、Connection、ConnectionFactory、Consumer等,与RabbitMQ相关的开发工作,基本上是围绕Connection和Channel这两个类展开的。

连接RabbitMQ

一个Connection可以创建多个Channel实例,但Channel实例不能在线程间共享,应用程序应该为每一个线程开辟一个Channel。

Channel或者Connection中有个isOpen方法可以用来检测其是否已处于开启状态。但并不推荐使用,这个方法的返回值依赖于shutdownCause的存在,有可能会产生竞争。更多的是捕获ShutdownSignalException,IOException或SocketException等异常判断RabbitMq的连接状态。

实际操作过程中遇到BrokerUnreachableException异常

因为我使用的账号是guest,guest账号默认是不支持远程连接,需要在http://localhost:15672(前提是安装了web插件)的Admin选项卡中添加一个新用户(或者使用命令行添加)。

安装web插件

rabbitmq-plugins enable rabbitmq_management

添加新用户:

sudo rabbitmqctl add_user  user_admin  passwd_admin

如上图所示,新添加的用户没有任何权限,需要点击用户名设置权限。

示例代码:

var factory = new ConnectionFactory {
HostName = "localhost", //主机名
UserName = "mymq", //默认用户名
Password = "123456", //默认密码
RequestedHeartbeat = TimeSpan.FromSeconds(30)
}; using (var connection = factory.CreateConnection())//连接服务器
{
//创建一个通道
using (var channel = connection.CreateModel())
{
channel.QueueDeclare("stacking", false, false, false, null);//创建消息队列
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 1;
string message = "RabbitMQ Test"; //传递的消息内容
channel.BasicPublish("", "stacking", properties, Encoding.UTF8.GetBytes(message)); //生产消息
Console.WriteLine($"Send:{message}");
}
}

在管理界面处看到消息插入成功

使用新加的账号链接MQ还会提示BrokerUnreachableException异常,很纳闷。折腾了半天把WSL升级到WSL2就链接成功。

交换器和队列

交换器和队列是应用层面的构建模块,使用前应对其进行声明确保其存在。

 var exchangeName = "exchange_name";
channel.ExchangeDeclare(exchangeName, "direct", true);//创建一个持久化的、非自动删除的、绑定类型为direct的交换器
var queueName = channel.QueueDeclare().QueueName; //创建一个非持久化的、排他的、自动删除的队列(队列名由RabbitMQ自动生成)
channel.QueueBind(queueName, exchangeName, "routing_key"); //使用路由键(routing_key)将队列和交换器绑定 channel.QueueDeclare("queue_name", true); // QueueDeclare拥有多个重载

ExchangeDeclare方法详解

各个参数详细说明如下:

exchange:交换器的名称。

type:交换器的类型,常见的如fanout、direct、topic。

durable:设置是否持久化。durable设置为true表示持久化,反之是非持久化。持久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息。

autoDelete:设置是否自动删除。autoDelete设置为true则表示自动删除。自动删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑才会删除。

internal:设置是否是内置的。如果设置为true,则表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。

argument:其他一些结构化参数

QueueDeclareNoWait方法实现设置了一个nowait参数(AMQP中Exchange.Declare命令的参数),意思是不需要等待服务区返回结果。

ExchangeDeclarePassive方法用来检测相应的交换器是否存在。如果存在则正常返回;如果不存在则抛出异常。

QueueDeclare方法详解

方法的参数详细说明如下:

queue:队列的名称。

durable:设置是否持久化。为true则设置队列为持久化。持久化的队列会存盘,在服务器重启的时候可以保证不丢失相关信息。

exclusive:设置是否排他。为true则设置队列为排他的。

autoDelete:设置是否自动删除。为true则设置队列为自动删除。自动删除的前提是:至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。

arguments:设置队列的其他一些参数,如x-message-ttl、x-expires、x-max-length、x-max-length-bytes、x-dead-letter-exchange、x-dead-letter-routing-key、x-max-priority等。

如果一个队列被声明为排他队列,则该队列仅对首次声明它的连接可见,并在连接断开时自动删除。需要注意:排他队列是基于连接(Connection)可见的,同一个连接的不同信道(Channel)可以访问同一连接创建的排他队列;“首次”是指如果一个连接已经声明了一个排他队列,其他连接不允许再建立同名的排他队列;即使该队列是持久化的,一旦连接关闭或客户端退出,该排他队列都会被自动删除,这种队列适用于一个客户端同时发送和读取消息的应用场景。

QueueDeclareNoWait方法实现设置了一个nowait参数,意思是不需要等待服务区返回结果。

QueueDeclarePassive方法用来检测相应的队列是否存在。如果存在则正常返回;如果不存在则抛出异常。

QueueBind方法详解

方法中涉及的参数:

queue:队列名称;

exchange:交换器的名称;

routingKey:用来绑定队列和交换器的路由键;

argument:定义绑定的一些参数。

ExchangeBind方法详解

不仅可以将交换器与队列绑定,也可以将交换器与交换器绑定。绑定之后,消息从source交换器转发到destination交换器

方法中涉及的参数:

destination:目的交换器名;

source:源交换器的名称;

routingKey:用来绑定队列和交换器的路由键;

argument:定义绑定的一些参数。

交换器的使用并不会真正耗费服务器的性能,而队列会。要衡量RabbitMQ当前的QPS只需看队列的即可。

发送消息

BasicPublish方法用来发送一条消息到。为了更好地控制发送,可以使用mandatory这个参数

对应的具体参数解释如下:

exchange:交换器的名称,指明消息需要发送到哪个交换器中。如果设置为空字符串,则消息会被发送到RabbitMQ默认的交换器中。

routingKey:路由键,交换器根据路由键将消息存储到相应的队列之中。

basicProperties:消息的基本属性集,其包含14个属性成员,分别有contentType、contentEncoding、headers(Map<String,Object>)、deliveryMode、priority、correlationId、replyTo、expiration、messageId、timestamp、type、userId、appId、clusterId。

byte[] body:消息体(payload),真正需要发送的消息。

mandatory:设置为true时,如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,会调用basic.return方法将消息返还给生产者;设为false时,出现上述情形broker会直接将消息扔掉。

丰富了一下第一部分的代码:

var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 1;
properties.Priority = 2;
properties.ContentType = "text/plain";
properties.Expiration = "60000";
string message = "RabbitMQ Test"; //传递的消息内容
channel.BasicPublish("", "stacking", properties, Encoding.UTF8.GetBytes(message)); //生产消息

消费消息

RabbitMQ的消费模式分两种:推(Push)模式和拉(Pull)模式。推模式采用Basic.Consume进行消费,而拉模式则是调用Basic.Get进行消费。

推模式

推模式接收消息需要实例化一个EventingBasicConsumer类,订阅Received事件来接收消息。EventingBasicConsumer实现了DefaultBasicConsumer类,实际使用中如果不满足需求可以继承该类。

示例代码:

var consumer = new EventingBasicConsumer(channel);
consumer.Received += (ch, ea) =>
{
var body = ea.Body.ToArray();
Console.WriteLine($"Received:{Encoding.UTF8.GetString(body)}");
channel.BasicAck(ea.DeliveryTag, false);
};
var consumerTag = channel.BasicConsume("stacking", false, consumer);

BasicConsume方法对应的参数说明如下:

queue:队列的名称;

autoAck:设置是否自动确认。建议设成false,即不自动确认;

consumerTag:消费者标签,用来区分多个消费者;

arguments:设置消费者的其他参数;

callback:设置消费者的回调函数。

BasicConsume返回字符串类型consumerTag,可以通过调用channel.BasicCancel(consumerTag)显式地取消一个消费者的订阅。BasicCancel方法会首先触发HandleConsumerOk方法,之后触发HandleDelivery方法,最后才触发HandleCancelOk方法.

拉模式

拉模式通过channel.BasicGet方法可以单条地获取消息。

示例代码:

var result = channel.BasicGet("stacking",false);
Console.WriteLine($"Received:{Encoding.UTF8.GetString(result.Body.ToArray())}");
channel.BasicAck(result.DeliveryTag, false);

Basic.Consume将信道(Channel)置为接收模式,直到取消队列的订阅,RabbitMQ会不断地推送消息给消费者,当然推送消息的个数还是会受到Basic.Qos的限制。如果只想从队列获得单条消息而不是持续订阅,建议使用Basic.Get进行消费。但是不能将Basic.Get放在一个循环里来代替Basic.Consume,这样做会严重影响RabbitMQ的性能。

消费端的确认与拒绝

为了保证消息从队列可靠地达到消费者,RabbitMQ提供了消息确认机制(message acknowledgement)。消费者在订阅队列时指定autoAck参数,当autoAck为false时,RabbitMQ会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上是先打上删除标记,之后再删除)。当autoAck为true时,RabbitMQ会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正地消费到了这些消息。

当autoAck参数设置为false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题。

当autoAck参数设置为false,对于RabbitMQ服务端而言,队列中的消息分为两部分:一部分是等待投递给消费者的消息;一部分是已投递给消费者,但是还没有收到消费者确认信号的消息。如果RabbitMQ一直没有收到消费者的确认信号,并且此消息的消费者断开连接,则RabbitMQ会安排该消息重新进入队列,等待投递给下一个消费者(可能还是原来的那个消费者 ),并且RabbitMQ不会为未确认的消息设置过期时间。

消费消息时autoAck参数设置为false需要主动调用channel.BasicAck对消息进行确认,以便RabbitMQ删除消息,对应的也可以调用channel.BasicReject方法拒绝消息,由其他消费端处理或者丢弃。

deliveryTag可以看作消息的编号。如果requeue参数设置为true,则RabbitMQ会重新将这条消息存入队列;如果requeue参数设置为false,则RabbitMQ立即会把消息从队列中移除,不会把它发送给新的消费者。

Basic.Reject命令一次只能拒绝一条消息,如果想要批量拒绝消息,则可以使用Basic.Nack这个命令,对应的实现方法为channel.BasicNack.

其中deliveryTag和requeue的含义可以参考BasicReject方法。multiple参数设置为false则表示仅拒绝编号为deliveryTag的单条消息;multiple参数设置为true则表示拒绝deliveryTag编号之前所有未被当前消费者确认的消息。

channel.BasicReject或者channel.BasicNack中的requeue设置为false,可以启用“死信队列”的功能。死信队列可以通过检测被拒绝或者未送达的消息来追踪问题。

关闭连接

可以显示的调用Connection和Channel的Close方法来关闭连接,也可以借助using来管理连接。

Connection和Channel所具备的生命周期如下:

Open:开启状态,代表当前对象可以使用。

Closing:正在关闭状态。当前对象被显式地通知调用关闭方法(shutdown),这样就产生了一个关闭请求让其内部对象进行相应的操作,并等待这些关闭操作的完成。

Closed:已经关闭状态。当前对象已经接收到所有的内部对象已完成关闭动作的通知,并且其也关闭了自身。

在Connection和Channel中都定义了对应实现监听状态的改变。

Connection

Channel

Github

示例代码地址:https://github.com/MayueCif/RabbitMQ

.Net RabbitMQ实战指南——客户端开发的更多相关文章

  1. 【RabbitMQ 实战指南】一 RabbitMQ 开发

    1.RabbitMQ 安装 RabbitMQ 的安装可以参考官方文档:https://www.rabbitmq.com/download.html 2.管理页面 rabbitmq-management ...

  2. 【RabbitMQ 实战指南】一 延迟队列

    1.什么是延迟队列 延迟队列中存储延迟消息,延迟消息是指当消息被发送到队列中不会立即消费,而是等待一段时间后再消费该消息. 延迟队列很多应用场景,一个典型的应用场景是订单未支付超时取消,用户下单之后3 ...

  3. .Net RabbitMQ实战指南——RabbitMQ相关概念介绍

    什么是消息中间件 消息(Message)是指在应用间传送的数据.消息可以非常简单,比如只包含文本字符串.JSON等,也可以很复杂,比如内嵌对象. 消息队列中间件(Message Queue Middl ...

  4. RabbitMQ基础系列--客户端开发

    Ⅰ.高层接口 ConnectionFactory Connection Channel Consumor Ⅱ.操作流程及API [一]创建连接工厂ConnectionFactory Connectio ...

  5. 【RabbitMQ 实战指南】一 RabbitMQ入门

    1.消息中间件 1.1.什么是消息中间件 消息中间件(Message Queue Middleware,简称 MQ)是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通道来进行分布式系 ...

  6. .Net RabbitMQ实战指南——进阶(二)

    持久化 持久化可以提高RabbitMQ的可靠性,防止异常情况下的数据丢失.RabbitMQ的持久化分为三个部分:交换器的持久化.队列的持久化和消息的持久化. 交换器的持久化通过声明队列时将durabl ...

  7. .Net RabbitMQ实战指南——服务日志

    RabbitMQ的日出输入方式有很多种:file.console .syslog .exchange. 在RabbitMQ中,日志级别有none(0).critical(4).error(8).war ...

  8. Modbus软件开发实战指南 之 开发自己的Modbus Poll工具 - 1

    在开发Modbus程序的过程中,也可以发现经常需要使用诸如Modbus Poll和Modbus Slave等辅助调试工具, 用于验证MODBUS通讯消息是否正确.但是,Modbus Poll和Modb ...

  9. Modbus软件开发实战指南 之 开发自己的Modbus Poll工具 - 2

    接上一篇文章的内容. 看了前面需求提到的复杂的命令行解析功能,很多人立马开始发怵,其实大可不必. 我们都知道,Linux下的程序往往都提供了复杂的命令行参数处理机制,因为这是与 其他程序或用户进行交互 ...

随机推荐

  1. JAVAEE_Servlet_04_在service()方法中连接数据库获取表信息

    在service()方法中连接数据库获取表信息 代码: package com.shige.controller; import javax.servlet.*; import java.io.IOE ...

  2. Day13_65_线程sleep()方法

    线程sleep()方法 * public static void sleep​(long millis) throws InterruptedException * Thread.sleep(),该方 ...

  3. Day03_16_递归

    Java递归 递归包含两个部分 递归头: 标明了什么时候结束递归调用,如果没有递归头,程序将陷入死循环. 递归体: 标明了什么时候需要继续调用自身. 实例 import java.util.Scann ...

  4. JDK8新特性(一) Lambda表达式及相关特性

    函数式接口 函数式接口是1.8中的新特性,他不属于新语法,更像是一种规范 面向对象接口复习 在这里先回顾一下面向对象的接口,创建接口的关键字为interface,这里创建一个日志接口: public ...

  5. Python爬取笔趣阁小说,有趣又实用

    上班想摸鱼?为了摸鱼方便,今天自己写了个爬取笔阁小说的程序.好吧,其实就是找个目的学习python,分享一下. 1. 首先导入相关的模块 import os import requests from ...

  6. 【Redis连接超时】记录线上RedisConnectionFailureException异常排查过程

    项目架构: 部分组件如下: SpringCloudAlibaba(Nacos+Gateway+OpenFeign)+SpringBoot2.x+Redis 问题背景: 最近由于用户量增大,在高峰时期, ...

  7. 从苏宁电器到卡巴斯基第30篇:难忘的三年硕士时光 VIII

    自给自足 临近毕业答辩,别的导师的学生基本上都完成了各自的论文,也都开始交由第三方进行审核.而我们导师由于情况特殊,还没有机会看我们的论文,所以我们也打算和老师约一个时间,来给我们的论文提点意见,修改 ...

  8. hdu 5020 求三点共线的组合数(容器记录斜率出现次数)

    题意:       给你n个点,问你3点共线的组合数有多少,就是有多少种组合是满足3点共线的. 思路:      一开始抱着试1试的态度,暴力了一个O(n^3),结果一如既往的超时了,然后又在刚刚超时 ...

  9. POJ1722二维spfa+优先队列优化

    题意:      给你一个有向图,然后求从起点到终点的最短,但是还有一个限制,就是总花费不能超过k,也就是说每条边上有两个权值,一个是长度,一个是花费,求满足花费的最短长度. 思路:       一开 ...

  10. NTDDK 从两个最简单的驱动谈起

    第 1 章 从两个最简单的驱动谈起 Windows 驱动程序的编写,往往需要开发人员对 Windows 内核有深入了解和大量的内 核调试技巧,稍有不慎,就会造成系统的崩溃.因此,初次涉及 Window ...