RabbitMQ是一個開源的,基於AMQP(Advanced Message Queuing Protocol)協議的完整的可複用的企業級訊息隊,RabbitMQ可以實現點對點,釋出訂閱等訊息處理模式。
RabbitMQ有五種模式
- 簡單工作模式(一對一):一個生產者,一個佇列,一個消費者
- 工作模式(一對多):一個生產者,一個佇列,多個消費者
- 釋出訂閱模式(Fanout):一個生產者,一個交換機,多個佇列,多個消費者,一個訊息可以被多個消費者消費
- 路由模式(Direct):一個生產者,一個交換機,多個佇列,多個消費者,key,訊息只傳送給符合條件的訊息佇列
- 萬用字元模式(Topic):萬用字元和路由模式類似,路由是匹配Key,萬用字元是模糊匹配,主要符合模糊匹配條件,都可以收到訊息
下面貼上一下,專案中用到的一些RabbitMQ相關內容。
1、配置檔案設定RabbitMQ的URL連結
{ "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" } } } }
2、連線字串類
/// <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; } }
3、透過IOC建立單例的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; } }
4、定義連線RabbitMQ的介面
public interface IRabbitMQPersistentConnection { /// <summary> /// 判斷是否連線 /// </summary> bool IsConnected { get; } /// <summary> /// 連線 /// </summary> /// <returns></returns> bool TryConnect(); /// <summary> /// 建立模型 /// </summary> /// <returns></returns> IModel CreateModel(); }
5、介面實現
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(); } }
6、定義釋出訊息介面
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); }
7、實現釋出訊息介面
/// <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); } } }
8、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; } }
9、接收訊息(Topic)
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); }