• .net平台的rabbitmq使用封装


    .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里面,因为声明与绑定是非常耗时的,其次,往重复的队列发送消息是不需要重新初始化的。

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

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

      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(0, 1, 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 > 30000)
                            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/RabbitMq 。如果有发现写得不对的地方麻烦在评论指出,我会及时修改以免误导别人。

  • 相关阅读:
    vue从详情页回到列表页,停留在之前的tab上
    vue-touch监听手指左滑右滑事件
    vue事件代理
    vue通过ref获取组件渲染后的dom(this.$refs.xxxRef.$el)
    vue水印-第二种方法:通过指令
    # 有时候代码超时
    # 今天的leetcode1268又用上了二分搜索。
    # linux命令小常识
    # 大家好
    今天尝试配置maven的时候
  • 原文地址:https://www.cnblogs.com/zxtceq/p/7692165.html
Copyright © 2020-2023  润新知