.Net Core 集成 RabbitMQ 訂閱與發送

什麼是RabbitMQ?

  • 專業理解
    MQ全稱爲Message Queue,即消息隊列, RabbitMQ是由erlang語言開發,基於AMQP(Advanced MessageQueue 高級消息隊列協議)協議實現的消息隊列,它是一種應用程序之間的通信方法,消息隊列在分佈式系統開發中應用非常廣泛。
  • 通俗理解
    你可以把它想象成一個郵局:當你把你想要發佈的郵件放在郵箱中時,你可以確定郵差先生最終將郵件發送給你的收件人。在這個比喻中,RabbitMQ是郵政信箱,郵局和郵遞員。RabbitMQ和郵局的主要區別在於它不處理紙張,而是接受,存儲和轉發二進制數據塊。

使用場景

  • 任務異步處理
    將不需要同步處理的並且耗時長的操作由消息隊列通知消息接收方進行異步處理。提高了應用程序的響應時間。

  • 應用程序解耦合
    MQ相當於一箇中介,生產方通過MQ與消費方交互,它將應用程序進行解耦合。

組成原理

Broker:消息隊列服務進程,此進程包括兩個部分:Exchange和Queue。

Exchange:消息隊列交換機,按一定的規則將消息路由轉發到某個隊列,對消息進行過慮。

Queue:消息隊列,存儲消息的隊列,消息到達隊列並轉發給指定的消費方。

Producer:消息生產者,即生產方客戶端,生產方客戶端將消息發送到MQ。

Consumer:消息消費者,即消費方客戶端,接收MQ轉發的消息。

配置安裝

RabbitMQ下載地址:https://www.rabbitmq.com/download.html
RabbitMQ是由Erlang編寫,所以需要安裝Erlang

Windows安裝後,按 window 鍵會出現如下
在這裏插入圖片描述

這個時候點擊RabbitMQ Service - start 就可以啓動RabbitMQ服務,但是如果需要用到RabbitMQ 頁面管理工具,就需要開啓管理工具插件

點擊 RabbitMQ Command Prompt,輸入 rabbitmq-plugins list 就可以查看所有插件激活狀態,執行 rabbitmq-plugins enable rabbitmq_management 啓用 rabbitmq_management

在這裏插入圖片描述

重啓 RabbitMQ服務 ,訪問 http://127.0.0.1:15672/ 就可以訪問RabbitMQ管理頁面,默認賬號密碼都是 guest
在這裏插入圖片描述
在這裏插入圖片描述

.NET Core 集成、監聽與發送

作爲事件監聽,就需要程序啓動時就連接RabbitMQ。在 .Net Core 中,微軟給我們提供了後臺託管服務支持,不再像framework 時代一樣需要寫windows 服務程序了。Core 中託管服務是一個類,具有實現 IHostedService 接口的後臺任務邏輯。IHostedService 接口爲主機託管的對象定義了兩種方法:StartAsync 和 StopAsync,顧名思義前者會在Web主機啓動是調用,後者在主機正常關閉時觸發。除了IHostedService接口外,微軟還給我們提供了更加強大的抽象基類BackgroundService,你也可以繼承該類並在ExecuteAsync抽象方法中寫自己的業務邏輯,這樣就不必關注其他的工作了。

安裝RabbitMQ官方SDK ,通過命令或直接安裝Nuget包都行

Install-Package RabbitMQ.Client
配置文件中新增連接RabbitMQ服務實例的配置

"MessageQueue": {
    "RabbitConnect": {
      "HostName": "127.0.0.1", // 主機名稱
      "Port": 5672, // 主機端口
      "UserName": "guest", // 連接賬號
      "Password": "guest" // 連接密碼
    },
    "QueueBase": {
      "Exchange": "face.message.topic", // 交換機名稱
      "Queue": "face.host.queue", // 隊列名稱
      "ExchangeType": "topic", // 交換機類型 direct、fanout、headers、topic 必須小寫
      "ClientQueue": "face.client.queue" // 客戶端隊列名稱
    }
}

