• RabbitMQ(二)


    RabbitMQ C#操作示例

    发布第二版,对RabbitMQ的知识重新做了梳理,优化了.NET的客户端操作代码(MarkDown贴图麻烦就不搞图了)
    推荐:https://geewu.gitbooks.io/rabbitmq-quick/content/RabbitMQ介绍.html

    前世今生

    要说RabbitMQ就不得不扯一扯AMQP,消息队列这个概念其实并不新鲜,早在80年代的时候就被应用在金融交易中,如:TIB、在到之后IBM开发了MQSeries、微软搞了MSMQ,这些MQ有一个共同点就是要钱,虽然Java搞了JMQ,但舅舅不疼姥姥不爱,凄凉哉。人们依旧生活在水深火热之中,业界也亟需一款开源、免费、统一的消息队列项目的出现。

    转机出现在2004年,大财主摩根大通和iMatrix开始着手AMQP标准的制定,并于2006年发布规范,2007年基于AMQP标准、Erlang语言开发的RabbitMQ应运而生,这位新生儿一出生就得到业界的广泛关注、好评如潮、blabla……

    基本概念

    接下来对于AMQP中的一些基本概念进行说明,对于操作上的理解有一定帮助。

    • Broker:用于接收和分发消息,RabbitMQ总的Broker就是RabbitMQ的Server端;
    • Virtual Host:是出于安全和多用户角度考虑的,在RabbitMQ中可以为多个用户划分不同的Virtual Host;
    • Connection:客户端和服务端的TCP连接;
    • Channel:通信管道、毕竟不停的创建连接的消耗比较大,可以通过Channel来进行通讯,不过拎不清的是为啥.NET的客户端里叫CreateModel;
    • Exchange:Message到达Broker后,会由Exchange根据规则查询Routing Key进行分发,将Message分发到Queue中去,Exchange常用的类型有:
      • Direct:这个是默认的Exchange类型,在使用这种类型的Exchange时,可以不指定Routing Key,这个RoutingKey和Queue的名字相同;
      • Fanout:这种类型会忽略RoutingKey的存在,直接将Message广播到其上绑定的所有的的Queue中去;
      • Topic:这种类型会在Queue绑定的时候可疑使用通配符,如*表示一个关键字、#表示多个关键字,你在发布的时候就可疑根据RoutingKey和绑定关系找到对应的Queue,示例:
        static void Main(string[] args)
            {
                Console.WriteLine("开始执行");
    
                var factory = new ConnectionFactory()
                {
                    UserName = "admin",
                    Password = "admin",
                    HostName = "192.168.253.131"
                };
                var conn = factory.CreateConnection();
                var channel = conn.CreateModel();
    
                //声明转发器类型为Topic
                channel.ExchangeDeclare("Korea", ExchangeType.Topic, true, false, null);
    
                //声明Q1,RoutingKey的匹配规则为log.*,可以匹配到诸如log.error,log.warn等,但是无法匹配到log.error.self
                channel.QueueDeclare("Q1", true);
                channel.QueueBind("Q1", "Korea", "log.*", null);
    
    
                channel.QueueDeclare("Q2", true);
                channel.QueueBind("Q2", "Korea", "log.*", null);
    
                var properties = channel.CreateBasicProperties();
                properties.DeliveryMode = 2;
    
                channel.BasicPublish("Korea", "log.warn", properties, Encoding.UTF8.GetBytes("你好"));
    
    
                var bgr = channel.BasicGet("Q2", true);
    
                Console.WriteLine(Encoding.UTF8.GetString(bgr.Body));
    
                Console.WriteLine("执行完毕");
    
                Console.ReadLine();
            }
    
     * Headers:Headers会忽略RoutingKey的存在,在Publish时,可以在Headers中加入相关信息,依据Headers中的规则来匹配,示例:
    
        static void Main(string[] args)
            {
                Console.WriteLine("开始执行");
    
                var factory = new ConnectionFactory()
                {
                    UserName = "admin",
                    Password = "admin",
                    HostName = "192.168.253.131"
                };
                var conn = factory.CreateConnection();
                var channel = conn.CreateModel();
    
                //声明转发器类型为Topic
                channel.ExchangeDeclare("school", ExchangeType.Headers, true, false, null);
    
                //声明Q1,RoutingKey的匹配规则为log.*,可以匹配到诸如log.error,log.warn等,但是无法匹配到log.error.self
                Dictionary<string, object> headers = new Dictionary<string, object>();
                headers.Add("x-match", "any"); //all/ any(只要有一个键值对匹配即可用)
                headers.Add("name", "lilei");
                headers.Add("age", 13);
    
    
                channel.QueueDeclare("students", true);
                channel.QueueBind("students", "school", "", headers);
    
    
                channel.QueueDeclare("teachers", true);
                channel.QueueBind("teachers", "school", "", null);
    
                var properties = channel.CreateBasicProperties();
                properties.DeliveryMode = 2;
                //添加Header存储的键值对
                properties.Headers=new Dictionary<string,object>();
                properties.Headers.Add("name", "hanmeimei");
                properties.Headers.Add("age", 13);
    
    
                channel.BasicPublish("school", "", properties, Encoding.UTF8.GetBytes("你好"));
    
    
                var bgr = channel.BasicGet("students", true); //但是注意,如果Queue中不绑定headers,如teachers也可以拿到值
    
                Console.WriteLine(Encoding.UTF8.GetString(bgr.Body));
    
                Console.WriteLine("执行完毕");
    
                Console.ReadLine();
            }
    
    • Queue:消息最终会被缓存在Queue中等待消费;
    • Binding:Exchange和Queue中的绑定关系,Binding信息会存储在Exchange的查询表中,用于分发Message数据。
      我们再梳理一下流程(这里不画图了,自己YY一下),建立Connection之后,消息通过Channel到达Exchange,Exchange根据绑定的RoutingKey规则将Mesage拷贝到各个Queue中去,等待消费。

    生产/消费代码

    这里写了一个基础操作类,用于展示如何使用RabbitMQ

    *RabbitMQ C#操作示例*
    ======================
    _发布第二版,对RabbitMQ的知识重新做了梳理,优化了.NET的客户端操作代码(MarkDown贴图麻烦就不搞图了)_
    
    # 前世今生
    要说RabbitMQ就不得不扯一扯AMQP,消息队列这个概念其实并不新鲜,早在80年代的时候就被应用在金融交易中,如:TIB、在到之后IBM开发了MQSeries、微软搞了MSMQ,这些MQ有一个共同点就是要钱,虽然Java搞了JMQ,但舅舅不疼姥姥不爱,凄凉哉。人们依旧生活在水深火热之中,业界也亟需一款开源、免费、统一的消息队列项目的出现。<br/>
    转机出现在2004年,大财主摩根大通和iMatrix开始着手AMQP标准的制定,并于2006年发布规范,2007年基于AMQP标准、Erlang语言开发的RabbitMQ应运而生,这位新生儿一出生就得到业界的广泛关注、好评如潮、blabla……
    # 基本概念
    接下来对于AMQP中的一些基本概念进行说明,对于操作上的理解有一定帮助。
    * Broker:用于接收和分发消息,RabbitMQ总的Broker就是RabbitMQ的Server端;
    * Virtual Host:是出于安全和多用户角度考虑的,在RabbitMQ中可以为多个用户划分不同的Virtual Host;
    * Connection:客户端和服务端的TCP连接;
    * Channel:通信管道、毕竟不停的创建连接的消耗比较大,可以通过Channel来进行通讯,不过拎不清的是为啥.NET的客户端里叫CreateModel;
    * Exchange:Message到达Broker后,会由Exchange根据规则查询Routing Key进行分发,将Message分发到Queue中去,Exchange常用的类型有:
         * Direct:这个是默认的Exchange类型,在使用这种类型的Exchange时,可以不指定Routing Key,这个RoutingKey和Queue的名字相同;
         * Fanout:这种类型会忽略RoutingKey的存在,直接将Message广播到其上绑定的所有的的Queue中去;
         * Topic:这种类型会在Queue绑定的时候可疑使用通配符,如*表示一个关键字、#表示多个关键字,你在发布的时候就可疑根据RoutingKey和绑定关系找到对应的Queue,示例:
    ````csharp
        static void Main(string[] args)
            {
                Console.WriteLine("开始执行");
    
                var factory = new ConnectionFactory()
                {
                    UserName = "admin",
                    Password = "admin",
                    HostName = "192.168.253.131"
                };
                var conn = factory.CreateConnection();
                var channel = conn.CreateModel();
    
                //声明转发器类型为Topic
                channel.ExchangeDeclare("Korea", ExchangeType.Topic, true, false, null);
    
                //声明Q1,RoutingKey的匹配规则为log.*,可以匹配到诸如log.error,log.warn等,但是无法匹配到log.error.self
                channel.QueueDeclare("Q1", true);
                channel.QueueBind("Q1", "Korea", "log.*", null);
    
    
                channel.QueueDeclare("Q2", true);
                channel.QueueBind("Q2", "Korea", "log.*", null);
    
                var properties = channel.CreateBasicProperties();
                properties.DeliveryMode = 2;
    
                channel.BasicPublish("Korea", "log.warn", properties, Encoding.UTF8.GetBytes("你好"));
    
    
                var bgr = channel.BasicGet("Q2", true);
    
                Console.WriteLine(Encoding.UTF8.GetString(bgr.Body));
    
                Console.WriteLine("执行完毕");
    
                Console.ReadLine();
            }
    
     * Headers:Headers会忽略RoutingKey的存在,在Publish时,可以在Headers中加入相关信息,依据Headers中的规则来匹配,示例:
    
        static void Main(string[] args)
            {
                Console.WriteLine("开始执行");
    
                var factory = new ConnectionFactory()
                {
                    UserName = "admin",
                    Password = "admin",
                    HostName = "192.168.253.131"
                };
                var conn = factory.CreateConnection();
                var channel = conn.CreateModel();
    
                //声明转发器类型为Topic
                channel.ExchangeDeclare("school", ExchangeType.Headers, true, false, null);
    
                //声明Q1,RoutingKey的匹配规则为log.*,可以匹配到诸如log.error,log.warn等,但是无法匹配到log.error.self
                Dictionary<string, object> headers = new Dictionary<string, object>();
                headers.Add("x-match", "any"); //all/ any(只要有一个键值对匹配即可用)
                headers.Add("name", "lilei");
                headers.Add("age", 13);
    
    
                channel.QueueDeclare("students", true);
                channel.QueueBind("students", "school", "", headers);
    
    
                channel.QueueDeclare("teachers", true);
                channel.QueueBind("teachers", "school", "", null);
    
                var properties = channel.CreateBasicProperties();
                properties.DeliveryMode = 2;
                //添加Header存储的键值对
                properties.Headers=new Dictionary<string,object>();
                properties.Headers.Add("name", "hanmeimei");
                properties.Headers.Add("age", 13);
    
    
                channel.BasicPublish("school", "", properties, Encoding.UTF8.GetBytes("你好"));
    
    
                var bgr = channel.BasicGet("students", true); //但是注意,如果Queue中不绑定headers,如teachers也可以拿到值
    
                Console.WriteLine(Encoding.UTF8.GetString(bgr.Body));
    
                Console.WriteLine("执行完毕");
    
                Console.ReadLine();
            }
    
    • Queue:消息最终会被缓存在Queue中等待消费;
    • Binding:Exchange和Queue中的绑定关系,Binding信息会存储在Exchange的查询表中,用于分发Message数据。
      我们再梳理一下流程(这里不画图了,自己YY一下),建立Connection之后,消息通过Channel到达Exchange,Exchange根据绑定的RoutingKey规则将Mesage拷贝到各个Queue中去,等待消费。

    生产/消费代码

    这里写了一个基础操作类,用于展示如何使用RabbitMQ

        /// <summary>
        /// RabbitMQ的操作类。
        /// </summary>
        public class RabbitMQClient : IDisposable
        {
            /// <summary>
            /// 连接,链接的开启和关闭比较耗费系统资源,建议缓存当前连接,使用完毕后,使用Close()方法释放链接。
            /// </summary>
            protected IConnection RabbitMQConnection { get; set; }
    
            /// <summary>
            /// 通道,在多线程中使用时,建议将isDefaultChannel设置为false。
            /// </summary>
            protected IModel RabbitMQChannel { get; set; }
    
    
            /// <summary>
            /// 声明队列。
            /// </summary>
            /// <param name="exchange">转发器。</param>
            /// <param name="queue">队列名称。</param>
            public void DeclareQueue(string exchange, string queue)
            {
                IModel channel = BuildChannel(true);
    
                //这里将durable声明为true,表示会持久化到erlang自带的mnesia数据库中,当rabbitmq宕机时可以恢复
                channel.QueueDeclare(queue, true, false, false, null);
                if (!string.IsNullOrEmpty(exchange))
                {
                    //在不声明routingkey的情况下,routingkey默认为queue的名称
                    channel.ExchangeDeclare(exchange, ExchangeType.Direct, true);
                    channel.QueueBind(queue, exchange, queue, null);
                }
            }
    
    
            /// <summary>
            /// 构造函数。
            /// </summary>
            /// <param name="connStr">链接字符串,多个链接用逗号隔开,如:“192.168.253.129,192.168.253.131”。</param>
            /// <param name="uName">RabbitMQ的服务端用户名。</param>
            /// <param name="uPwd">>RabbitMQ的服务端密码。</param>
            public RabbitMQClient(string connStr, string uName, string uPwd)
            {
                RabbitMQConnection = CreateRabbitConnection(connStr, uName, uPwd);
                RabbitMQChannel = RabbitMQConnection.CreateModel();
            }
    
            /// <summary>
            /// 发布消息。
            /// </summary>
            /// <param name="msg">消息。</param>
            /// <param name="exchange">转发器名称(若为空,则默认使用AMQP default)。</param>
            /// <param name="queue">队列名称。</param>
            /// <param name="isDefaultChannel">是否启用新的Channel。</param>
            /// <returns>操作结果。</returns>
            public bool PubMsg(string msg, string exchange, string queue, bool isDefaultChannel = true, bool isQueueDeclared = true)
            {
                if (!isQueueDeclared)
                {
                    DeclareQueue(exchange, queue);
                }
    
                IModel channel = BuildChannel(isDefaultChannel);
    
                var properties = channel.CreateBasicProperties();
                properties.DeliveryMode = 2;
                channel.BasicPublish(exchange, queue, properties, Encoding.UTF8.GetBytes(msg));
                return true;
            }
    
            /// <summary>
            /// 订阅信息(每次取出一条),获取成功后该消息会被自动删除。
            /// </summary>
            /// <param name="queue">队列名称。</param>
            /// <param name="isDefaultChannel">是否启用新的Channel。</param>
            /// <returns>取出的消息。</returns>
            public string SubMsg(string queue, bool isDefaultChannel = true, ushort qos = 0)
            {
                IModel channel = BuildChannel(isDefaultChannel);
    
                if (qos != 0)
                {
                    channel.BasicQos(0, qos, false);
                }
    
    
                var gr = channel.BasicGet(queue, true);
                if (gr == null || gr.Body == null)
                    return null;
                return Encoding.UTF8.GetString(gr.Body);
            }
    
            /// <summary>
            /// 从队列中取出消息,消息不会自动ACK,需要在Handler手动剔除,该方法会阻塞当前进程。
            /// </summary>
            /// <param name="queue">队列名称。</param>
            /// <param name="handler">监听到消息时的处理函数。</param>
            /// <param name="isDefaultChannel">是否启用新的Channel。</param>
            public void ListenMessages(string queue, EventHandler<BasicDeliverEventArgs> handler)
            {
                //多线程消费时不建议使用同一个Channel
                IModel channel = BuildChannel(false);
    
                List<string> bgrs = new List<string>();
    
                var consumer = new EventingBasicConsumer(channel);
                consumer.Received += handler;
                do
                {
                    var msg = channel.BasicConsume(queue, true, consumer);
                    if (channel.MessageCount(queue) == 0)
                    {
                        Console.WriteLine("跳出线程");
                        break;
                    }                   
                } while (true);
            }
    
    
    
    
            /// <summary>
            /// 订阅信息(每次取出一条),获取成功后该消息会被自动删除。
            /// </summary>
            /// <param name="queue">队列名称。</param>
            /// <param name="autoAck">是否自动删除队列。</param>
            /// <param name="isDefaultChannel">是否启用新的Channel。</param>
            /// <returns>取出的消息。</returns>
            public BasicGetResult SubResult(string queue, bool autoAck, bool isDefaultChannel = true)
            {
    
                IModel channel = BuildChannel(isDefaultChannel);
    
                var bgr = channel.BasicGet(queue, autoAck);
                return bgr;
            }
    
            /// <summary>
            /// 消费确认。
            /// </summary>
            /// <param name="deliveryTag">信息传输标识。</param>
            /// <param name="isDefaultChannel">是否启用新的Channel。</param>
            public void SubAck(ulong deliveryTag, bool isDefaultChannel = true)
            {
                IModel channel = BuildChannel(isDefaultChannel);
                channel.BasicAck(deliveryTag, false);  //如果将multipe设置为true,则会一次性nack所有小于deliveryTag的值
            }
    
    
            /// <summary>
            /// 消费失败处理。
            /// </summary>
            /// <param name="deliveryTag">信息传输标识。</param>
            /// <param name="requeue">true表示重新发送,false表示丢弃。</param>
            /// <param name="isDefaultChannel">是否启用新的Channel。</param>
            public void SubNAck(ulong deliveryTag, bool requeue, bool isDefaultChannel = true)
            {
                IModel channel = BuildChannel(isDefaultChannel);
                channel.BasicNack(deliveryTag, false, requeue); //如果将multipe设置为true,则会一次性ack所有小于deliveryTag的值
            }
    
    
            /// <summary>
            /// 清除所有存储在Queue中的内容。
            /// </summary>
            /// <param name="queue">队列名。</param>
            /// <param name="isDefaultChannel">是否启用新的Channel。</param>
            public void PurgeQueue(string queue, bool isDefaultChannel = true)
            {
                IModel channel = BuildChannel(isDefaultChannel);
                channel.QueuePurge(queue);
            }
    
            /// <summary>
            /// 快速推送消息(单条)。
            /// </summary>
            /// <param name="msg">待推送消息主体。</param>
            /// <param name="connStr">链接字符串,多个链接用逗号隔开,如:“192.168.253.129,192.168.253.131”。</param>
            /// <param name="uName">RabbitMQ的服务端用户名。</param>
            /// <param name="uPwd">>RabbitMQ的服务端密码。</param>
            /// <param name="queue">推送的队列名称。</param>
            /// <param name="exchange">转发器的名称。</param>
            public static void QuickPubMsg(string msg, string connStr, string uName, string uPwd, string queue, string exchange = "")
            {
                using (var conn = CreateRabbitConnection(connStr, uName, uPwd))
                {
                    var channel = conn.CreateModel();
    
                    if (!string.IsNullOrEmpty(exchange))
                    {
                        //在不声明routingkey的情况下,routingkey默认为queue的名称
                        channel.ExchangeDeclare(exchange, ExchangeType.Direct, true);
                        channel.QueueBind(queue, exchange, queue, null);
                    }
    
                    var properties = channel.CreateBasicProperties();
                    properties.DeliveryMode = 2;
    
                    channel.QueueDeclare(queue, true, false, false, null);
                    channel.BasicPublish(exchange, queue, properties, Encoding.UTF8.GetBytes(msg));
                }
            }
    
            /// <summary>
            /// 快速消费消息(单条)。
            /// </summary>
            /// <param name="connStr">链接字符串,多个链接用逗号隔开,如:“192.168.253.129,192.168.253.131”。</param>
            /// <param name="uName">RabbitMQ的服务端用户名。</param>
            /// <param name="uPwd">RabbitMQ的服务端密码。</param>
            /// <param name="queue">推送的队列名称。</param>
            /// <returns>得到的消息。</returns>
            public static string QuickSubMsg(string connStr, string uName, string uPwd, string queue)
            {
                using (var conn = CreateRabbitConnection(connStr, uName, uPwd))
                {
                    var channel = conn.CreateModel();
                    var gr = channel.BasicGet(queue, true);
                    if (gr == null || gr.Body == null)
                        return null;
                    return Encoding.UTF8.GetString(gr.Body);
                }
            }
    
            /// <summary>
            /// 获取RabbitMQ的链接。
            /// </summary>
            protected static IConnection CreateRabbitConnection(string connStr, string uName, string uPwd)
            {
                if (string.IsNullOrEmpty(connStr))
                    throw new ArgumentNullException("未将对象的引用设置到对象的实例");
    
                var factory = new ConnectionFactory()
                {
                    UserName = uName,
                    Password = uPwd
                };
                var hosts = connStr.Trim().Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                return factory.CreateConnection(hosts);
            }
    
            protected IModel BuildChannel(bool isDefaultChannel)
            {
                if (!RabbitMQConnection.IsOpen)
                    throw new Exception("The connection is closed.");
    
                IModel channel;
                if (isDefaultChannel)
                    channel = RabbitMQChannel;
                else
                    channel = RabbitMQConnection.CreateModel();
    
                return channel;
            }
    
            /// <summary>
            /// 关闭客户端。
            /// </summary>
            public void Close()
            {
                if (RabbitMQConnection != null)
                    RabbitMQConnection.Close();
            }
    
    
            /// <summary>
            /// 释放资源。
            /// </summary>
            public void Dispose()
            {
                if (RabbitMQConnection != null)
                {
                    RabbitMQConnection.Close();
                    RabbitMQConnection = null;
                }
            }
        }
    

    使用示例(不看也罢):

            static void Main(string[] args)
            {
                Console.WriteLine("开始执行");
    
                RabbitMQClient client = new RabbitMQClient("192.168.253.131", "admin", "admin");
                client.PubMsg("Hello World", "MyExchange", "MyQueue");
                var msg = client.SubMsg("MyQueue");
                client.Close();
                Console.WriteLine(msg);
                Console.WriteLine("执行完毕");
    
                Console.ReadLine();
            }
    
  • 相关阅读:
    黄聪:HBuilder左侧项目管理器如何不与标签页一起自动切换
    黄聪:is_file和file_exists效率比较
    黄聪:HBuilder复制PHP项目后,【转到定位】功能失效
    黄聪:jquery.bootgrid表格插件有的属性(visibleInSelection、cssClass、headerCssClass、headerAlign)不能识别的解决办法
    黄聪:PHP如何实现延迟一定时间后自动刷新当前页面、自动跳转header("refresh:1;url={$url}");
    黄聪:bootstrapValidator验证成功,按钮变灰却无法提交的问题
    黄聪:如何使用钩子定制WordPress添加媒体界面,去除不需要的元素
    黄聪:jquery+Datatables出现数据过长,表格不自动换行,columns设置width失效的办法
    黄聪:Jquery+DataTables插件,如何在ajax调用服务器数据后,自动给tr添加id属性
    黄聪:JQUERY判断操作CHECKBOX选中、取消选中、反选、第二次无法选中的问题
  • 原文地址:https://www.cnblogs.com/krockey/p/8950290.html
Copyright © 2020-2023  润新知