RabbitMQ是什么,怎么使用我就不介绍了,大家可以到园子里搜一下教程。本篇的重点在于实现服务与服务之间的异步通信。

首先说一下为什么要使用消息队列来实现服务通信:1.提高接口并发能力。  2.保证服务各方数据最终一致。  3.解耦。

使用消息队列通信的优点就是直接调用的缺点,比如在直接调用过程中发生未知错误,很可能就会出现数据不一致的问题,这个时候就需要人工修补数据,如果有过这个经历的同学一定是可怜的,人工修补数据简直痛苦!!再比如高并发情况下接口直接挂点,这就更直白了,接口挂了,功能就挂了,事故报告写起来!!而消息队列可以轻松解决上面两个问题,接口发生错误,不要紧,MQ重试一下,再不行,人工重试MQ;在使用消息队列的时候,请求实际是被串行化,简单说就是排队,所以再也不用担心因为并发导致数据不一致或者接口直接挂掉的问题。

我现在公司使用的消息队列排队的请求最高的有上万个,所以完全不需要担心MQ的性能。

OK,我们来实现一下微服务里如何使用消息队列,主要思路是这样的:

【提供消费者注册界面,用于绑定RoutingKey和队列;消息发布后,根据RoutingKey去Redis中查找对应的服务地址,然后异步调用。】

上面这句话就是消息队列的主体思路,也是我司现在使用的方式,话不多说,代码敲起来。

首先看下我们的项目结构:

首先我们需要先建三个这样的类库,这里面有些东西是用不到的,当然最最主要的就是标记出来的消息队列部分,现在暂时提供了两个方法,分别是发布(Publish)和订阅(Subscribe)。

首先新增消息·队列接口类IEventBus,这个将来用于在业务系统中注入使用,这里提供了发布订阅方法:

  1. public interface IEventBus
  2. {
  3. void Publish(string RoutingKey, object Model);
  4.  
  5. void Subscribe(string QueueName, string RoutingKey);
  6. }

新增RabbitMQ操作接口类IRabbitMQPersistentConnection,这个用来检查RabbitMQ的连接和释放:

  1. public interface IRabbitMQPersistentConnection : IDisposable
  2. {
  3. bool IsConnected { get; }
  4.  
  5. bool TryConnect();
  6.  
  7. IModel CreateModel();
  8. }

新增IRabbitMQPersistentConnection的实现类DefaultRabbitMQPersistentConnection,这个是RabbitMQ连接和释放方法的具体实现,这个没什么可说的,大家一看就知道了,就是检查RabbitMQ的连接状态,没有连接创建连接,发生错误的捕捉错误重新连接,这里用到了Polly的重新策略:

  1. public class DefaultRabbitMQPersistentConnection:IRabbitMQPersistentConnection
  2. {
  3. private readonly IConnectionFactory _connectionFactory;
  4. private readonly ILogger<DefaultRabbitMQPersistentConnection> _logger;
  5. private readonly int _retryCount;
  6. IConnection _connection;
  7. bool _disposed;
  8.  
  9. object sync_root = new object();
  10.  
  11. public DefaultRabbitMQPersistentConnection(IConnectionFactory connectionFactory, ILogger<DefaultRabbitMQPersistentConnection> logger, int retryCount = )
  12. {
  13. _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
  14. _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  15. _retryCount = retryCount;
  16. }
  17.  
  18. public bool IsConnected
  19. {
  20. get
  21. {
  22. return _connection != null && _connection.IsOpen && !_disposed;
  23. }
  24. }
  25.  
  26. public IModel CreateModel()
  27. {
  28. if (!IsConnected)
  29. {
  30. throw new InvalidOperationException("No RabbitMQ connections are available to perform this action");
  31. }
  32.  
  33. return _connection.CreateModel();
  34. }
  35.  
  36. public void Dispose()
  37. {
  38. if (_disposed) return;
  39.  
  40. _disposed = true;
  41.  
  42. try
  43. {
  44. _connection.Dispose();
  45. }
  46. catch (IOException ex)
  47. {
  48. _logger.LogCritical(ex.ToString());
  49. }
  50. }
  51.  
  52. public bool TryConnect()
  53. {
  54. _logger.LogInformation("RabbitMQ Client is trying to connect");
  55.  
  56. lock (sync_root)
  57. {
  58. var policy = RetryPolicy.Handle<SocketException>()
  59. .Or<BrokerUnreachableException>()
  60. .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(, retryAttempt)), (ex, time) =>
  61. {
  62. _logger.LogWarning(ex.ToString());
  63. });
  64.  
  65. policy.Execute(() =>
  66. {
  67. _connection = _connectionFactory
  68. .CreateConnection();
  69. });
  70.  
  71. if (IsConnected)
  72. {
  73. _connection.ConnectionShutdown += OnConnectionShutdown;
  74. _connection.CallbackException += OnCallbackException;
  75. _connection.ConnectionBlocked += OnConnectionBlocked;
  76.  
  77. _logger.LogInformation($"RabbitMQ persistent connection acquired a connection {_connection.Endpoint.HostName} and is subscribed to failure events");
  78.  
  79. return true;
  80. }
  81. else
  82. {
  83. _logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened");
  84.  
  85. return false;
  86. }
  87. }
  88. }
  89.  
  90. private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e)
  91. {
  92. if (_disposed) return;
  93.  
  94. _logger.LogWarning("A RabbitMQ connection is shutdown. Trying to re-connect...");
  95.  
  96. TryConnect();
  97. }
  98.  
  99. void OnCallbackException(object sender, CallbackExceptionEventArgs e)
  100. {
  101. if (_disposed) return;
  102.  
  103. _logger.LogWarning("A RabbitMQ connection throw exception. Trying to re-connect...");
  104.  
  105. TryConnect();
  106. }
  107.  
  108. void OnConnectionShutdown(object sender, ShutdownEventArgs reason)
  109. {
  110. if (_disposed) return;
  111.  
  112. _logger.LogWarning("A RabbitMQ connection is on shutdown. Trying to re-connect...");
  113.  
  114. TryConnect();
  115. }
  116.  
  117. }

