.Net Core中使用RabbitMQ

高空燕子飞过發表於2024-05-08

RabbitMQ是一個開源的,基於AMQP(Advanced Message Queuing Protocol)協議的完整的可複用的企業級訊息隊,RabbitMQ可以實現點對點,釋出訂閱等訊息處理模式。

RabbitMQ有五種模式

  1. 簡單工作模式(一對一):一個生產者,一個佇列,一個消費者
  2. 工作模式(一對多):一個生產者,一個佇列,多個消費者
  3. 釋出訂閱模式(Fanout):一個生產者,一個交換機,多個佇列,多個消費者,一個訊息可以被多個消費者消費
  4. 路由模式(Direct):一個生產者,一個交換機,多個佇列,多個消費者,key,訊息只傳送給符合條件的訊息佇列
  5. 萬用字元模式(Topic):萬用字元和路由模式類似,路由是匹配Key,萬用字元是模糊匹配,主要符合模糊匹配條件,都可以收到訊息

下面貼上一下,專案中用到的一些RabbitMQ相關內容。

1、配置檔案設定RabbitMQ的URL連結

.Net Core中使用RabbitMQ
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "MySql": "server=192.168.0.140;port=3306;userid=root;password=1qaz@WSX3edc;database=netsituation",
    "RedisConnection": "127.0.0.1:6379,defaultDatabase=0"
  },
  "rabbit": {
    "uri": "amqp://guest:guest@127.0.0.1:5672/"
  },
  "Kestrel": {
    "EndPoints": {
      "Http": {
        "Url": "http://*:8001"
      }
    }
  }
}
View Code

2、連線字串類

.Net Core中使用RabbitMQ
    /// <summary>
    /// 連線字串類
    /// </summary>
    public class RabbitOption
    {
        public RabbitOption(IConfiguration config)
        {
            if (config == null)
                throw new ArgumentException(nameof(config));

            var section = config.GetSection("rabbit");
            section.Bind(this);
        }

        public string Uri { get; set; }
    }
View Code

3、透過IOC建立單例的RabbitMQ的客戶端

.Net Core中使用RabbitMQ
    /// <summary>
    /// 透過IOC建立單例的RabbitMQ的客戶端
    /// </summary>
    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddeRabbitMQConnection(this IServiceCollection services, IConfiguration configuration)
        {
            if (services == null)
                throw new ArgumentException(nameof(services));

            if (configuration == null)
                throw new ArgumentException(nameof(configuration));

            var rabbitOption = new RabbitOption(configuration);

            var factory = new ConnectionFactory { Uri = new Uri(rabbitOption.Uri), AutomaticRecoveryEnabled = true, NetworkRecoveryInterval = TimeSpan.FromSeconds(60) };

            services.AddSingleton<IRabbitMQPersistentConnection>(x =>
            {
                var logger = x.GetRequiredService<ILogger<RabbitMQPersistentConnection>>();
                return new RabbitMQPersistentConnection(factory, logger);
            });

            return services;
        }
    }
View Code

4、定義連線RabbitMQ的介面

.Net Core中使用RabbitMQ
    public interface IRabbitMQPersistentConnection
    {
        /// <summary>
        /// 判斷是否連線
        /// </summary>
        bool IsConnected { get; }
        /// <summary>
        /// 連線
        /// </summary>
        /// <returns></returns>
        bool TryConnect();
        /// <summary>
        /// 建立模型
        /// </summary>
        /// <returns></returns>
        IModel CreateModel();
    }
View Code

5、介面實現

.Net Core中使用RabbitMQ
    public class RabbitMQPersistentConnection : IRabbitMQPersistentConnection
    {
        private readonly IConnectionFactory connectionFactory;
        private readonly ILogger<RabbitMQPersistentConnection> logger;

        private IConnection connection;

        private const int RETTRYCOUNT = 6;

        private static readonly object lockObj = new object();
        public static bool IsBreak = false;
        public RabbitMQPersistentConnection(IConnectionFactory connectionFactory, ILogger<RabbitMQPersistentConnection> logger)
        {
            this.connectionFactory = connectionFactory;
            this.logger = logger;
        }

        public bool IsConnected
        {
            get
            {
                return connection != null && connection.IsOpen;
            }
        }

        public void Cleanup()
        {
            try
            {
                connection.Dispose();
                connection = null;

            }
            catch (IOException ex)
            {
                logger.LogCritical(ex.ToString());
            }
        }

        public IModel CreateModel()
        {
            if (!IsConnected)
            {
                connection.Close();
                throw new InvalidOperationException("連線不到rabbitmq");
            }
            return connection.CreateModel();
        }

        public bool TryConnect()
        {
            logger.LogInformation("RabbitMQ客戶端嘗試連線");

            lock (lockObj)
            {
                if (connection == null)
                {
                    var policy = Policy.Handle<SocketException>()
                        .Or<BrokerUnreachableException>()
                        .WaitAndRetry(RETTRYCOUNT, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
                        {
                            logger.LogWarning(ex.ToString());
                        });

                    policy.Execute(() =>
                    {
                        connection = connectionFactory.CreateConnection();
                    });
                }

                if (IsConnected)
                {
                    connection.ConnectionShutdown += OnConnectionShutdown;
                    connection.CallbackException += OnCallbackException;
                    connection.ConnectionBlocked += OnConnectionBlocked;

                    logger.LogInformation($"RabbitMQ{connection.Endpoint.HostName}獲取了連線");
                    return true;
                }
                else
                {
                    logger.LogCritical("無法建立和開啟RabbitMQ連線");
                    return false;
                }
            }
        }

        private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e)
        {

            logger.LogWarning("RabbitMQ連線異常,嘗試重連...");

            Cleanup();
            TryConnect();
        }

        private void OnCallbackException(object sender, CallbackExceptionEventArgs e)
        {

            logger.LogWarning("RabbitMQ連線異常,嘗試重連...");

            Cleanup();
            TryConnect();
        }

        private void OnConnectionShutdown(object sender, ShutdownEventArgs reason)
        {

            logger.LogWarning("RabbitMQ連線異常,嘗試重連...");
            IsBreak = true;
            Cleanup();
            TryConnect();
        }
    }
