.NET中间件 -- 消息队列- RabbitMQ

RabbitMQ是一款使用Erlang语言开发的,实现AMQP(高级消息队列协议)的开源消息中间件。

消息指的是两个应用间传递的数据。数据的类型有很多种形式,可能只包含文本字符串,也可能包含嵌入对象。

“消息队列(Message Queue)”是在消息的传输过程中保存消息的容器。在消息队列中,通常有生产者和消费者两个角色。生产者只负责发送数据到消息队列; 消费者只负责从消息队列中取出数据处理。

启用之前下载安装RabbitMQ

RabbitMQ防止数据丢失:

1.生产者发送消息时,使用Confirm机制,来确认消息到达消息队列

2.RabbitMQ端设置消息持久化

3. 消费者消费消息时,使用ACK事务机制,进行手动签收,正常消费则返回确认签收,如果出现异常,则返回拒绝签收重回队列。

生产者RabbitMQClient.cs

namespace RabbitMQClient
{
    class RabbitMQClient
    {
        public void SendMessage()
        {
            //实例化连接工厂
            var factory = new ConnectionFactory()
            {
                HostName = "localhost",  //RabbitMQ服务在本地运行
                Port = 5672,                   //端口号
                UserName = "guest",    //默认用户名
                Password = "guest"     //默认密码
            };

            //建立连接
            using (var connection=factory.CreateConnection())
            {
                //创建信道
                using (var channel=connection.CreateModel())
                {
                    //声明队列
                    /* 创建一个名为 ProcessQueue 的消息队列,如果名称相同不会重复创建,参数解释:
                     * 参1:消息队列名称;
                     * 参2:是否持久化,持久化的队列会存盘,服务器重启后任然存在;
                     * 参3:是否为排他队列,排他队列表示仅对首次声明它的连接可见,并在连接断开时自动删除。这种队列适用于一个客户端同时发送和读取消息的应用场景。
                     * 参4:是否自动删除,自动删除的前提是:至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。
                     * 参5:设置队列的其他一些参数,如 x-rnessage-ttl、x-expires、x-rnax-length、x-rnax-length-bytes、x-dead-letter-exchange、x-deadletter-routing-key、x-rnax-priority 等。
                     */
                    channel.QueueDeclare("ProcessQueue", durable: true, false, false, null);

                    //设置消息优先级(Priority)
                    //channel.QueueDeclare("ProcessQueue", durable: true, false, false, new Dictionary {
                    //    { "x-rnax-priority", 10 }
                    //});

                    var properties = channel.CreateBasicProperties();
                    properties.DeliveryMode = 2; //持久化

                    //设置优先级,0-9,大的优先发送,先消费
                    //properties.Priority = 9;

                    //轮询调度(默认)
                    //此情形下,RabbitMQ把信息按顺序发送给每一个消费者。平均每个消费者将获得同等数量的信息。

                    /*公平分发
                     * 设置消息通道的基础 Qos 参数
                     * 只有当消费者回传消息标记后,才会将下一个消息发送给它,否则将消息分发给其它空闲的消费者
                     * prefetchCount:1 表示告诉 RabbitMQ, 在未接收到消费者确认消息之前,不在分发消息
                     */
                    channel.BasicQos(0, prefetchCount: 1, false);
                    //构建字节数据包
                    string message = State.Initialized.ToString();
                    var body = Encoding.UTF8.GetBytes(message);

                    //开启confirm模式
                    channel.ConfirmSelect();
                    //发送数据包
                    channel.BasicPublish("", "ProcessQueue", properties, body);
                    Console.WriteLine($"已发布消息:{message}");

                    message = State.Terminated.ToString();
                    body = Encoding.UTF8.GetBytes(message);
                    channel.BasicPublish("", "ProcessQueue", properties, body);
                    Console.WriteLine($"已发布消息:{message}");

                    message = State.Error.ToString();
                    body = Encoding.UTF8.GetBytes(message);
                    channel.BasicPublish("", "ProcessQueue", properties, body);
                    Console.WriteLine($"已发布消息:{message}");

                    if (channel.WaitForConfirms())
                    {
                        Console.WriteLine("所有消息均已发送到Broker");
                    }
                }
            }
        }

        //将生产者推送的信息发给每个订阅的消费者处理,使用Exchange实现
        public void PublishMessage()
        {
            var factory = new ConnectionFactory()
            {
                HostName ="localhost",
                Port = 5672,
            };

            using (var connection=factory.CreateConnection())
            {
                using (var channel=connection.CreateModel())
                {
                    //声明信息交换机,
                    //type: fanout(将信息分发到exchange上绑定的所有队列上);direct(消费者绑定的队列名称须和生产者发布指定的路由名称一致)
                    channel.ExchangeDeclare(exchange:"fanoutTest",type:ExchangeType.Fanout);

                    var message = "Kilter";
                    byte[] body = Encoding.UTF8.GetBytes(message);

                    //发布到指定exchange,fanout类型的会忽视routingKey的值,所以无需填写
                    channel.BasicPublish(exchange:"fanoutTest",routingKey:"",null,body);
                    Console.WriteLine($"发布消息:{message} 到exchange");

                }
            }
        }


    }