接下来是最重要,IEventBus的实现类EventBusRabbitMQ,在这个类里我们实现了消息的发布、订阅、消费,首先把代码展示出来,然后一个一个的介绍:

  1. public class EventBusRabbitMQ : IEventBus, IDisposable
  2. {
  3. const string BROKER_NAME = "mi_event_bus";
  4. private readonly IRabbitMQPersistentConnection _persistentConnection;
  5. private readonly ILogger<EventBusRabbitMQ> _logger;
  6. private readonly ILifetimeScope _autofac;
  7. private readonly IApiHelperService _apiHelperService;
  8. private readonly string AUTOFAC_SCOPE_NAME = "mi_event_bus";
  9. private readonly int _retryCount;
  10.  
  11. private IModel _consumerChannel;
  12. private string _queueName;
  13.  
  14. public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection,ILogger<EventBusRabbitMQ> logger,
  15. ILifetimeScope autofac, IApiHelperService apiHelperService, string queueName=null,int retryCount=)
  16. {
  17. _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
  18. _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  19. _queueName = queueName;
  20. _consumerChannel = CreateConsumerChannel();
  21. _autofac = autofac;
  22. _retryCount = retryCount;
  23. _apiHelperService = apiHelperService;
  24. }
  25.  
  26. /// <summary>
  27. /// 发布消息
  28. /// </summary>
  29. public void Publish(string routingKey,object Model)
  30. {
  31. if (!_persistentConnection.IsConnected)
  32. {
  33. _persistentConnection.TryConnect();
  34. }
  35.  
  36. var policy = RetryPolicy.Handle<BrokerUnreachableException>()
  37. .Or<SocketException>()
  38. .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(, retryAttempt)), (ex, time) =>
  39. {
  40. _logger.LogWarning(ex.ToString());
  41. });
  42.  
  43. using (var channel = _persistentConnection.CreateModel())
  44. {
  45. channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
  46. var message = JsonConvert.SerializeObject(Model);
  47. var body = Encoding.UTF8.GetBytes(message);
  48.  
  49. policy.Execute(() =>
  50. {
  51. var properties = channel.CreateBasicProperties();
  52. properties.DeliveryMode = ; //持久化
  53.  
  54. channel.BasicPublish(exchange: BROKER_NAME, routingKey: routingKey, mandatory: true, basicProperties: properties, body: body);
  55. });
  56. }
  57. }
  58.  
  59. /// <summary>
  60. /// 订阅(绑定RoutingKey和队列)
  61. /// </summary>
  62. public void Subscribe(string QueueName, string RoutingKey)
  63. {
  64. if (!_persistentConnection.IsConnected)
  65. {
  66. _persistentConnection.TryConnect();
  67. }
  68.  
  69. using (var channel = _persistentConnection.CreateModel())
  70. {
  71. channel.QueueBind(queue: QueueName, exchange: BROKER_NAME, routingKey: RoutingKey);
  72. }
  73. }
  74.  
  75. /// <summary>
  76. /// 创建消费者并投递消息
  77. /// </summary>
  78. /// <returns></returns>
  79. private IModel CreateConsumerChannel()
  80. {
  81. if (!_persistentConnection.IsConnected)
  82. {
  83. _persistentConnection.TryConnect();
  84. }
  85.  
  86. var channel = _persistentConnection.CreateModel();
  87.  
  88. channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
  89.  
  90. channel.QueueDeclare(queue: _queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);
  91.  
  92. var consumer = new EventingBasicConsumer(channel);
  93. consumer.Received += async (model, ea) =>
  94. {
  95. var message = Encoding.UTF8.GetString(ea.Body);
  96.  
  97. await ProcessEvent(ea.RoutingKey, message);
  98.  
  99. channel.BasicAck(ea.DeliveryTag, multiple: false);
  100. };
  101.  
  102. channel.BasicConsume(queue: _queueName, autoAck: false, consumer: consumer);
  103.  
  104. channel.CallbackException += (sender, ea) =>
  105. {
  106. _consumerChannel.Dispose();
  107. _consumerChannel = CreateConsumerChannel();
  108. };
  109.  
  110. return channel;
  111. }
  112.  
  113. /// <summary>
  114. /// 发送MQ数据到指定服务接口
  115. /// </summary>
  116. private async Task ProcessEvent(string routingKey, string message)
  117. {
  118. using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
  119. {
  120. //获取绑定该routingKey的服务地址集合
  121. var subscriptions = await StackRedis.Current.GetAllList(routingKey);
  122. foreach(var apiUrl in subscriptions)
  123. {
  124. _logger.LogInformation(message);
  125. await _apiHelperService.PostAsync(apiUrl, message);
  126. }
  127. }
  128. }
  129.  
  130. public void Dispose()
  131. {
  132. _consumerChannel?.Dispose();
  133. }
  134. }

