【Rabbit MQ】詳細講解以及在C#(wpf)中的使用

352 阅读8分钟

图片.png

重點:生產者並不是把消息發送到隊列,所有的消息都是發送給交換機,再由交換機發送到隊列。

1.Docker Desk Install RabbitMq

1.1 Rabbit Install

  • 拉鏡像

    docker pull rabbitmq沒有界面的最新版

    docker pull rabbitmq:management有界面的最新版

    docker pull rabbitmq:3.11-management

  • 創建容器,可以修改賬密

    docker run -p ip:hostPort:containerPort語法

    docker run -d --name MyRabbit -p 15671-15672:15671-15672 -p 25672:25672 -p 15691-15692:15691-15692 -p 5671-5672:5671-5672 -p 4369:4369 -v /data/rabbitmq/data:/var/lib/rabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin rabbitmq:3.11-management

  • 因為我是window版本,直接app創建就好

    http://localhost:15672

    guest/guest

1.2 Add User

图片.png

编号

角色名称

备注

1

Admin/Administrator-超级管理员

可登录管理控制台,拥有所有权限,可查看用户所有信息,以及操作用户和策略(policy),

2

Monitoring-监控者

可登录,可查看节点信息,比如进程数、内存‘磁盘使用情况        

3

Policymaker-策略制定者

可登录,对policy策略进行管理,但不可查看节点西悉尼

4

Management-普通管理者

仅登录,无查看策略和节点权限

5

Impersonator-模拟者

6

None-其他

不可登录,普通得生产者和消费者

1.3 Permission

图片.png

1.4 Tutorial

RabbitMq提供了幾種模式:

  • Simple

    一個生產者,一個消費者,不需要設置交換機(默認交換機)

  • Work Queue

    一個生產者,多個消費者,不需要設置交換機(默認交換機)

  • Publish、Subscribe

    需要類型為fanout的交換機,並且交換機和隊列進行綁定。生產者發送消息到交換機後,交換機將消息發送到綁定隊列。

  • Routing

    需要類型為direct的交換機,交換機和隊列進行綁定並且要制定routing key。生產者發送消息到交換機,交換機通過routing key將消息發送給對應的隊列。

  • Topic

    需要類型為topic的交換機,交換機和隊列進行綁定並且要制定routing key。生產者發送消息到交換機,交換機通過routing key將消息發送給對應的隊列。

  • RPC

    不作介紹

www.rabbitmq.com/getstarted.…

图片.png

图片.png

1.5 Introduction

RabbitMQ 中的相关概念:

  • Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker

  • Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost创建 exchange/queue 等

  • Connection:publisher/consumer 和 broker 之间的 TCP 连接

  • Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method包含了channel id 帮助客户端和message broker 识别 channel,所以 channel之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销

  • Exchange:message 到达 broker 的第一站**,根据分发规则,匹配查询表中的 routing key,分发消息到queue中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) , header and fanout (multicast)

  • Queue:消息最终被送到这里等待 consumer 取走

  • Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据

Exchange:

调度策略是指Exchange在收到生产者发送的消息后依据规则把消息转发到一个或多个队列中保存。

调度策略与三个因素相关:

  • Exchange Type(Exchange的类型)
  • Binding Key(Exchange和Queue的绑定关系)
  • 消息的标记信息(Routing Key和headers)

Exchange根据消息的Routing Key和Exchange绑定Queue的Binding Key分配消息。

生产者在将消息发送给Exchange的时候,一般会指定一个Routing Key,来指定这个消息的路由规则,而这个Routing Key需要与Exchange Type及Binding Key联合使用才能最终生效。

