php rabbitmq 封装,.net平台的rabbitmq使用封装

前言

RabbitMq大家再熟悉不过,这篇文章主要整对rabbitmq学习后封装RabbitMQ.Client的一个分享。文章最后,我会把封装组件和demo奉上。

Rabbitmq的运作

从下图可以看出,发布者(Publisher)是把消息先发送到交换器(Exchange),再从交换器发送到指定队列(Queue),而先前已经声明交换器与队列绑定关系,最后消费者(Customer)通过订阅或者主动取指定队列消息进行消费。

f1d178d68bdefadc596344482c5ccd2d.png

那么刚刚提到的订阅和主动取可以理解成,推(被动),拉(主动)。

推,只要队列增加一条消息,就会通知空闲的消费者进行消费。(我不找你,就等你找我,观察者模式)

拉,不会通知消费者,而是由消费者主动轮循或者定时去取队列消息。(我需要才去找你)

使用场景我举个例子,假如有两套系统 订单系统和发货系统,从订单系统发起发货消息指令,为了及时发货,发货系统需要订阅队列,只要有指令就处理。

可是程序偶尔会出异常,例如网络或者DB超时了,把消息丢到失败队列,这个时候需要重发机制。但是我又不想while(IsPostSuccess == True),因为只要出异常了,会在某个时间段内都会有异常,这样的重试是没意义的。

这个时候不需要及时的去处理消息,有个JOB定时或者每隔几分钟(失败次数*间隔分钟)去取失败队列消息,进行重发。

Publish(发布)的封装

步骤:初始化链接->声明交换器->声明队列->换机器与队列绑定->发布消息。注意的是,我将Model存到了ConcurrentDictionary里面,因为声明与绑定是非常耗时的,其次,往重复的队列发送消息是不需要重新初始化的。

d55d826299ce7363ecf2dccc06c98a04.gif

2d445f8512bc539f4441a5636f315a7e.gif

1 /// 2 /// 交换器声明 3 /// 4 /// 5 /// 交换器 6 /// 交换器类型: 7 /// 1、Direct Exchange – 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全 8 /// 匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的 9 /// 消息才被转发,不会转发dog.puppy,也不会转发dog.guard,只会转发dog10 /// 2、Fanout Exchange – 不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都11 /// 会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout12 /// 交换机转发消息是最快的。13 /// 3、Topic Exchange – 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配一个或多14 /// 个词,符号“*”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*”15 /// 只会匹配到“audit.irs”。16 /// 持久化17 /// 自动删除18 /// 参数19 private static void ExchangeDeclare(IModel iModel, string exchange, string type = ExchangeType.Direct,20 bool durable = true,21 bool autoDelete = false, IDictionary arguments = null)22 {23 exchange = exchange.IsNullOrWhiteSpace() ? "" : exchange.Trim();24 iModel.ExchangeDeclare(exchange, type, durable, autoDelete, arguments);25 }26 27 /// 28 /// 队列声明29 /// 30 /// 31 /// 队列32 /// 持久化33 /// 排他队列,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,34 /// 并在连接断开时自动删除。这里需要注意三点:其一,排他队列是基于连接可见的,同一连接的不同信道是可35 /// 以同时访问同一个连接创建的排他队列的。其二,“首次”,如果一个连接已经声明了一个排他队列,其他连36 /// 接是不允许建立同名的排他队列的,这个与普通队列不同。其三,即使该队列是持久化的,一旦连接关闭或者37 /// 客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景。38 /// 自动删除39 /// 参数40 private static void QueueDeclare(IModel channel, string queue, bool durable = true, bool exclusive = false,41 bool autoDelete = false, IDictionary arguments = null)42 {43 queue = queue.IsNullOrWhiteSpace() ? "UndefinedQueueName" : queue.Trim();44 channel.QueueDeclare(queue, durable, exclusive, autoDelete, arguments);45 }46 47 /// 48 /// 获取Model49 /// 50 /// 交换机名称51 /// 队列名称52 /// 53 /// 是否持久化54 /// 55 private static IModel GetModel(string exchange, string queue, string routingKey, bool isProperties = false)56 {57 return ModelDic.GetOrAdd(queue, key =>58 {59 var model = _conn.CreateModel();60 ExchangeDeclare(model, exchange, ExchangeType.Fanout, isProperties);61 QueueDeclare(model, queue, isProperties);62 model.QueueBind(queue, exchange, routingKey);63 ModelDic[queue] = model;64 return model;65 });66 }67 68 /// 69 /// 发布消息70 /// 71 /// 路由键72 /// 队列信息73 /// 交换机名称74 /// 队列名75 /// 是否持久化76 /// 77 public void Publish(string exchange, string queue, string routingKey, string body, bool isProperties = false)78 {79 var channel = GetModel(exchange, queue, routingKey, isProperties);80 81 try82 {83 channel.BasicPublish(exchange, routingKey, null, body.SerializeUtf8());84 }85 catch (Exception ex)86 {87 throw ex.GetInnestException();88 }89 }

