.NET Core 中使用 RabbitMQ

目录

一、RabbitMQ 队列模型

1. 简单队列

2.Work模式

3.订阅模式

4.路由模式

5.主题模式(通配符模式)

二、RabbitMQ 相关知识

1. 消息分发机制

2. 消息的确认模式

3. Ack,Nack,Reject的关系

三、RabbitMQ简单封装


一、RabbitMQ 队列模型

1. 简单队列

说明:

P:消息的生产者
C:消息的消费者
红色:队列

生产者将消息发送到队列,消费者从队列中获取消息。

2.Work模式

说明:

一个生产者、2个消费者。

一个消息只能被一个消费者获取。

生产者代码:

using System;
using RabbitMQ.Client;
using System.Text;

namespace RabbitMQDemo.Producter
{
    class Program
    {
        static string QueueName = "task_queue";
        static IConnection connection;

        static void Main(string[] args)
        {
            var factory = new ConnectionFactory() { HostName = "192.168.1.11", UserName = "user", Password = "user123" };
            connection = factory.CreateConnection();

            Run();

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();
        }

        private static void Run()
        {
            while (true)
            {
                using (var channel = connection.CreateModel())
                {
                    channel.QueueDeclare(queue: QueueName,
                                         durable: true,
                                         exclusive: false,
                                         autoDelete: false,
                                         arguments: null);

                    var message = GetMessage(DateTime.Now.ToString("HH:mm:ss"));
                    var body = Encoding.UTF8.GetBytes(message);

                    var properties = channel.CreateBasicProperties();
                    properties.Persistent = true;

                    channel.BasicPublish(exchange: "",
                                         routingKey: QueueName,
                                         basicProperties: properties,
                                         body: body);
                    Console.WriteLine(" [x] Sent {0}", message);
                    Console.ReadLine();
                }
            }
        }

        private static string GetMessage(string args)
        {
            return "Hello World! " + args;
        }
    }
}

消费者代码:

using System;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Threading;

namespace RabbitMQDemo.Worker
{
    class Program
    {
        static string QueueName = "task_queue";

        static void Main(string[] args)
        {
            var factory = new ConnectionFactory() { HostName = "192.168.1.11", UserName="user", Password="user123" };
            using (var connection = factory.CreateConnection())
            using (var channel = connection.CreateModel())
            {
                channel.QueueDeclare(queue: QueueName,
                                     durable: true,
                                     exclusive: false,
                                     autoDelete: false,
                                     arguments: null);

                channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);

                Console.WriteLine(" [*] Waiting for messages.");

                var consumer = new EventingBasicConsumer(channel);
                consumer.Received += (model, ea) =>
                {
                    var body = ea.Body;
                    var message = Encoding.UTF8.GetString(body);
                    Console.WriteLine(" [x] Received {0}", message);

                    channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
                };
                channel.BasicConsume(queue: QueueName,
                                     autoAck: false,
                                     consumer: consumer);

                Console.WriteLine(" Press [enter] to exit.");
                Console.ReadLine();
            }
        }
    }
}

3.订阅模式

说明:

1、1个生产者,多个消费者
2、每一个消费者都有自己的一个队列
3、生产者没有将消息直接发送到队列,而是发送到了交换机
4、每个队列都要绑定到交换机
5、生产者发送的消息,经过交换机,到达队列,实现,一个消息被多个消费者获取的目的
注意:一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费

4.路由模式

说明:

交换机可以绑定一个或多个队列

生产者代码:

当交换机没有绑定队列时,消息会直接丢弃。

using System;
using RabbitMQ.Client;
using System.Text;

namespace RabbitMQDemo.Producter
{
    class Program
    {
        static void Main(string[] args)
        {
            new RoutingProducter().Run();

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();
        }
    }

    public class RoutingProducter
    {
        static string ExchangeName = "logs";
        static IConnection connection;

        public void Run()
        {
            var factory = new ConnectionFactory() { HostName = "192.168.1.11", UserName = "user", Password = "user123" };
            connection = factory.CreateConnection();

            while (true)
            {
                using (var channel = connection.CreateModel())
                {
                    //声明交换机
                    channel.ExchangeDeclare(ExchangeName, "direct");

                    string routingKey = new Random().Next(0, 2) == 0 ? "error" : "info";
                    string message = routingKey == "error" ? "系统异常" : "请求执行成功";
                    var body = Encoding.UTF8.GetBytes(message);
                    //发送消息到交换机
                    channel.BasicPublish(exchange: ExchangeName,
                                         routingKey: routingKey,
                                         basicProperties: null,
                                         body: body);
                    Console.WriteLine(" [x] Sent '{0}':'{1}'", routingKey, message);
                }
                Console.ReadLine();
            }
        }
    }
}

消费者代码:

声明的队列没有持久化,程序停止后,绑定队列会自动删除。