在Exchange Type与Binding Key固定的情况下(一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定Routing Key来决定消息流向哪里。

  • direct

    通过routing key 和队列绑定在一起的

  • topic

    和direct类似,topic也是根据routing key,将exchange和queue绑定在一起。direct的routing key是精确匹配,而topic exchange的的routing key是模式匹配,类似于正则表达式匹配 .orange. 或者 lazy.# 。其中,* 代表一个单词,# 代表0个或多个单词。

  • fanout

    和direct exchange、topic exchange不同,fanout exchange不使用routing key,它会将消息路由到所有与其绑定的队列。

  • header

    headers exchange是根据Message的一些头部信息来分发过滤Message的,它会忽略routing key的属性,如果Header信息和message消息的头信息相匹配,那么这条消息就匹配上了

1.6 Queue

RabbitMQ Queue的属性:

  • Durable:该属性表示队列是否持久化至硬盘。如果希望队列中的消息不丢失,需要将消息声明为持久化,同时队列也需要持久化。
  • Exclusive:该属性表示队列是否为连接独占。如果队列为独占,那么当连接关闭后,队列即被删除。

RabbitMQ Queue的類型:

  • Classic:这是最常用的队列类型。Classic队列在消息发送到其上的消息被确认之前,会一直保留在队列中。
  • Quorum:这种类型的队列在大多数消息被确认后,才会将消息从队列中移除。这有助于确保消息被大多数队列接收,但可能会导致延迟。
  • Stream:这种类型的队列会将接收到的消息持久化,并且可以按照消息的原始发送顺序将消息分发到消费者。Stream类型的队列适用于需要持久化存储和顺序消费的场景。

RabbitMQ Queue的消息查詢:

图片.png

  • Nack:就是預覽
  • Ack:相當於被消費一次

2. Publish Code

2.1 Hello Sender

RoutingKey必須是Queue name

    public void HelloSender()
    {
        ConnectionFactory factory = new ConnectionFactory();
        factory.UserName = "admin";
        factory.Password = "admin";
        factory.VirtualHost = "cfx-virtual-host";
        factory.HostName = "localhost";

        var connection = factory.CreateConnection();
        var channel = connection.CreateModel();

        //声明一个队列(新的)
        channel.QueueDeclare(queue: "hello01",
                            durable: true,
                            exclusive: false,
                            autoDelete: false,
                            arguments: null);

        var sendbody = Encoding.UTF8.GetBytes(CfxSendMsg);
        channel.BasicPublish(exchange: string.Empty,
                 //routingKey就是queue name
                 routingKey: "hello01",
                 basicProperties: null,
                 body: sendbody);

        System.Diagnostics.Trace.WriteLine("----send msg : " + CfxSendMsg);
        channel.Close();
        connection.Close();
    }

2.2 Publish/Subscribe

不配置Routing Key,Exchange 是Fanout類型

    public void PublishAndSubscribeSender()
    {
        ConnectionFactory factory = new ConnectionFactory();
        factory.UserName = "admin";
        factory.Password = "admin";
        factory.VirtualHost = "cfx-virtual-host";
        factory.HostName = "localhost";

        var connection = factory.CreateConnection();
        var channel = connection.CreateModel();

        //声明一个队列
        channel.QueueDeclare(queue: "hello02",
                            durable: true,
                            exclusive: false,
                            autoDelete: false,
                            arguments: null);
        channel.QueueDeclare(queue: "hello03",
                            durable: true,
                            exclusive: false,
                            autoDelete: false,
                            arguments: null);

        channel.QueueBind(exchange: "my.cfx.02",
                          queue: "hello02",
                          routingKey: string.Empty,
                          arguments: null);
        channel.QueueBind(exchange: "my.cfx.02",
                          queue: "hello03",
                          routingKey: string.Empty,
                          arguments: null);

        var sendbody = Encoding.UTF8.GetBytes(CfxSendMsg);
        channel.BasicPublish(exchange: "my.cfx.02",
                 routingKey: string.Empty,
                 basicProperties: null,
                 body: sendbody);

        System.Diagnostics.Trace.WriteLine("----send msg : " + CfxSendMsg);
        channel.Close();
        connection.Close();
    }

2.3 Routing

配置Routingkey,Exchange的類型是Fanout or Direct

    public void RoutingSender()
    {
        ConnectionFactory factory = new ConnectionFactory();
        factory.UserName = "admin";
        factory.Password = "admin";
        factory.VirtualHost = "cfx-virtual-host";
        factory.HostName = "localhost";

        var connection = factory.CreateConnection();
        var channel = connection.CreateModel();

        //声明一个队列
        channel.QueueDeclare(queue: "hellonolan",
                durable: true,
                exclusive: false,
                autoDelete: false,
                arguments: null);
        channel.QueueDeclare(queue: "helloworld",
                durable: true,
                exclusive: false,
                autoDelete: false,
                arguments: null);

        //fanout 廣播交換機
        channel.QueueBind(queue: "hellonolan",
                          exchange: "amq.direct",
                          routingKey: "hello01",
                          arguments: null);
        channel.QueueBind(queue: "helloworld",
                          exchange: "amq.direct",
                          routingKey: "hello01",
                          arguments: null);
        channel.QueueBind(queue: "helloworld",
                          exchange: "amq.direct",
                          routingKey: "hello02",
                          arguments: null);

        var sendbody = Encoding.UTF8.GetBytes(CfxSendMsg);

        var routingKey = "hello01";

        channel.BasicPublish(exchange: "amq.direct",
                             routingKey: routingKey,
                             basicProperties: null,
                             body: sendbody);

        System.Diagnostics.Trace.WriteLine("----send msg : " + CfxSendMsg);
        channel.Close();
        connection.Close();
    }

2.4 Topic

使用通配符配置Bindingkey,Exchange的類型是Fanout or Topic

使用通配符的好處就是不用重啟服務,就能將消息發送給不同的隊列

    public void TopicSender()
    {
        ConnectionFactory factory = new ConnectionFactory();
        factory.UserName = "admin";
        factory.Password = "admin";
        factory.VirtualHost = "cfx-virtual-host";
        factory.HostName = "localhost";

        var connection = factory.CreateConnection();
        var channel = connection.CreateModel();

        //声明一个队列
        channel.QueueDeclare(queue: "hellotopic01",
                durable: true,
                exclusive: false,
                autoDelete: false,
                arguments: null);
        channel.QueueDeclare(queue: "hellotopic02",
                durable: true,
                exclusive: false,
                autoDelete: false,
                arguments: null);

        //Topic 廣播交換機
        channel.QueueBind(queue: "hellotopic01",
                          exchange: "my.cfx.01",
                          routingKey: "hello.#.#",
                          arguments: null);
        channel.QueueBind(queue: "hellotopic01",
                          exchange: "my.cfx.02",
                          routingKey: "#.world.#",
                          arguments: null);

        var sendbody = Encoding.UTF8.GetBytes(CfxSendMsg);

        var routingKey = "hello.world";

        channel.BasicPublish(exchange: "my.cfx.01",
                             routingKey: routingKey,
                             basicProperties: null,
                             body: sendbody);

        System.Diagnostics.Trace.WriteLine("----send msg : " + CfxSendMsg);
        channel.Close();
        connection.Close();
    }

3. Subscribe Code

3.1 Hello

Hello Demo:Default Exchange

        public void HelloOneReceive()
        {

            var factory = new ConnectionFactory();
            factory.UserName = "admin";
            factory.Password = "admin";
            factory.VirtualHost = "cfx-virtual-host";
            factory.HostName = "localhost";

            var connection = factory.CreateConnection();
            var channel = connection.CreateModel();

            channel.QueueDeclare(queue: "hello01",
                                 durable: true,
                                 exclusive: false,
                                 autoDelete: false,
                                 arguments: null);

            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (model, ea) =>
            {
                var body = ea.Body.ToArray();
                var message = Encoding.UTF8.GetString(body);
                System.Diagnostics.Trace.WriteLine("----hello receive msg : " + message);
            };
            channel.BasicConsume(queue: "hello01",
                                 noAck: true,
                                 consumer: consumer);
        }

3.2 work

        public void WorkSubscribe01()
        {
            ResultList = new ObservableCollection<string>();

            var factory = new ConnectionFactory();
            factory.UserName = "admin";
            factory.Password = "admin";
            factory.HostName = "localhost";
            factory.VirtualHost = "cfx-virtual-host";

            var connection = factory.CreateConnection();
            var channel = connection.CreateModel();

            channel.QueueDeclare(queue: "work_queue",
                                 durable: true,
                                 exclusive: false,
                                 autoDelete: false,
                                 arguments: null);

            //channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false); 这段代码设置预取策略。
            //prefetchSize: 0 是指设置预取的字节数为0,即不预取任何消息。
            //prefetchCount: 1 是指设置预取的消息数量为1,即每次只预取一条消息。
            //global: false 预取策略是否为全局。true=>所有的消费者都将共享相同的预取策略;false=>每个消费者都将有自己的预取策略。
            channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);

            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += SubscribeMsg;

            channel.BasicConsume(queue: "work_queue",
                                 autoAck: true,
                                 consumer: consumer);

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

3.3 Publish

Publish mode: Fanout Exchange,No Routingkey

        public void PublishReceive()
        {
            ObservableCollection<string> bufferCollection = new ObservableCollection<string>();
            var factory = new ConnectionFactory();
            factory.UserName = "admin";
            factory.Password = "admin";
            factory.VirtualHost = "cfx-virtual-host";
            factory.HostName = "localhost";

            var connection = factory.CreateConnection();
            var channel = connection.CreateModel();

           // channel.ExchangeDeclare(exchange: "my.cfx.02", type: ExchangeType.Fanout);

            channel.QueueDeclare(queue: "hello02",
                                durable: true,
                                exclusive: false,
                                autoDelete: false,
                                arguments: null);

            channel.QueueBind(queue: "hello02",
                  exchange: "my.cfx.02",
                  routingKey: string.Empty);     

            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (model, ea) =>
            {
                
                byte[] body = ea.Body.ToArray();
                var message = Encoding.UTF8.GetString(body);
                System.Diagnostics.Trace.WriteLine("----hello receive msg : " + message);
                bufferCollection.Add(message);
            };
            channel.BasicConsume(queue: "hello02",
                                 noAck: true,
                                 consumer: consumer);
        }

3.4 Routing、Topic

多個QueueBind

        channel.QueueBind(queue: "hello02",
              exchange: "my.cfx.02",
              routingKey: "xxx");  

3.5 Display Subscribe Msg

    public void HelloSubscribe()
    {
        ResultList = new ObservableCollection<string>();
        System.Diagnostics.Trace.WriteLine("hello");

        //port 5672
        var factory = new ConnectionFactory();
        factory.UserName = "admin";
        factory.Password = "admin";
        factory.VirtualHost = "cfx-virtual-host";
        factory.HostName = "localhost";

        var connection = factory.CreateConnection();
        var channel = connection.CreateModel();

        channel.QueueDeclare(queue: "hello_queue",
                             durable: true,
                             exclusive: false,
                             autoDelete: false,
                             arguments: null) ;

        var consumer = new EventingBasicConsumer(channel);
        consumer.Received += SubscribeMsg;

        channel.BasicConsume(queue: "hello_queue",
                             autoAck: true,
                             consumer: consumer);

    }
    public void SubscribeMsg(object sender,BasicDeliverEventArgs ea) {
        var body = ea.Body.ToArray();
        var message = Encoding.UTF8.GetString(body);
        System.Diagnostics.Trace.WriteLine(message);

        aggregator.SendMessage(message);
    }


    public void ShowMsg() {
        aggregator.RegisterMessage(arg => {

            System.Diagnostics.Trace.WriteLine ("Subscribe: "+arg.Message);
            ResultList.Add(arg.Message);

        });
    }

重點:必須先渲染UI

    public static class DialogExtension
    {
        public static void UpdateLoading(this IEventAggregator aggregator, bool isOpen)
        {
            aggregator.GetEvent<UpdateLoadingEvent>().Publish(new UpdateModel() { IsOpen = isOpen });
        }

        public static void Resgiter(this IEventAggregator aggregator, Action<UpdateModel> action)
        {
            aggregator.GetEvent<UpdateLoadingEvent>().Subscribe(action);
        }

        public static void RegisterMessage(this IEventAggregator aggregator,
    Action<MessageModel> action)
        {

            aggregator.GetEvent<MessageEvent>().Subscribe(action,ThreadOption.UIThread);

        }

        /// <summary>
        /// 发送提示消息
        /// </summary>
        /// <param name="aggregator"></param>
        /// <param name="message"></param>
        public static void SendMessage(this IEventAggregator aggregator, string message)
        {
            aggregator.GetEvent<MessageEvent>().Publish(new MessageModel()
            {
                Message = message
            });
        }
    }