View Code

6、定義釋出訊息介面

.Net Core中使用RabbitMQ
    public interface IMessageService
    {
        /// <summary>
        /// 傳送訊息(工作佇列模式)
        /// </summary>
        /// <param name="queueName"></param>
        /// <param name="body"></param>
        void SendMessage(string queueName, byte[] body);

        /// <summary>
        /// 傳送訊息(釋出訂閱模式)
        /// </summary>
        /// <param name="exchangeName"></param>
        /// <param name="body"></param>
        void SendFanoutMessage(string exchangeName, byte[] body);
    }
View Code

7、實現釋出訊息介面

.Net Core中使用RabbitMQ
    /// <summary>
    ///  RabbitMQ實現
    /// </summary>
    public class MessageService : IMessageService
    {
        private readonly IRabbitMQPersistentConnection rabbitMQPersistentConnection;
        public MessageService(IRabbitMQPersistentConnection rabbitMQPersistentConnection)
        {
            this.rabbitMQPersistentConnection = rabbitMQPersistentConnection;
        }

        public void SendFanoutMessage(string exchangeName,byte[] body)
        {
            if (!rabbitMQPersistentConnection.IsConnected)
            {
                rabbitMQPersistentConnection.TryConnect();
            }

            using (var channel = rabbitMQPersistentConnection.CreateModel())
            {
                //把交換機設定成Fanout模式
                channel.ExchangeDeclare(exchangeName, ExchangeType.Fanout, false, false);
                var properties = channel.CreateBasicProperties();
                properties.Timestamp = new AmqpTimestamp(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
                properties.Persistent = true;
                channel.BasicPublish(exchangeName, "", properties, body);
            }
        }

        public void SendMessage(string queueName, byte[] body)
        {
            if (!rabbitMQPersistentConnection.IsConnected)
            {
                rabbitMQPersistentConnection.TryConnect();
            }

            using (var channel = rabbitMQPersistentConnection.CreateModel())
            {
                channel.QueueDeclare(queueName, true, false, false, null);
                var properties = channel.CreateBasicProperties();
                properties.Timestamp = new AmqpTimestamp(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
                properties.Persistent = true;
                channel.BasicPublish("", queueName, properties, body);
            }
        }
    }
View Code

8、RabbitMQ連線引數

.Net Core中使用RabbitMQ
    /// <summary>
    /// RabbitMQ連線引數
    /// </summary>
    public class RabbitMQModel
    {
        /// <summary>
        /// 交換機名稱
        /// </summary>
        public string ExchangeName = "pvm";
        /// <summary>
        /// 訊息佇列名稱
        /// </summary>
        public string QueueName { get; set; }
        /// <summary>
        /// 訊息內容
        /// </summary>
        public byte[] Body { get; set; }
    }
View Code

9、接收訊息(Topic)

.Net Core中使用RabbitMQ
        public ReceiveCrackConsumer(IRabbitMQPersistentConnection rabbitMQPersistentConnection, ILogger<ReceiveCrackConsumer> logger, IWritableOptions<AppSettings> options, IMessageService msgSrv)
        {
            this.logger = logger;
            this.msgSrv = msgSrv;
            this.options = options;
            this.rabbitMQPersistentConnection = rabbitMQPersistentConnection;
        }
        public void CrackConsumer()
        {
            if (!rabbitMQPersistentConnection.IsConnected)
            {
                rabbitMQPersistentConnection.TryConnect();
            }

            consumerchannel = rabbitMQPersistentConnection.CreateModel();

            //訊息佇列名稱
            string queueName = $"CrackDispatcher_{options.Value.ServiceID}.Data";
            consumerchannel.ExchangeDeclare(RabbitMQModel.ExchangeName, ExchangeType.Topic, true, false);
            consumerchannel.QueueDeclare(queueName, true, false, false);
            consumerchannel.QueueBind(queueName, RabbitMQModel.ExchangeName, $"{options.Value.ServiceID}.Data");
            var consumer = new EventingBasicConsumer(consumerchannel);

            consumer.Received += OnConsumerHash;
            consumerchannel.BasicConsume(queueName, false, consumer);
        }

        /// <summary>
        /// 處理控制通道訊息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ea"></param>
        private void OnConsumerHash(object sender, BasicDeliverEventArgs ea)
        {
            try
            {
                byte[] message = ea.Body.ToArray();
                logger.LogDebug($"接收到資訊為:{Encoding.UTF8.GetString(message)}");
                var msg = JsonConvert.DeserializeObject<MessageReceive>(Encoding.UTF8.GetString(message));
                if (msg == null)
                {
                    logger.LogDebug($"訊息為空返回");
                    return;
                }
                if (msg.RabbitMQType == RabbitMQType.TaskPublish)
                {
                    CrackHashcat(msg.Msg);
                }
                else if (msg.RabbitMQType == RabbitMQType.Resume)
                {
                    Resume(msg.Msg);
                }
                else
                {
                    logger.LogDebug($"未識別訊息型別,返回");
                }
            }
            catch (Exception ex)
            {
                logger.LogCritical(ex, "異常");
            }
            consumerchannel.BasicAck(ea.DeliveryTag, false);
        }
View Code

相關文章