View Code

下次是本机测试的发布速度截图:

d7f7fb3438ab8bdca1b2833f08a83ecb.png

4.2W/S属于稳定速度,把反序列化(ToJson)会稍微快一些。

Subscribe(订阅)的封装

发布的时候是申明了交换器和队列并绑定,然而订阅的时候只需要声明队列就可。从下面代码能看到,捕获到异常的时候,会把消息送到自定义的“死信队列”里,由另外的JOB进行定时重发,因此,finally是应答成功的。

d55d826299ce7363ecf2dccc06c98a04.gif

2d445f8512bc539f4441a5636f315a7e.gif

///

/// 获取Model ///

/// 队列名称

///

///

private static IModel GetModel(string queue, bool isProperties = false)

{ return ModelDic.GetOrAdd(queue, value =>

{ var model = _conn.CreateModel();

QueueDeclare(model, queue, isProperties); //每次消费的消息数

model.BasicQos(0, 1, false);

ModelDic[queue] = model; return model;

});

}

///

/// 接收消息 ///

///

/// 队列名称

///

/// 消费处理

///

public void Subscribe(string queue, bool isProperties, Action 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(); try

{

handler(msg);

} catch (Exception ex)

{

ex.GetInnestException().WriteToFile("队列接收消息", "RabbitMq"); if (!isDeadLetter)

PublishToDead(queue, msgStr, ex);

} finally

{

channel.BasicAck(ea.DeliveryTag, false);

}

};

channel.BasicConsume(queue, false, consumer);

}

View Code

下次是本机测试的发布速度截图:

1f0804489e2303e854ba3c87c0a3e8a6.png

快的时候有1.9K/S,慢的时候也有1.7K/S

Pull(拉)的封装

直接上代码:

d55d826299ce7363ecf2dccc06c98a04.gif

2d445f8512bc539f4441a5636f315a7e.gif

///

/// 获取消息 ///

///

///

///

///

/// 消费处理

private void Poll(string exchange, string queue, string routingKey, Action 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(); try

{

handler(msg);

} catch (Exception ex)

{

ex.GetInnestException().WriteToFile("队列接收消息", "RabbitMq");

} finally

{

channel.BasicAck(result.DeliveryTag, false);

}

}

View Code

4e0cbe29acec22899bf488f41565534e.png

快的时候有1.8K/s,稳定是1.5K/S

Rpc(远程调用)的封装

首先说明下,RabbitMq只是提供了这个RPC的功能,但是并不是真正的RPC,为什么这么说:

1、传统Rpc隐藏了调用细节,像调用本地方法一样传参、抛出异常

2、RabbitMq的Rpc是基于消息的,消费者消费后,通过新队列返回响应结果。

d55d826299ce7363ecf2dccc06c98a04.gif

2d445f8512bc539f4441a5636f315a7e.gif

///

/// RPC客户端 ///

///

///

///

///

///

///

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 > 30000) throw new Exception("等待响应超时");

}

} catch (Exception ex)

{ throw ex.GetInnestException();

}

}

///

/// RPC服务端 ///

///

///

///

///

///

///

public void RpcService(string exchange, string queue, bool isProperties, Func 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(); 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);

}

View Code

可以用,但不建议去用。可以考虑其他的RPC框架。grpc、thrift等。

结尾

本篇文章,没有过多的写RabbitMq的知识点,因为园子的学习笔记实在太多了。下面把我的代码奉上 http://www.php.cn/ 。如果有发现写得不对的地方麻烦在评论指出,我会及时修改以免误导别人。

如果本篇文章您有用,请点击一下推荐,谢谢大家阅读。

本文原创发布php中文网,转载请注明出处,感谢您的尊重!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值