using System;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RabbitMQDemo.Worker
{
    class Program
    {
        static void Main(string[] args)
        {
            new RoutingWorker().Run();

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();
        }
    }

    public class RoutingWorker
    {
        static string ExchangeName = "logs";
        static string RoutingError = "error";
        static string RoutingInfo = "info";
        static IConnection connection;

        public void Run()
        {
            var factory = new ConnectionFactory() { HostName = "192.168.1.11", UserName = "user", Password = "user123" };
            connection = factory.CreateConnection();

            using (var channel = connection.CreateModel())
            {
                //声明交换机
                channel.ExchangeDeclare(ExchangeName, "direct");
                //声明队列
                channel.QueueDeclare("logs_queue");
                //将队列绑定到交换机
                channel.QueueBind("logs_queue", ExchangeName, RoutingInfo);
                channel.QueueBind("logs_queue", ExchangeName, RoutingError);

                Console.WriteLine(" [*] Waiting for messages.");

                var consumer = new EventingBasicConsumer(channel);
                consumer.Received += (model, ea) =>
                {
                    var body = ea.Body;
                    var message = Encoding.UTF8.GetString(body);
                    Console.WriteLine(" [x] Received '{0}':'{1}'",
                                      ea.RoutingKey, message);
                };
                channel.BasicConsume(queue: "logs_queue",
                                     autoAck: true,
                                     consumer: consumer);

                Console.ReadLine();
            }
        }
    }
}

5.主题模式(通配符模式)

同一个消息被多个消费者获取。一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费到消息。

二、RabbitMQ 相关知识

1. 消息分发机制

轮询分发 :使用任务队列的优点之一就是可以轻易的并行工作。如果我们积压了好多工作,我们可以通过增加工作者(消费者)来解决这一问题,使得系统的伸缩性更加容易。在默认情况下,RabbitMQ将逐个发送消息到在序列中的下一个消费者(而不考虑每个任务的时长等等,且是提前一次性分配,并非一个一个分配)。平均每个消费者获得相同数量的消息。这种方式分发消息机制称为Round-Robin(轮询)。

公平分发 :虽然上面的分配法方式也还行,但是有个问题就是:比如:现在有2个消费者,所有的奇数的消息都是繁忙的,而偶数则是轻松的。按照轮询的方式,奇数的任务交给了第一个消费者,所以一直在忙个不停。偶数的任务交给另一个消费者,则立即完成任务,然后闲得不行。而RabbitMQ则是不了解这些的。这是因为当消息进入队列,RabbitMQ就会分派消息。它不看消费者为应答的数目,只是盲目的将消息发给轮询指定的消费者。

2. 消息的确认模式

消费者从队列中获取消息,服务端如何知道消息已经被消费呢?

模式1:自动确认
只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费。
模式2:手动确认
消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。

3. Ack,Nack,Reject的关系

1. 消息处理成功,执行Ack,RabbitMQ会把消息从队列中删除。

2. 消息处理失败,执行Nack或者Reject:

a) 当requeue=true时,消息会重新回到队列,然后当前消费者会马上再取回这条消息;

b) 当requeue=false时,如果Exchange有设置Dead Letter Exchange,则消息会去到Dead Letter Exchange;

c) 当requeue=false时,如果Exchange没设置Dead Letter Exchange,则消息从队列中删除,效果与Ack相同。

3. Nack与Reject的区别在于:Nack可以批量操作,Reject只能单条操作。

三、RabbitMQ简单封装

Program.cs

using System;

namespace RabbitMQ
{
    class Program
    {
        static void Main(string[] args)
        {

            MQHelperFactory.Default().Receive("EasyNetQ_Default_Error_Queue", item =>
            {
                MQHelperFactory.Default().SendMsg<string>("fanout_queue_default", item);
            });
            Console.ReadKey();
        }
    }
}

MQHelperFactory.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace RabbitMQ
{
    public class MQHelperFactory
    {
        public static RabbitMQHelper Default() =>
            new RabbitMQHelper("exchange_fanout");
    }
}

RabbitMQHelper.cs

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;

namespace RabbitMQ
{
    public class RabbitMQHelper
    {
        ConnectionFactory connectionFactory;
        IConnection connection;
        IModel channel;
        string exchangeName;

        public RabbitMQHelper(string changeName= "fanout_mq") {
            this.exchangeName = changeName;
            //创建连接工厂
            connectionFactory = new ConnectionFactory
            {
                HostName = "192.168.8.203",
                UserName = "superrd",
                Password = "superrd"
            };
            //创建连接
            connection = connectionFactory.CreateConnection();
            //创建通道
            channel = connection.CreateModel();
            //声明交换机
            channel.ExchangeDeclare(exchangeName, ExchangeType.Topic);
            
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="queName"></param>
        /// <param name="msg"></param>
        public void SendMsg<T>(string queName,T msg)where T:class
        {
            //声明一个队列
            channel.QueueDeclare(queName, true, false, false, null);
            //绑定队列,交换机,路由键
            channel.QueueBind(queName, exchangeName, queName);

            var basicProperties = channel.CreateBasicProperties();
            //1:非持久化 2:可持久化
            basicProperties.DeliveryMode = 2;
            var payload = Encoding.UTF8.GetBytes("我发出的消息");
            var address = new PublicationAddress(ExchangeType.Direct, exchangeName, queName);
            channel.BasicPublish(address, basicProperties, payload);
        }

        /// <summary>
        /// 消费消息
        /// </summary>
        /// <param name="queName"></param>
        /// <param name="received"></param>
        public void Receive(string queName,Action<string> received)
        {
            //事件基本消费者
            EventingBasicConsumer consumer = new EventingBasicConsumer(channel);

            //接收到消息事件
            consumer.Received += (ch, ea) =>
            {
                string message = Encoding.UTF8.GetString(ea.Body);
                received(message);
                //确认该消息已被消费
                channel.BasicAck(ea.DeliveryTag, false);
            };
            //启动消费者 设置为手动应答消息
            channel.BasicConsume(queName, false, consumer);
            
        }
    }
}

猜你喜欢

转载自blog.csdn.net/m0_54852350/article/details/124501873