約定好配置文件的格式後,新建 .Net Core 強類型選項類。

    /// <summary>
    /// Rabbit 強類型配置
    /// </summary>
    public class RabbitConnectOption
    {
        /// <summary>
        /// Gets or sets the name of the host.
        /// </summary>
        /// <value>
        /// The name of the host.
        /// </value>
        public string HostName { get; set; }
        /// <summary>
        /// Gets or sets the port.
        /// </summary>
        /// <value>
        /// The port.
        /// </value>
        public int Port { get; set; }
        /// <summary>
        /// Gets or sets the name of the user.
        /// </summary>
        /// <value>
        /// The name of the user.
        /// </value>
        public string UserName { get; set; }
        /// <summary>
        /// Gets or sets the password.
        /// </summary>
        /// <value>
        /// The password.
        /// </value>
        public string Password { get; set; }
        /// <summary>
        /// Gets or sets the virtual host.
        /// </summary>
        /// <value>
        /// The virtual host.
        /// </value>
        public string VirtualHost { get; set; }
        /// <summary>
        /// Gets or sets the connection.
        /// </summary>
        /// <value>
        /// The connection.
        /// </value>
        public IConnection Connection { get; set; }
        /// <summary>
        /// Gets or sets the channel.
        /// </summary>
        /// <value>
        /// The channel.
        /// </value>
        public IModel Channel { get; set; }
    }

    /// <summary>
    /// 隊列配置基類
    /// </summary>
    public class QueueBaseOption
    {
        /// <summary>
        /// 交換機名稱
        /// </summary>
        /// <value>
        /// The exchange.
        /// </value>
        public string Exchange { get; set; }
        /// <summary>
        /// 隊列名稱
        /// </summary>
        /// <value>
        /// The queue.
        /// </value>
        public string Queue { get; set; }
        /// <summary>
        /// 交換機類型 direct、fanout、headers、topic 必須小寫
        /// </summary>
        /// <value>
        /// The type of the exchange.
        /// </value>
        public string ExchangeType { get; set; }
        /// <summary>
        /// 路由
        /// </summary>
        /// <value>
        /// The route key.
        /// </value>
        //public string RouteKey { get; set; }

        /// <summary>
        /// 客戶端隊列名稱
        /// </summary>
        /// <value>
        /// The client queue
        public string ClientQueue { get; set; }
    }

    /// <summary>
    /// 約定 強類型配置
    /// </summary>
    public class MessageQueueOption
    {
        /// <summary>
        /// rabbit 連接配置
        /// </summary>
        /// <value>
        /// The rabbit connect option.
        /// </value>
        public RabbitConnectOption RabbitConnect { get; set; }

        /// <summary>
        /// 批量推送消息到雲信的隊列配置信息
        /// </summary>
        /// <value>
        /// 
        /// </value>
        public QueueBaseOption QueueBase { get; set; }

    }

定義RabbitMQ 監聽服務抽象類 RabbitListenerHostService , 繼承 IHostedService

/// <summary>
    /// RabbitMQ 監聽服務
    /// </summary>
    /// <seealso cref="Microsoft.Extensions.Hosting.IHostedService" />
    public abstract class RabbitListenerHostService : IHostedService
    {
        /// <summary>
        /// 
        /// </summary>
        protected IConnection _connection;
        /// <summary>
        /// The channel
        /// </summary>
        protected IModel _channel;
        /// <summary>
        /// The rabbit connect options
        /// </summary>
        private readonly RabbitConnectOption _rabbitConnectOptions;
        /// <summary>
        /// The logger
        /// </summary>
        protected readonly ILogger _logger;
        /// <summary>
        /// Initializes a new instance of the <see cref="RabbitListenerHostService" /> class.
        /// </summary>
        /// <param name="messageQueueOption"></param>
        /// <param name="logger">The logger.</param>
        public RabbitListenerHostService(IOptions<MessageQueueOption> messageQueueOption, ILogger logger)
        {
            _rabbitConnectOptions = messageQueueOption.Value?.RabbitConnect;
            _logger = logger;
        }
        /// <summary>
        /// Triggered when the application host is ready to start the service.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
        /// <returns></returns>
        public Task StartAsync(CancellationToken cancellationToken)
        {
            try
            {
                if (_rabbitConnectOptions == null) return Task.CompletedTask;
                var factory = new ConnectionFactory()
                {
                    HostName = _rabbitConnectOptions.HostName,
                    Port = _rabbitConnectOptions.Port,
                    UserName = _rabbitConnectOptions.UserName,
                    Password = _rabbitConnectOptions.Password,
                };
                _connection = factory.CreateConnection();
                _channel = _connection.CreateModel();
                _rabbitConnectOptions.Connection = _connection;
                _rabbitConnectOptions.Channel = _channel;
                Process();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Rabbit連接出現異常");
            }
            return Task.CompletedTask;
        }

        /// <summary>
        /// Triggered when the application host is performing a graceful shutdown.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
        /// <returns></returns>
        public Task StopAsync(CancellationToken cancellationToken)
        {
            if (_connection != null)
                this._connection.Close();
            return Task.CompletedTask;
        }

        /// <summary>
        /// Processes this instance.
        /// </summary>
        protected abstract void Process();
    }