    public enum State
    {
        Terminated,             // 终止状态
        Initialized,            // 初始化状态
        Idle,                   // 空闲
        Busy,                   // 忙碌
        Pause,                  // 暂停状态

        Warning,                // 警告,人工消除后可继续
        Error,                  // 错误,人工消除后可继续
        FatalError,             // 致命错误,无法继续工作,仅可通过重新初始化
    }
}

消费者RabbitMQServer.cs

class RabbitMQServer
    {
        public  ConcurrentDictionary methodDic = new ConcurrentDictionary();
        public object Instance { get; set; }
        public RabbitMQServer()
        {
            SetExecutor();
        }

        public void SetExecutor()
        {
            Type type= Assembly.Load("RabbitMQServer").GetType("RabbitMQServer.Executor");
            Instance = Activator.CreateInstance(type);
            MethodInfo[] methods = type.GetMethods();
            foreach (var item in methods)
            {
                methodDic.TryAdd(item.Name,item);
            }
        }

        public void ReceiveMessage()
        {
            var factory = new ConnectionFactory()
            {
                HostName = "localhost",
                Port = 5672,
                UserName = "guest",
                Password = "guest"
            };

            //tips: 不能放入 using 语句中,否则当 using 语句结束后会 Close 连接,EventingBasicConsumer.Received 事件将不会被触发
            var connection = factory.CreateConnection();
            var channel = connection.CreateModel();
            channel.QueueDeclare("ProcessQueue", durable:true, false, false, null);
            //构造消费者实例(指定消息通道)
            var consumer = new EventingBasicConsumer(channel);
            /*消费者消费消息(在当前通道中监听 ProcessQueue 队列,并进行消费)
            autoAck参数属性
            true:自动信息确认,当消费者接收到信息后,自动发送ack信号,不管信息是否处理完毕
            false:关闭自动信息确认,通过调用BasicAck方法手动进行信息确认,表示开启消息响应的功能, 当一条消息发送给消费者后,该消息必须得到消费者的“确认”后,RabbitMQ 才会将该消息删除。
            */
            channel.BasicConsume("ProcessQueue", autoAck: false, consumer);
            //绑定消息接收后的事件委托
            consumer.Received += (sender, e) =>
            {
                //ReadOnlyMemory转化为byte[] :  .ToArray()
                //string转化为ReadOnlyMemory: .AsMemory()
                var body = e.Body.ToArray();    //消息字节数组
                var message = Encoding.UTF8.GetString(body);  //消息内容
                Console.WriteLine($"已消费消息:{message}");
                //根据消息,添加逻辑
                if (methodDic.ContainsKey(message))
                {
                    methodDic[message].Invoke(Instance, null);
                }

                //消息响应,在消息处理完成后回传该消息标记, 只有当响应此消息标记后,该消息才会在消息队列中删除
                //deliveryTag参数是分发的标记,multiple表示是否确认多条。
                channel.BasicAck(e.DeliveryTag, false);

                //用于拒绝消息,requeue表示消息被拒绝后是否重新放回queue中,true表示放回queue中,false表示直接丢弃。
                //channel.BasicReject(e.DeliveryTag, requeue: false);
            };

        }

        //多个消费者订阅一个生产者
        public void SubscribeMessage()
        {
            var factory = new ConnectionFactory()
            {
                HostName = "localhost",
                Port = 5672
            };

            var connection = factory.CreateConnection();
            var channel = connection.CreateModel();
            //声明信息交换机
            channel.ExchangeDeclare(exchange: "fanoutTest", type: ExchangeType.Fanout);
            //循环生产3个消费者,模拟此情境
            for (int i = 0; i < 3; i++)
            {
                //生成随机队列名称
                var queuename = channel.QueueDeclare().QueueName;
                //绑定队列到指定fanout类型exchange
                channel.QueueBind(queuename, "fanoutTest", "", null);

                var consumer = new EventingBasicConsumer(channel);
                consumer.Received += (sender, e) =>
                {
                    var body = e.Body.ToArray();    //消息字节数组
                    var message = Encoding.UTF8.GetString(body);  //消息内容
                    Console.WriteLine($"消费者{queuename}通过exchange已消费消息:{message}");
                };

                channel.BasicConsume(queuename, true, consumer);
            }
            
        }


    }

    public class Executor
    {
        public void Terminated()
        {
            Console.WriteLine("执行Terminated对应事件");
        }

        public void Initialized()
        {
            Console.WriteLine("执行Initialized对应事件");
        }

        public void Error()
        {
            Console.WriteLine("执行Error对应事件");
        }
    }

运行结果:

.NET中间件 -- 消息队列- RabbitMQ_第1张图片

参考:

[1] C#调用RabbitMQ实现消息队列 - kiba518 - 博客园 (cnblogs.com)

[2] 快速掌握RabbitMQ(三)——消息确认、持久化、优先级的C#实现 - 捞月亮的猴子 - 博客园 (cnblogs.com)

[3] C# 消息队列之 RabbitMQ 进阶篇 - Abeam - 博客园 (cnblogs.com)

[4] C#教程之RabbitMQ基础入门篇|C#教程 (xin3721.com)

你可能感兴趣的:(.NET中间件,消息队列,rabbitmq,c#)