.net平台的rabbitmq使用封装
前言
RabbitMq大家再熟悉不过,这篇文章主要整对rabbitmq学习后封装RabbitMQ.Client的一个分享。文章最后,我会把封装组件和demo奉上。
Rabbitmq的关键术语
1、绑定器(Binding):根据路由规则绑定Queue和Exchange。
2、路由键(Routing Key):Exchange根据关键字进行消息投递。
3、交换机(Exchange):指定消息按照路由规则进入指定队列
4、消息队列(Queue):消息的存储载体
5、生产者(Producer):消息发布者。
6、消费者(Consumer):消息接收者。
Rabbitmq的运作
从下图可以看出,发布者(Publisher)是把消息先发送到交换器(Exchange),再从交换器发送到指定队列(Queue),而先前已经声明交换器与队列绑定关系,最后消费者(Customer)通过订阅或者主动取指定队列消息进行消费。

那么刚刚提到的订阅和主动取可以理解成,推(被动),拉(主动)。
推,只要队列增加一条消息,就会通知空闲的消费者进行消费。(我不找你,就等你找我,观察者模式)
拉,不会通知消费者,而是由消费者主动轮循或者定时去取队列消息。(我需要才去找你)
使用场景我举个例子,假如有两套系统 订单系统和发货系统,从订单系统发起发货消息指令,为了及时发货,发货系统需要订阅队列,只要有指令就处理。
可是程序偶尔会出异常,例如网络或者DB超时了,把消息丢到失败队列,这个时候需要重发机制。但是我又不想while(IsPostSuccess == True),因为只要出异常了,会在某个时间段内都会有异常,这样的重试是没意义的。
这个时候不需要及时的去处理消息,有个JOB定时或者每隔几分钟(失败次数*间隔分钟)去取失败队列消息,进行重发。
Publish(发布)的封装
步骤:初始化链接->声明交换器->声明队列->换机器与队列绑定->发布消息。注意的是,我将Model存到了ConcurrentDictionary里面,因为声明与绑定是非常耗时的,其次,往重复的队列发送消息是不需要重新初始化的。
/// <summary>
/// 交换器声明
/// </summary>
/// <param name="iModel"></param>
/// <param name="exchange">交换器</param>
/// <param name="type">交换器类型:
/// 1、Direct Exchange – 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全
/// 匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的
/// 消息才被转发,不会转发dog.puppy,也不会转发dog.guard,只会转发dog
/// 2、Fanout Exchange – 不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都
/// 会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout
/// 交换机转发消息是最快的。
/// 3、Topic Exchange – 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配一个或多
/// 个词,符号“*”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*”
/// 只会匹配到“audit.irs”。</param>
/// <param name="durable">持久化</param>
/// <param name="autoDelete">自动删除</param>
/// <param name="arguments">参数</param>
private static void ExchangeDeclare(IModel iModel, string exchange, string type = ExchangeType.Direct,
bool durable = true,
bool autoDelete = false, IDictionary<string, object> arguments = null)
{
exchange = exchange.IsNullOrWhiteSpace() ? "" : exchange.Trim();
iModel.ExchangeDeclare(exchange, type, durable, autoDelete, arguments);
} /// <summary>
/// 队列声明
/// </summary>
/// <param name="channel"></param>
/// <param name="queue">队列</param>
/// <param name="durable">持久化</param>
/// <param name="exclusive">排他队列,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,
/// 并在连接断开时自动删除。这里需要注意三点:其一,排他队列是基于连接可见的,同一连接的不同信道是可
/// 以同时访问同一个连接创建的排他队列的。其二,“首次”,如果一个连接已经声明了一个排他队列,其他连
/// 接是不允许建立同名的排他队列的,这个与普通队列不同。其三,即使该队列是持久化的,一旦连接关闭或者
/// 客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景。</param>
/// <param name="autoDelete">自动删除</param>
/// <param name="arguments">参数</param>
private static void QueueDeclare(IModel channel, string queue, bool durable = true, bool exclusive = false,
bool autoDelete = false, IDictionary<string, object> arguments = null)
{
queue = queue.IsNullOrWhiteSpace() ? "UndefinedQueueName" : queue.Trim();
channel.QueueDeclare(queue, durable, exclusive, autoDelete, arguments);
} /// <summary>
/// 获取Model
/// </summary>
/// <param name="exchange">交换机名称</param>
/// <param name="queue">队列名称</param>
/// <param name="routingKey"></param>
/// <param name="isProperties">是否持久化</param>
/// <returns></returns>
private static IModel GetModel(string exchange, string queue, string routingKey, bool isProperties = false)
{
return ModelDic.GetOrAdd(queue, key =>
{
var model = _conn.CreateModel();
ExchangeDeclare(model, exchange, ExchangeType.Fanout, isProperties);
QueueDeclare(model, queue, isProperties);
model.QueueBind(queue, exchange, routingKey);
ModelDic[queue] = model;
return model;
});
} /// <summary>
/// 发布消息
/// </summary>
/// <param name="routingKey">路由键</param>
/// <param name="body">队列信息</param>
/// <param name="exchange">交换机名称</param>
/// <param name="queue">队列名</param>
/// <param name="isProperties">是否持久化</param>
/// <returns></returns>
public void Publish(string exchange, string queue, string routingKey, string body, bool isProperties = false)
{
var channel = GetModel(exchange, queue, routingKey, isProperties); try
{
channel.BasicPublish(exchange, routingKey, null, body.SerializeUtf8());
}
catch (Exception ex)
{
throw ex.GetInnestException();
}
}
下次是本机测试的发布速度截图:

4.2W/S属于稳定速度,把反序列化(ToJson)会稍微快一些。
Subscribe(订阅)的封装
发布的时候是申明了交换器和队列并绑定,然而订阅的时候只需要声明队列就可。从下面代码能看到,捕获到异常的时候,会把消息送到自定义的“死信队列”里,由另外的JOB进行定时重发,因此,finally是应答成功的。
/// <summary>
/// 获取Model
/// </summary>
/// <param name="queue">队列名称</param>
/// <param name="isProperties"></param>
/// <returns></returns>
private static IModel GetModel(string queue, bool isProperties = false)
{
return ModelDic.GetOrAdd(queue, value =>
{
var model = _conn.CreateModel();
QueueDeclare(model, queue, isProperties); //每次消费的消息数
model.BasicQos(, , false); ModelDic[queue] = model; return model;
});
} /// <summary>
/// 接收消息
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="queue">队列名称</param>
/// <param name="isProperties"></param>
/// <param name="handler">消费处理</param>
/// <param name="isDeadLetter"></param>
public void Subscribe<T>(string queue, bool isProperties, Action<T> handler, bool isDeadLetter) where T : class
{
//队列声明
var channel = GetModel(queue, isProperties); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var msgStr = body.DeserializeUtf8();
var msg = msgStr.FromJson<T>();
try
{
handler(msg);
}
catch (Exception ex)
{
ex.GetInnestException().WriteToFile("队列接收消息", "RabbitMq");
if (!isDeadLetter)
PublishToDead<DeadLetterQueue>(queue, msgStr, ex);
}
finally
{
channel.BasicAck(ea.DeliveryTag, false);
}
};
channel.BasicConsume(queue, false, consumer);
}
下次是本机测试的发布速度截图:

快的时候有1.9K/S,慢的时候也有1.7K/S
Pull(拉)的封装
直接上代码:
/// <summary>
/// 获取消息
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="exchange"></param>
/// <param name="queue"></param>
/// <param name="routingKey"></param>
/// <param name="handler">消费处理</param>
private void Poll<T>(string exchange, string queue, string routingKey, Action<T> handler) where T : class
{
var channel = GetModel(exchange, queue, routingKey); var result = channel.BasicGet(queue, false);
if (result.IsNull())
return; var msg = result.Body.DeserializeUtf8().FromJson<T>();
try
{
handler(msg);
}
catch (Exception ex)
{
ex.GetInnestException().WriteToFile("队列接收消息", "RabbitMq");
}
finally
{
channel.BasicAck(result.DeliveryTag, false);
}
}