首先是发布方法,接受一个字符串类型的RoutingKey和Object类型的MQ数据,然后根据RoutingKey将数据发布到指定的队列,这里RoutingKey发布到队列的方式用的是direct模式,生产环境下我们通常会使用Topic模式,后面真正使用的时候这里也会改掉;同时在MQ发布方面也采用了Polly的重试策略。

接下来是订阅Subscribe方法,这个比较简单,就是包RoutingKey和Queue进行绑定,这里会提供一个专门的注册界面,用于配置RoutingKey、Queue、ExChange和服务接口地址之间的对应关系,用的就是这个方法。

  1. using (var channel = _persistentConnection.CreateModel())
  2. {
  3. channel.QueueBind(queue: QueueName, exchange: BROKER_NAME, routingKey: RoutingKey);
  4. }

然后是消费者的创建和消费方式方法CreateConsumerChannel,这个是最重要一个,在这个方法里真正实现了消息的消费,消息的消费通过委托实现,我们需要关注的是下面这个地方:

  1. var channel = _persistentConnection.CreateModel();
  2.  
  3. channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
  4.  
  5. channel.QueueDeclare(queue: _queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);
  6.  
  7. var consumer = new EventingBasicConsumer(channel);
  8. consumer.Received += async (model, ea) =>
  9. {
  10. var message = Encoding.UTF8.GetString(ea.Body);
  11.  
  12. await ProcessEvent(ea.RoutingKey, message);
  13.  
  14. channel.BasicAck(ea.DeliveryTag, multiple: false);
  15. };
  16.  
  17. channel.BasicConsume(queue: _queueName, autoAck: false, consumer: consumer);

解释下这段代码,首先创建消息通道,并为它绑定交换器Exchange和队列Queue,然后在这条消息通道上创建消费者Consumer,为这个消费者的接受消息的委托注册一个处理方法。

当消息被路由到当前队列Queue上时,就会触发这个消息的处理方法,处理完成后,自动发送ack确认。

ProcessEvent是消息的具体处理方法,大体流程是这样的,它接受一个RoutingKey和消息数据message,根据RoutingKey从Redis中拿到对应的服务地址,我们前面说过会有一个专门的页面用于绑定RoutingKey和服务地址的关系,拿到地址集合之后循环调用,即Api调用。

  1. /// <summary>
  2. /// 发送MQ到指定服务接口
  3. /// </summary>
  4. private async Task ProcessEvent(string routingKey, string message)
  5. {
  6. using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
  7. {
  8. //获取绑定该routingKey的服务地址集合
  9. var subscriptions = await StackRedis.Current.GetAllList(routingKey);
  10. foreach(var apiUrl in subscriptions)
  11. {
  12. _logger.LogInformation(message);
  13. await _apiHelperService.PostAsync(apiUrl, message);
  14. }
  15. }
  16. }