定義一個真正處理消息的監聽服務 MessageSubscribeHostService 繼承 RabbitListenerHostService,實現其中的抽象方法 Process()。

    /// <summary>
    /// 
    /// </summary>
    /// <seealso cref="Magicodes.Admin.Application.Core.RabbitMQ.RabbitListenerHostService" />
    public class MessageSubscribeHostService : RabbitListenerHostService
    {
        /// <summary>
        /// The services
        /// </summary>
        private readonly IServiceProvider _services;
        /// <summary>
        /// The batch advance option
        /// </summary>
        private readonly QueueBaseOption _queueBaseOption;
        /// <summary>
        /// Initializes a new instance of the <see cref="MessageSubscribeHostService" /> class.
        /// </summary>
        public MessageSubscribeHostService(IServiceProvider services, IOptions<MessageQueueOption> messageQueueOption,
            ILogger<MessageSubscribeHostService> logger) : base(messageQueueOption, logger)
        {
            _services = services;
            _queueBaseOption = messageQueueOption.Value?.QueueBase;
        }
        /// <summary>
        /// Processes this instance.
        /// </summary>
        protected override void Process()
        {
            _logger.LogInformation("調用ExecuteAsync");
            using (var scope = _services.CreateScope())
            {
                try
                {
                    //我們在消費端 從新進行一次 隊列和交換機的綁定 ,防止 因爲消費端在生產端 之前運行的 問題。
                    _channel.ExchangeDeclare(_queueBaseOption.Exchange, _queueBaseOption.ExchangeType, true);
                    _channel.QueueDeclare(_queueBaseOption.ClientQueue, true, false, false, null);
                    _channel.QueueBind(_queueBaseOption.ClientQueue, _queueBaseOption.Exchange, "face.log", null);
                    _logger.LogInformation("開始監聽隊列:" + _queueBaseOption.ClientQueue); // 監聽客戶端列隊
                    _channel.BasicQos(0, 1, false);//設置一個消費者在同一時間只處理一個消息,這個rabbitmq 就會將消息公平分發
                    var consumer = new EventingBasicConsumer(_channel);
                    consumer.Received += (ch, ea) =>
                    {
                        try
                        {
                            var content = Encoding.UTF8.GetString(ea.Body);
                            _logger.LogInformation($"{_queueBaseOption.ClientQueue}[face.log]獲取到消息:{content}");
                            // TODO: 對接業務代碼
                        }
                        catch (Exception ex)
                        {
                            _logger.LogError(ex, "");
                        }
                        finally
                        {
                            _channel.BasicAck(ea.DeliveryTag, false);
                        }
                    };

                    _channel.BasicConsume(_queueBaseOption.ClientQueue, false, consumer);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "");
                }
            }
        }
    }

定義消息推送服務類 MessagePublishService

public class MessagePublishService : ITransientDependency
    {
        /// <summary>
        /// The channel
        /// </summary>
        protected IModel _channel;
        /// <summary>
        /// The batch advance option
        /// </summary>
        private readonly QueueBaseOption _queueBaseOption;

        public MessagePublishService(IOptions<MessageQueueOption> messageQueueOption)
        {
            _queueBaseOption = messageQueueOption.Value?.QueueBase;
            _channel = messageQueueOption.Value?.RabbitConnect.Channel;
        }

        /// <summary>
        /// 發送消息
        /// </summary>
        public void SendMsg<T>(T msg , string routeKey)
        {
            _channel.ExchangeDeclare(_queueBaseOption.Exchange, _queueBaseOption.ExchangeType, true);
            _channel.QueueDeclare(_queueBaseOption.Queue, true, false, false, null);
            _channel.QueueBind(_queueBaseOption.Queue, _queueBaseOption.Exchange, routeKey, null);
            var basicProperties = _channel.CreateBasicProperties();
            //1:非持久化 2:可持久化
            basicProperties.DeliveryMode = 2;
            var json = JsonConvert.SerializeObject(msg);
            var payload = Encoding.UTF8.GetBytes(json);
            var address = new PublicationAddress(ExchangeType.Direct, _queueBaseOption.Exchange, routeKey);
            _channel.BasicPublish(address, basicProperties, payload);
        }
    }

新建 IServiceCollection 的擴展類,方便在Startup 中注入服務。

    /// <summary>
    /// 依賴注入擴展類
    /// </summary>
    public static class RabbitRegistrar
    {
        /// <summary>
        /// Adds the register queue sub.
        /// </summary>
        /// <param name="services">The services.</param>
        /// <param name="option">The option.</param>
        /// <returns></returns>
        public static IServiceCollection AddMessageQueueOption(this IServiceCollection services, Action<MessageQueueOption> option)
        {
            services.Configure(option);
            return services;
        }
        /// <summary>
        /// Adds the register queue sub.
        /// </summary>
        /// <param name="services">The services.</param>
        /// <param name="configuration">The configuration.</param>
        /// <returns></returns>
        public static IServiceCollection AddMessageQueueOption(this IServiceCollection services, IConfiguration configuration)
        {
            services.Configure<MessageQueueOption>(configuration);
            return services;
        }
        /// <summary>
        /// 服務自注冊,實現自管理
        /// </summary>
        /// <param name="services">The services.</param>
        /// <returns></returns>
        public static IServiceCollection AddMessageSubscribeHostService(this IServiceCollection services)
        {
            services.AddHostedService<MessageSubscribeHostService>();
            return services;
        }
    }

Startup ConfigureServices 中注入服務

RabbitRegistrar.AddMessageQueueOption(services, _appConfiguration.GetSection("MessageQueue"));
RabbitRegistrar.AddMessageSubscribeHostService(services);

啓動項目,如果能在RabbitMQ管理頁面看到連接存在,則配置成功
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章