快的时候有1.8K/s,稳定是1.5K/S
Rpc(远程调用)的封装
首先说明下,RabbitMq只是提供了这个RPC的功能,但是并不是真正的RPC,为什么这么说:
1、传统Rpc隐藏了调用细节,像调用本地方法一样传参、抛出异常
2、RabbitMq的Rpc是基于消息的,消费者消费后,通过新队列返回响应结果。
/// <summary>
/// RPC客户端
/// </summary>
/// <param name="exchange"></param>
/// <param name="queue"></param>
/// <param name="routingKey"></param>
/// <param name="body"></param>
/// <param name="isProperties"></param>
/// <returns></returns>
public string RpcClient(string exchange, string queue, string routingKey, string body, bool isProperties = false)
{
var channel = GetModel(exchange, queue, routingKey, isProperties); var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(queue, true, consumer); try
{
var correlationId = Guid.NewGuid().ToString();
var basicProperties = channel.CreateBasicProperties();
basicProperties.ReplyTo = queue;
basicProperties.CorrelationId = correlationId; channel.BasicPublish(exchange, routingKey, basicProperties, body.SerializeUtf8()); var sw = Stopwatch.StartNew();
while (true)
{
var ea = consumer.Queue.Dequeue();
if (ea.BasicProperties.CorrelationId == correlationId)
{
return ea.Body.DeserializeUtf8();
} if (sw.ElapsedMilliseconds > )
throw new Exception("等待响应超时");
}
}
catch (Exception ex)
{
throw ex.GetInnestException();
}
} /// <summary>
/// RPC服务端
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="exchange"></param>
/// <param name="queue"></param>
/// <param name="isProperties"></param>
/// <param name="handler"></param>
/// <param name="isDeadLetter"></param>
public void RpcService<T>(string exchange, string queue, bool isProperties, Func<T, T> handler, bool isDeadLetter)
{
//队列声明
var channel = GetModel(queue, isProperties); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var msgStr = body.DeserializeUtf8();
var msg = msgStr.FromJson<T>(); var props = ea.BasicProperties;
var replyProps = channel.CreateBasicProperties();
replyProps.CorrelationId = props.CorrelationId; try
{
msg = handler(msg);
}
catch (Exception ex)
{
ex.GetInnestException().WriteToFile("队列接收消息", "RabbitMq");
}
finally
{
channel.BasicPublish(exchange, props.ReplyTo, replyProps, msg.ToJson().SerializeUtf8());
channel.BasicAck(ea.DeliveryTag, false);
}
};
channel.BasicConsume(queue, false, consumer);
}
可以用,但不建议去用。可以考虑其他的RPC框架。grpc、thrift等。
结尾
本篇文章,没有过多的写RabbitMq的知识点,因为园子的学习笔记实在太多了。下面把我的代码奉上 https://github.com/SkyChenSky/Sikiro.Mq.Rabbit。如果有发现写得不对的地方麻烦在评论指出,我会及时修改以免误导别人。
如果本篇文章您有用,请点击一下推荐,谢谢大家阅读。
.net平台的rabbitmq使用封装的更多相关文章
- net平台的rabbitmq
net平台的rabbitmq使用封装 前言 RabbitMq大家再熟悉不过,这篇文章主要整对rabbitmq学习后封装RabbitMQ.Client的一个分享.文章最后,我会把封装组件和demo奉上. ...
- 微信公众平台消息接口开发-封装weixin.class.php
原文:微信公众平台消息接口开发-封装weixin.class.php 一.封装weixin.class.php 由于微信公众平台的通信使用的是特定格式的XML数据,每次接受和回复都要去做一大堆的数据处 ...
- Linux(Centos平台)RabbitMQ消息中间件服务器搭建
本篇结合接口测试平台部署来讲,不了解的请先查看我的另一篇文档,HttpRunnerManager接口测试平台部署在服务器上(Centos + python3.6 + Mysql5.7 + uwsgi ...
- 微信公众平台消息接口开发-封装weixin.class.php(转)
一.封装weixin.class.php 由于微信公众平台的通信使用的是特定格式的XML数据,每次接受和回复都要去做一大堆的数据处理. 我们就考虑在这个基础上做一次封装,weixin.class.ph ...
- [c#]asp.net开发微信公众平台(3)微信消息封装及反射赋值
上一篇已经搭建好整体框架,实现了入口的验证, 验证通过后就交给LookMsgType方法处理,LookMsgType方法主要是对微信发来的不同的消息进行分解,不同的类型交给业务逻辑层不同的方法处理 ...
- php对业务平台接口调用的封装格式
1.封装类示例:E:\html\pim\php_mcloud_cas\util\UmcPlatform.class.php <?php class Util_UmcPlatform{ const ...
- Windows平台安装RabbitMQ(亲测)
一.下载安装包 https://www.rabbitmq.com/download.html 选择Windows下载 3.下载RabbitMQ安装包和运行环境Erlang安装包 (1)比对下载对应的版 ...
- C# RabbitMQ
鉴于本人很懒,发现好的文章一般都直接copy,本文大体摘自:https://www.cnblogs.com/MuNet/p/8546192.html 1.引言 RabbitMQ——Rabbit Mes ...
- Java异步消息平台
l JAVA平台异步消息模块 JAVA平台异步消息模块,是一个针对RabbitMQ的消息发送及处理封装,包含消息的配置.发送.接收.失败重试.日志记录等,总共分为4个部分: 1)RabbitMQ访问 ...
随机推荐
- Poi之Word文档结构介绍
1.poi之word文档结构介绍之正文段落 一个文档包含多个段落,一个段落包含多个Runs,一个Runs包含多个Run,Run是文档的最小单元 获取所有段落:List<XWPFParagraph ...
- 解决Apache的错误日志巨大的问题以及关闭Apache web日志记录
调整错误日志的级别 这几天 apache错误日志巨大 莫名其妙的30G 而且 很多都是那种页面不存在的 网站太多了 死链接相应的也很多于是把错误警告调低了 因为写日志会给系统带来很大的损耗.关闭 ...
- js模块化开发——AMD规范
这个系列的第一部分介绍了Javascript模块的基本写法,今天介绍如何规范地使用模块. 七.模块的规范 先想一想,为什么模块很重要? 因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就 ...
- 文本切换器(TextSwitcher)的功能和用法
TextSwitcher继承了ViewSwitcher,因此它具有与ViewSwitcher相同的特征:可以在切换View组件的同时使用动画效果.与ImageSwitcher相似的是,使用TextSw ...
- springMvc获取servletContext
springMvc获取servletContext WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebA ...
- Valgrind使用记录
0.安装valgrind wget http://valgrind.org/downloads/valgrind-3.11.0.tar.bz2 tar xvf valgrind-3.11.0.tar. ...
- 条形码--JsBarcode
介绍一下在GitHub生成条形码的js插件→JsBarcode 支持的条形码: 条码支持的有: CODE128 CODE128 (自动模式切换) CODE128 A/B/C (强制模式)EAN ...
- 关于WIN10开机无法输入密码的问题
昨日,电脑 遇到了开机无法输入密码的问题,神烦. 作为一个计算狗,怎么能直接装系统(百度了一堆方法,装系统,果真万能)呢. 所以,深刻的分析了下. 1 .首先说明基本情况. 计算机品牌:ASUS 系统 ...
- [CSS3] 学习笔记-CSS3盒子样式
1.盒子的类型 在CSS3中,使用display来定义盒子的类型,包括block,inline,inline-block类型.div元素和P元素,属于block类型,span元素和a元素,属于inli ...
- [TPYBoard-Micropython之会python就能做硬件 4] 学习使用电位器和1602显示屏
一.实验器材 1.TPYboard V102板 一块 2.电位器 一个 3.1602 屏 一块 4.杜邦线:若干 二.电位器的使用 电位器 (英文:Potentiometer)是可变电阻器的一种 ...