这里用到了Api调用的帮助类,前面已经写过了,只不过把它放到了这个公共的地方,还是贴下代码:

  1. public interface IApiHelperService
  2. {
  3. Task<T> PostAsync<T>(string url, object Model);
  4. Task<T> GetAsync<T>(string url);
  5. Task PostAsync(string url, string requestMessage);
  6. }
  1. public class ApiHelperService : IApiHelperService
  2. {
  3. private readonly IHttpClientFactory _httpClientFactory;
  4. private readonly ILogger<ApiHelperService> _logger;
  5.  
  6. public ApiHelperService(ILogger<ApiHelperService> _logger, IHttpClientFactory _httpClientFactory)
  7. {
  8. this._httpClientFactory = _httpClientFactory;
  9. this._logger = _logger;
  10. }
  11.  
  12. /// <summary>
  13. /// HttpClient实现Post请求
  14. /// </summary>
  15. public async Task<T> PostAsync<T>(string url, object Model)
  16. {
  17. var http = _httpClientFactory.CreateClient("MI.Web");
  18. //添加Token
  19. var token = await GetToken();
  20. http.SetBearerToken(token);
  21. //使用FormUrlEncodedContent做HttpContent
  22. var httpContent = new StringContent(JsonConvert.SerializeObject(Model), Encoding.UTF8, "application/json");
  23. //await异步等待回应
  24. var response = await http.PostAsync(url, httpContent);
  25.  
  26. //确保HTTP成功状态值
  27. response.EnsureSuccessStatusCode();
  28.  
  29. //await异步读取
  30. string Result = await response.Content.ReadAsStringAsync();
  31.  
  32. var Item = JsonConvert.DeserializeObject<T>(Result);
  33.  
  34. return Item;
  35. }
  36.  
  37. /// <summary>
  38. /// HttpClient实现Post请求(用于MQ发布功能 无返回)
  39. /// </summary>
  40. public async Task PostAsync(string url, string requestMessage)
  41. {
  42. var http = _httpClientFactory.CreateClient();
  43. //添加Token
  44. var token = await GetToken();
  45. http.SetBearerToken(token);
  46. //使用FormUrlEncodedContent做HttpContent
  47. var httpContent = new StringContent(requestMessage, Encoding.UTF8, "application/json");
  48. //await异步等待回应
  49. var response = await http.PostAsync(url, httpContent);
  50.  
  51. //确保HTTP成功状态值
  52. response.EnsureSuccessStatusCode();
  53. }
  54.  
  55. /// <summary>
  56. /// HttpClient实现Get请求
  57. /// </summary>
  58. public async Task<T> GetAsync<T>(string url)
  59. {
  60. var http = _httpClientFactory.CreateClient("MI.Web");
  61. //添加Token
  62. var token = await GetToken();
  63. http.SetBearerToken(token);
  64. //await异步等待回应
  65. var response = await http.GetAsync(url);
  66. //确保HTTP成功状态值
  67. response.EnsureSuccessStatusCode();
  68.  
  69. var Result = await response.Content.ReadAsStringAsync();
  70.  
  71. var Items = JsonConvert.DeserializeObject<T>(Result);
  72.  
  73. return Items;
  74. }
  75.  
  76. /// <summary>
  77. /// 转换URL
  78. /// </summary>
  79. /// <param name="str"></param>
  80. /// <returns></returns>
  81. public static string UrlEncode(string str)
  82. {
  83. StringBuilder sb = new StringBuilder();
  84. byte[] byStr = System.Text.Encoding.UTF8.GetBytes(str);
  85. for (int i = ; i < byStr.Length; i++)
  86. {
  87. sb.Append(@"%" + Convert.ToString(byStr[i], ));
  88. }
  89. return (sb.ToString());
  90. }
  91.  
  92. //获取Token
  93. //获取Token
  94. public async Task<string> GetToken()
  95. {
  96. var client = _httpClientFactory.CreateClient("MI.Web");
  97. string token = await Untity.StackRedis.Current.Get("ApiToken");
  98. if (!string.IsNullOrEmpty(token))
  99. {
  100. return token;
  101. }
  102. try
  103. {
  104. //DiscoveryClient类:IdentityModel提供给我们通过基础地址(如:http://localhost:5000)就可以访问令牌服务端;
  105. //当然可以根据上面的restful api里面的url自行构建;上面就是通过基础地址,获取一个TokenClient;(对应restful的url:token_endpoint "http://localhost:5000/connect/token")
  106. //RequestClientCredentialsAsync方法:请求令牌;
  107. //获取令牌后,就可以通过构建http请求访问API接口;这里使用HttpClient构建请求,获取内容;
  108. var cache = new DiscoveryCache("http://localhost:7000");
  109. var disco = await cache.GetAsync();
  110. if (disco.IsError) throw new Exception(disco.Error);
  111. var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
  112. {
  113. Address = disco.TokenEndpoint,
  114. ClientId = "MI.Web",
  115. ClientSecret = "miwebsecret",
  116. Scope = "MI.Service"
  117. });
  118. if (tokenResponse.IsError)
  119. {
  120. throw new Exception(tokenResponse.Error);
  121.  
  122. }
  123. token = tokenResponse.AccessToken;
  124. await Untity.StackRedis.Current.Set("ApiToken", token, (int)TimeSpan.FromSeconds(tokenResponse.ExpiresIn).TotalMinutes);
  125. }
  126. catch (Exception ex)
  127. {
  128. throw new Exception(ex.Message);
  129. }
  130. return token;
  131. }
  132. }

