RabbitMQ(二)

RabbitMQ C#操作示例

发布第二版,对RabbitMQ的知识重新做了梳理,优化了.NET的客户端操作代码(MarkDown贴图麻烦就不搞图了)
推荐:https://geewu.gitbooks.io/rabbitmq-quick/content/RabbitMQ%E4%BB%8B%E7%BB%8D.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
````csharp
RabbitMQ C#操作示例
======================
发布第二版,对RabbitMQ的知识重新做了梳理,优化了.NET的客户端操作代码(MarkDown贴图麻烦就不搞图了)

前世今生

要说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

    /// <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();
        }

转载于:https://www.cnblogs.com/krockey/p/8950290.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值