然后Redis帮助类的代码也贴一下,Redis这里大家可以根据自己习惯,如何使用没什么区别:

  1. public class StackRedis : IDisposable
  2. {
  3. #region 配置属性 基于 StackExchange.Redis 封装
  4. //连接串 (注:IP:端口,属性=,属性=)
  5. //public string _ConnectionString = "47.99.92.76:6379,password=shenniubuxing3";
  6. public string _ConnectionString = "47.99.92.76:6379";
  7. //操作的库(注:默认0库)
  8. public int _Db = ;
  9. #endregion
  10.  
  11. #region 管理器对象
  12.  
  13. /// <summary>
  14. /// 获取redis操作类对象
  15. /// </summary>
  16. private static StackRedis _StackRedis;
  17. private static object _locker_StackRedis = new object();
  18. public static StackRedis Current
  19. {
  20. get
  21. {
  22. if (_StackRedis == null)
  23. {
  24. lock (_locker_StackRedis)
  25. {
  26. _StackRedis = _StackRedis ?? new StackRedis();
  27. return _StackRedis;
  28. }
  29. }
  30.  
  31. return _StackRedis;
  32. }
  33. }
  34.  
  35. /// <summary>
  36. /// 获取并发链接管理器对象
  37. /// </summary>
  38. private static ConnectionMultiplexer _redis;
  39. private static object _locker = new object();
  40. public ConnectionMultiplexer Manager
  41. {
  42. get
  43. {
  44. if (_redis == null)
  45. {
  46. lock (_locker)
  47. {
  48.  
  49. _redis = _redis ?? GetManager(_ConnectionString);
  50. return _redis;
  51. }
  52. }
  53.  
  54. return _redis;
  55. }
  56. }
  57.  
  58. /// <summary>
  59. /// 获取链接管理器
  60. /// </summary>
  61. /// <param name="connectionString"></param>
  62. /// <returns></returns>
  63. public ConnectionMultiplexer GetManager(string connectionString)
  64. {
  65. return ConnectionMultiplexer.Connect(connectionString);
  66. }
  67.  
  68. /// <summary>
  69. /// 获取操作数据库对象
  70. /// </summary>
  71. /// <returns></returns>
  72. public IDatabase GetDb()
  73. {
  74. return Manager.GetDatabase(_Db);
  75. }
  76. #endregion
  77.  
  78. #region 操作方法
  79.  
  80. #region string 操作
  81.  
  82. /// <summary>
  83. /// 根据Key移除
  84. /// </summary>
  85. /// <param name="key"></param>
  86. /// <returns></returns>
  87. public async Task<bool> Remove(string key)
  88. {
  89. var db = this.GetDb();
  90.  
  91. return await db.KeyDeleteAsync(key);
  92. }
  93.  
  94. /// <summary>
  95. /// 根据key获取string结果
  96. /// </summary>
  97. /// <param name="key"></param>
  98. /// <returns></returns>
  99. public async Task<string> Get(string key)
  100. {
  101. var db = this.GetDb();
  102. return await db.StringGetAsync(key);
  103. }
  104.  
  105. /// <summary>
  106. /// 根据key获取string中的对象
  107. /// </summary>
  108. /// <typeparam name="T"></typeparam>
  109. /// <param name="key"></param>
  110. /// <returns></returns>
  111. public async Task<T> Get<T>(string key)
  112. {
  113. var t = default(T);
  114. try
  115. {
  116. var _str = await this.Get(key);
  117. if (string.IsNullOrWhiteSpace(_str)) { return t; }
  118.  
  119. t = JsonConvert.DeserializeObject<T>(_str);
  120. }
  121. catch (Exception ex) { }
  122. return t;
  123. }
  124.  
  125. /// <summary>
  126. /// 存储string数据
  127. /// </summary>
  128. /// <param name="key"></param>
  129. /// <param name="value"></param>
  130. /// <param name="expireMinutes"></param>
  131. /// <returns></returns>
  132. public async Task<bool> Set(string key, string value, int expireMinutes = )
  133. {
  134. var db = this.GetDb();
  135. if (expireMinutes > )
  136. {
  137. return db.StringSet(key, value, TimeSpan.FromMinutes(expireMinutes));
  138. }
  139. return await db.StringSetAsync(key, value);
  140. }
  141.  
  142. /// <summary>
  143. /// 存储对象数据到string
  144. /// </summary>
  145. /// <typeparam name="T"></typeparam>
  146. /// <param name="key"></param>
  147. /// <param name="value"></param>
  148. /// <param name="expireMinutes"></param>
  149. /// <returns></returns>
  150. public async Task<bool> Set<T>(string key, T value, int expireMinutes = )
  151. {
  152. try
  153. {
  154. var jsonOption = new JsonSerializerSettings()
  155. {
  156. ReferenceLoopHandling = ReferenceLoopHandling.Ignore
  157. };
  158. var _str = JsonConvert.SerializeObject(value, jsonOption);
  159. if (string.IsNullOrWhiteSpace(_str)) { return false; }
  160.  
  161. return await this.Set(key, _str, expireMinutes);
  162. }
  163. catch (Exception ex) { }
  164. return false;
  165. }
  166. #endregion
  167.  
  168. #region List操作(注:可以当做队列使用)
  169.  
  170. /// <summary>
  171. /// list长度
  172. /// </summary>
  173. /// <typeparam name="T"></typeparam>
  174. /// <param name="key"></param>
  175. /// <returns></returns>
  176. public async Task<long> GetListLen<T>(string key)
  177. {
  178. try
  179. {
  180. var db = this.GetDb();
  181. return await db.ListLengthAsync(key);
  182. }
  183. catch (Exception ex) { }
  184. return ;
  185. }
  186.  
  187. /// <summary>
  188. /// 获取队列出口数据并移除
  189. /// </summary>
  190. /// <typeparam name="T"></typeparam>
  191. /// <param name="key"></param>
  192. /// <returns></returns>
  193. public async Task<T> GetListAndPop<T>(string key)
  194. {
  195. var t = default(T);
  196. try
  197. {
  198. var db = this.GetDb();
  199. var _str = await db.ListRightPopAsync(key);
  200. if (string.IsNullOrWhiteSpace(_str)) { return t; }
  201. t = JsonConvert.DeserializeObject<T>(_str);
  202. }
  203. catch (Exception ex) { }
  204. return t;
  205. }
  206.  
  207. /// <summary>
  208. /// 集合对象添加到list左边
  209. /// </summary>
  210. /// <typeparam name="T"></typeparam>
  211. /// <param name="key"></param>
  212. /// <param name="values"></param>
  213. /// <returns></returns>
  214. public async Task<long> SetLists<T>(string key, List<T> values)
  215. {
  216. var result = 0L;
  217. try
  218. {
  219. var jsonOption = new JsonSerializerSettings()
  220. {
  221. ReferenceLoopHandling = ReferenceLoopHandling.Ignore
  222. };
  223. var db = this.GetDb();
  224. foreach (var item in values)
  225. {
  226. var _str = JsonConvert.SerializeObject(item, jsonOption);
  227. result += await db.ListLeftPushAsync(key, _str);
  228. }
  229. return result;
  230. }
  231. catch (Exception ex) { }
  232. return result;
  233. }
  234.  
  235. /// <summary>
  236. /// 单个对象添加到list左边
  237. /// </summary>
  238. /// <typeparam name="T"></typeparam>
  239. /// <param name="key"></param>
  240. /// <param name="value"></param>
  241. /// <returns></returns>
  242. public async Task<long> SetList<T>(string key, T value)
  243. {
  244. var result = 0L;
  245. try
  246. {
  247. result = await this.SetLists(key, new List<T> { value });
  248. }
  249. catch (Exception ex) { }
  250. return result;
  251. }
  252.  
  253. /// <summary>
  254. /// 获取List所有数据
  255. /// </summary>
  256. public async Task<List<string>> GetAllList(string list)
  257. {
  258. var db = this.GetDb();
  259. var redisList = await db.ListRangeAsync(list);
  260. List<string> listMembers = new List<string>();
  261. foreach (var item in redisList)
  262. {
  263. listMembers.Add(JsonConvert.DeserializeObject<string>(item));
  264. }
  265. return listMembers;
  266. }
  267.  
  268. #endregion
  269.  
  270. #region 额外扩展
  271.  
  272. /// <summary>
  273. /// 手动回收管理器对象
  274. /// </summary>
  275. public void Dispose()
  276. {
  277. this.Dispose(_redis);
  278. }
  279.  
  280. public void Dispose(ConnectionMultiplexer con)
  281. {
  282. if (con != null)
  283. {
  284. con.Close();
  285. con.Dispose();
  286. }
  287. }
  288.  
  289. #endregion
  290.  
  291. #endregion
  292. }

OK,核心代码部分介绍到这里,具体来看怎么使用,推送当前类库到自己的Nuget包,不知道怎么建Nuget服务器的可以看下我之前的那篇文章。

打开MI.Web项目,在Startup中注册RabbitMQ的相关信息:

  1. /// <summary>
  2. /// 消息总线RabbitMQ
  3. /// </summary>
  4. private void RegisterEventBus(IServiceCollection services)
  5. {
  6. #region 加载RabbitMQ账户
  7. services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
  8. {
  9. var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
  10. var factory = new ConnectionFactory()
  11. {
  12. HostName = Configuration["EventBusConnection"]
  13. };
  14.  
  15. if (!string.IsNullOrEmpty(Configuration["EventBusUserName"]))
  16. {
  17. factory.UserName = Configuration["EventBusUserName"];
  18. }
  19.  
  20. if (!string.IsNullOrEmpty(Configuration["EventBusPassword"]))
  21. {
  22. factory.Password = Configuration["EventBusPassword"];
  23. }
  24.  
  25. var retryCount = ;
  26. if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
  27. {
  28. retryCount = int.Parse(Configuration["EventBusRetryCount"]);
  29. }
  30.  
  31. return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
  32. });
  33. #endregion
  34.  
  35. var subscriptionClientName = Configuration["SubscriptionClientName"];
  36.  
  37. services.AddSingleton<IEventBus, EventBusRabbitMQ.EventBusRabbitMQ>(sp =>
  38. {
  39. var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();
  40. var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
  41. var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ.EventBusRabbitMQ>>();
  42. var apiHelper = sp.GetRequiredService<IApiHelperService>();
  43.  
  44. var retryCount = ;
  45. if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
  46. {
  47. retryCount = int.Parse(Configuration["EventBusRetryCount"]);
  48. }
  49.  
  50. return new EventBusRabbitMQ.EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, apiHelper, subscriptionClientName, retryCount);
  51. });
  52. }

这里暂时还没做出专门用于注册RoutingKey的界面,所以暂时用在这里用方法注册下,后面再修改,这里的RoutingKey用于用户注册使用:

  1. //绑定RoutingKey与队列
  2. private void ConfigureEventBus(IApplicationBuilder app)
  3. {
  4. var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
  5. eventBus.Subscribe(Configuration["SubscriptionClientName"], "UserRegister");
  6. }

上面用的都是appsettings.json里的配置,贴下代码,标蓝的部分是需要用到的:

  1. {
  2. "Logging": {
  3. "IncludeScopes": false,
  4. "LogLevel": {
  5. "Default": "Warning"
  6. }
  7. },
  8. "ConnectionStrings": {
  9. "ElasticSearchServerAddress": "",
  10. "Redis": "47.99.92.76:6379"
  11. },
  12. "ServiceAddress": {
  13. "Service.Identity": "http://localhost:7000",
  14. "Service.Account": "http://localhost:7001",
  15. "Service.Ocelot": "http://localhost:7003",
  16. "Service.Picture": "http://localhost:7005"
  17. },
  18. "MehtodName": {
  19. "Account.MiUser.SSOLogin": "/Account/MiUser/SSOLogin", //登录
  20. "Identity.Connect.Token": "/connect/token", //获取token
  21. "Picture.QueryPicture.QueryStartProduct": "/Picture/QueryPicture/QueryStartProduct", //查询明星产品
  22. "Picture.QueryPicture.QuerySlideImg": "/Picture/QueryPicture/QuerySlideImg", //查询轮播图
  23. "Picture.QueryPicture.QueryHadrWare": "/Picture/QueryPicture/QueryHadrWare" //查询智能硬件表数据
  24. },
  25. "EventBusConnection": "******", //RabbitMQ地址
  26. "EventBusUserName": "guest",
  27. "EventBusPassword": "guest",
  28. "EventBusRetryCount": 5,
  29. "SubscriptionClientName": "RabbitMQ_Bus_MI"
  30. }

OK,配置部分算是完成了,接下我们就要去发送MQ了,我们这里使用IEventBus对象调用发布方法,用于发送用户的注册信息,最终最调用新增用户接口:

  1. private readonly IEventBus _eventBus;
  2. public LoginController(IEventBus _eventBus)
  3. {
  4. this._eventBus = _eventBus;
  5. }
  6.  
  7. public JsonResult RegisterUser(string UserName, string UserPwd)
  8. {
  9. try
  10. {
  11. if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(UserPwd))
  12. {
  13. RegisterRequest request = new RegisterRequest
  14. {
  15. UserName = UserName,
  16. Password = UserPwd
  17. };
  18.  
  19. _eventBus.Publish("UserRegister", request);
  20. }
  21. }
  22. catch (Exception ex)
  23. {
  24. _logger.LogError(ex, "注册失败!");
  25. }
  26. return Json("");
  27. }

最终会新增当前传入的用户信息。

当然,这不是消息队列的最终使用方式,后面会继续修改,这里的问题在于发布和消费都耦合再了业务层,对于业务系统来说这是一种负担,举个例子,我们公司当前队列消息多的能达到上百万个,如果把消息的消费和业务系统放在一起可能会影响,所以使用的时候会把消费端单独拿出来做成Windows服务,并添加自动重试和补偿机制,毕竟RabbitMQ也不是没有错误的,比如调用Api出现问题,迟迟无法返回ack确认,这个时候就会报出 wait ack timeout的错误。

OK,今天先到这里,我去煮包泡面吃。。。

.Net Core 商城微服务项目系列(七):使用消息队列(RabbitMQ)实现服务异步通信的更多相关文章

  1. struts2官方 中文教程 系列七:消息资源文件

    介绍 在本教程中,我们将探索使用Struts 2消息资源功能(也称为 resource bundles 资源绑定).消息资源提供了一种简单的方法,可以将文本放在一个视图页面中,通过应用程序,创建表单字 ...

  2. openstack (共享服务) 消息队列rabbitmq服务

    云计算openstack共享组件——消息队列rabbitmq(3)   一.MQ 全称为 Message Queue, 消息队列( MQ ) 是一种应用程序对应用程序的通信方法.应用程序通过读写出入队 ...

  3. C# Azure 消息队列ServiceBus (服务总线队列)

    1. 前言 在阅读本文之前,可以查看微软官方的说明. https://www.azure.cn/documentation/articles/service-bus-create-queues/ 2. ...

  4. ASP.NET Core消息队列RabbitMQ基础入门实战演练

    一.课程介绍 人生苦短,我用.NET Core!消息队列RabbitMQ大家相比都不陌生,本次分享课程阿笨将给大家分享一下在一般项目中99%都会用到的消息队列MQ的一个实战业务运用场景.本次分享课程不 ...

  5. .NET 开源工作流: Slickflow流程引擎高级开发(七)--消息队列(RabbitMQ)的集成使用

    前言:工作流流程过程中,除了正常的人工审批类型的节点外,事件类型的节点处理也尤为重要.比如比较常见的事件类型的节点有:Timer/Message/Signal等.本文重点阐述消息类型的节点处理,以及实 ...

  6. RabbitMQ服务主机名更改导致消息队列无法连接

    RabbitMQ服务主机名更改导致消息队列无法连接 在多节点环境中,RabbitMQ服务使用一个独立节点部署.在此环境下,如果修改了RabbitMQ节点的主机名,则需要更新RabbitMQ用户才能保证 ...

  7. .Net Core 商城微服务项目系列(一):使用IdentityServer4构建基础登录验证

    这里第一次搭建,所以IdentityServer端比较简单,后期再进行完善. 1.新建API项目MI.Service.Identity,NuGet引用IdentityServer4,添加类InMemo ...

  8. .Net Core 商城微服务项目系列(八):购物车

    最近加班有点多,一周五天,四天加班到11点+,心很累.原因是我当前在的这个组比较特殊,相当于业务的架构组,要为其它的开发组提供服务和监控.所以最近更新的也少,不过这个元旦三天假应该会更新三篇. 这篇是 ...

  9. .Net Core 商城微服务项目系列(十三):搭建Log4net+ELK+Kafka日志框架

    之前是使用NLog直接将日志发送到了ELK,本篇将会使用Docker搭建ELK和kafka,同时替换NLog为Log4net. 一.搭建kafka 1.拉取镜像 //下载zookeeper docke ...

随机推荐

  1. windows服务autofac注入quartz任务

    一.nuget下载相关类库引用 install-package Quartz install-package Autofac install-package Autofac.Configuration ...

  2. 【selenium】- webdriver常见元素定位(下)

    本文由小编根据慕课网视频亲自整理,转载请注明出处和作者. 1.table 表格如下: 使用firebug查看: 代码实现: 1)显示出表格所有内容 2)显示表格某个特定的数值 自动化测试框架: 关键字 ...

  3. 2019DX#8

    Solved Pro.ID Title Ratio(Accepted / Submitted)   1001 Acesrc and Cube Hypernet 7.32%(3/41)   1002 A ...

  4. CodeForces 85D Sum of Medians Splay | 线段树

    Sum of Medians 题解: 对于这个题目,先想到是建立5棵Splay,然后每次更新把后面一段区间的树切下来,然后再转圈圈把切下来的树和别的树合并. 但是感觉写起来太麻烦就放弃了. 建立5棵线 ...

  5. Spreading the Wealth uva 11300

    A Communist regime is trying to redistribute wealth in a village. They have have decided to sit ever ...

  6. 对git使用的初步总结

    使用git也才一周多,就已经深深爱上这款软件了. 之前公司一直用的是clearcase,一款老到除了公司内部的人和曾经开发这款软件的人,估计再也不会有人知道了吧! (当然也许还会有其他公司也会使用,因 ...

  7. 工作中遇到的99%SQL优化,这里都能给你解决方案(二)

    -- 示例表 CREATE TABLE `employees` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(24) NOT NULL ...

  8. Python---变量和简单的数据类型

    我会站在一个c/c++的基础上去看python的学习,尽量会在文中比较两者的区别,有什么说的不对的地方,欢迎指出,大家共同学习(o_o).(此后的文章都会基于python3以上版本去写) 1.变量 变 ...

  9. Kubernetes --- 详细介绍和架构详解

    Kubernetes是一个跨主机集群的开源的容器调度平台,它可以自动化应用容器的部署,扩展和操作,提供以容器为中心的基础架构 目录 一. Kubernetes用途 二. Kubernetes特点 三. ...

  10. Kafka运维命令大全

    1.集群管理 前台启动broker bin/kafka-server-start.sh <path>/server.properties Ctrl + C 关闭 后台启动broker bi ...