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

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

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

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

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

 

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

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

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

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

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

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

    public interface IEventBus
    {
        void Publish(string RoutingKey, object Model);

        void Subscribe(string QueueName, string RoutingKey);
    }

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

    public interface IRabbitMQPersistentConnection : IDisposable
    {
        bool IsConnected { get; }

        bool TryConnect();

        IModel CreateModel();
    }

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

public class DefaultRabbitMQPersistentConnection:IRabbitMQPersistentConnection
    {
        private readonly IConnectionFactory _connectionFactory;
        private readonly ILogger<DefaultRabbitMQPersistentConnection> _logger;
        private readonly int _retryCount;
        IConnection _connection;
        bool _disposed;

        object sync_root = new object();

        public DefaultRabbitMQPersistentConnection(IConnectionFactory connectionFactory, ILogger<DefaultRabbitMQPersistentConnection> logger, int retryCount = 5)
        {
            _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _retryCount = retryCount;
        }

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

        public IModel CreateModel()
        {
            if (!IsConnected)
            {
                throw new InvalidOperationException("No RabbitMQ connections are available to perform this action");
            }

            return _connection.CreateModel();
        }

        public void Dispose()
        {
            if (_disposed) return;

            _disposed = true;

            try
            {
                _connection.Dispose();
            }
            catch (IOException ex)
            {
                _logger.LogCritical(ex.ToString());
            }
        }


        public bool TryConnect()
        {
            _logger.LogInformation("RabbitMQ Client is trying to connect");

            lock (sync_root)
            {
                var policy = RetryPolicy.Handle<SocketException>()
                    .Or<BrokerUnreachableException>()
                    .WaitAndRetry(_retryCount, 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 persistent connection acquired a connection {_connection.Endpoint.HostName} and is subscribed to failure events");

                    return true;
                }
                else
                {
                    _logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened");

                    return false;
                }
            }
        }

        private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e)
        {
            if (_disposed) return;

            _logger.LogWarning("A RabbitMQ connection is shutdown. Trying to re-connect...");

            TryConnect();
        }

        void OnCallbackException(object sender, CallbackExceptionEventArgs e)
        {
            if (_disposed) return;

            _logger.LogWarning("A RabbitMQ connection throw exception. Trying to re-connect...");

            TryConnect();
        }

        void OnConnectionShutdown(object sender, ShutdownEventArgs reason)
        {
            if (_disposed) return;

            _logger.LogWarning("A RabbitMQ connection is on shutdown. Trying to re-connect...");

            TryConnect();
        }

    }

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

public class EventBusRabbitMQ : IEventBus, IDisposable
    {
        const string BROKER_NAME = "mi_event_bus";
        private readonly IRabbitMQPersistentConnection _persistentConnection;
        private readonly ILogger<EventBusRabbitMQ> _logger;
        private readonly ILifetimeScope _autofac;
        private readonly IApiHelperService _apiHelperService;
        private readonly string AUTOFAC_SCOPE_NAME = "mi_event_bus";
        private readonly int _retryCount;

        private IModel _consumerChannel;
        private string _queueName;


        public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection,ILogger<EventBusRabbitMQ> logger,
            ILifetimeScope autofac, IApiHelperService apiHelperService, string queueName=null,int retryCount=5)
        {
            _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _queueName = queueName;
            _consumerChannel = CreateConsumerChannel();
            _autofac = autofac;
            _retryCount = retryCount;
            _apiHelperService = apiHelperService;
        }


        /// <summary>
        /// 发布消息
        /// </summary>
        public void Publish(string routingKey,object Model)
        {
            if (!_persistentConnection.IsConnected)
            {
                _persistentConnection.TryConnect();
            }

            var policy = RetryPolicy.Handle<BrokerUnreachableException>()
                .Or<SocketException>()
                .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
                {
                    _logger.LogWarning(ex.ToString());
                });

            using (var channel = _persistentConnection.CreateModel())
            {
                channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
                var message = JsonConvert.SerializeObject(Model);
                var body = Encoding.UTF8.GetBytes(message);

                policy.Execute(() =>
                {
                    var properties = channel.CreateBasicProperties();
                    properties.DeliveryMode = 2; //持久化

                    channel.BasicPublish(exchange: BROKER_NAME, routingKey: routingKey, mandatory: true, basicProperties: properties, body: body);
                });
            }
        }

        /// <summary>
        /// 订阅(绑定RoutingKey和队列)
        /// </summary>
        public void Subscribe(string QueueName, string RoutingKey)
        {
            if (!_persistentConnection.IsConnected)
            {
                _persistentConnection.TryConnect();
            }

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

        /// <summary>
        /// 创建消费者并投递消息
        /// </summary>
        /// <returns></returns>
        private IModel CreateConsumerChannel()
        {
            if (!_persistentConnection.IsConnected)
            {
                _persistentConnection.TryConnect();
            }

            var channel = _persistentConnection.CreateModel();

            channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");

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

            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += async (model, ea) =>
            {
                var message = Encoding.UTF8.GetString(ea.Body);

                await ProcessEvent(ea.RoutingKey, message);

                channel.BasicAck(ea.DeliveryTag, multiple: false);
            };

            channel.BasicConsume(queue: _queueName, autoAck: false, consumer: consumer);

            channel.CallbackException += (sender, ea) =>
            {
                _consumerChannel.Dispose();
                _consumerChannel = CreateConsumerChannel();
            };

            return channel;
        }

        /// <summary>
        /// 发送MQ数据到指定服务接口
        /// </summary>
        private async Task ProcessEvent(string routingKey, string message)
        {
            using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
            {
                //获取绑定该routingKey的服务地址集合
                var subscriptions = await StackRedis.Current.GetAllList(routingKey);
                foreach(var apiUrl in subscriptions)
                {
                    _logger.LogInformation(message);
                    await _apiHelperService.PostAsync(apiUrl, message);
                }
            }
        }

        public void Dispose()
        {
            _consumerChannel?.Dispose();
        }
    }

 

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

 

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

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

 

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

            var channel = _persistentConnection.CreateModel();

            channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");

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

            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += async (model, ea) =>
            {
                var message = Encoding.UTF8.GetString(ea.Body);

                await ProcessEvent(ea.RoutingKey, message);

                channel.BasicAck(ea.DeliveryTag, multiple: false);
            };

            channel.BasicConsume(queue: _queueName, autoAck: false, consumer: consumer);

 

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

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

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

        /// <summary>
        /// 发送MQ到指定服务接口
        /// </summary>
        private async Task ProcessEvent(string routingKey, string message)
        {
            using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
            {
                //获取绑定该routingKey的服务地址集合
                var subscriptions = await StackRedis.Current.GetAllList(routingKey);
                foreach(var apiUrl in subscriptions)
                {
                    _logger.LogInformation(message);
                    await _apiHelperService.PostAsync(apiUrl, message);
                }
            }
        }

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

    public interface IApiHelperService
    {
        Task<T> PostAsync<T>(string url, object Model);
        Task<T> GetAsync<T>(string url);
        Task PostAsync(string url, string requestMessage);
    }
public class ApiHelperService : IApiHelperService
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly ILogger<ApiHelperService> _logger;

        public ApiHelperService(ILogger<ApiHelperService> _logger, IHttpClientFactory _httpClientFactory)
        {
            this._httpClientFactory = _httpClientFactory;
            this._logger = _logger;
        }


        /// <summary>
        /// HttpClient实现Post请求
        /// </summary>
        public async Task<T> PostAsync<T>(string url, object Model)
        {
            var http = _httpClientFactory.CreateClient("MI.Web");
            //添加Token
            var token = await GetToken();
            http.SetBearerToken(token);
            //使用FormUrlEncodedContent做HttpContent
            var httpContent = new StringContent(JsonConvert.SerializeObject(Model), Encoding.UTF8, "application/json");
            //await异步等待回应
            var response = await http.PostAsync(url, httpContent);

            //确保HTTP成功状态值
            response.EnsureSuccessStatusCode();

            //await异步读取
            string Result = await response.Content.ReadAsStringAsync();

            var Item = JsonConvert.DeserializeObject<T>(Result);

            return Item;
        }

        /// <summary>
        /// HttpClient实现Post请求(用于MQ发布功能 无返回)
        /// </summary>
        public async Task PostAsync(string url, string requestMessage)
        {
            var http = _httpClientFactory.CreateClient();
            //添加Token
            var token = await GetToken();
            http.SetBearerToken(token);
            //使用FormUrlEncodedContent做HttpContent
            var httpContent = new StringContent(requestMessage, Encoding.UTF8, "application/json");
            //await异步等待回应
            var response = await http.PostAsync(url, httpContent);

            //确保HTTP成功状态值
            response.EnsureSuccessStatusCode();
        }


        /// <summary>
        /// HttpClient实现Get请求
        /// </summary>
        public async Task<T> GetAsync<T>(string url)
        {
            var http = _httpClientFactory.CreateClient("MI.Web");
            //添加Token
            var token = await GetToken();
            http.SetBearerToken(token);
            //await异步等待回应
            var response = await http.GetAsync(url);
            //确保HTTP成功状态值
            response.EnsureSuccessStatusCode();

            var Result = await response.Content.ReadAsStringAsync();

            var Items = JsonConvert.DeserializeObject<T>(Result);

            return Items;
        }


        /// <summary>
        /// 转换URL
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string UrlEncode(string str)
        {
            StringBuilder sb = new StringBuilder();
            byte[] byStr = System.Text.Encoding.UTF8.GetBytes(str);
            for (int i = 0; i < byStr.Length; i++)
            {
                sb.Append(@"%" + Convert.ToString(byStr[i], 16));
            }
            return (sb.ToString());
        }

        //获取Token
        //获取Token
        public async Task<string> GetToken()
        {
            var client = _httpClientFactory.CreateClient("MI.Web");
            string token = await Untity.StackRedis.Current.Get("ApiToken");
            if (!string.IsNullOrEmpty(token))
            {
                return token;
            }
            try
            {
                //DiscoveryClient类:IdentityModel提供给我们通过基础地址(如:http://localhost:5000)就可以访问令牌服务端;
                //当然可以根据上面的restful api里面的url自行构建;上面就是通过基础地址,获取一个TokenClient;(对应restful的url:token_endpoint   "http://localhost:5000/connect/token")
                //RequestClientCredentialsAsync方法:请求令牌;
                //获取令牌后,就可以通过构建http请求访问API接口;这里使用HttpClient构建请求,获取内容;
                var cache = new DiscoveryCache("http://localhost:7000");
                var disco = await cache.GetAsync();
                if (disco.IsError) throw new Exception(disco.Error);
                var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
                {
                    Address = disco.TokenEndpoint,
                    ClientId = "MI.Web",
                    ClientSecret = "miwebsecret",
                    Scope = "MI.Service"
                });
                if (tokenResponse.IsError)
                {
                    throw new Exception(tokenResponse.Error);

                }
                token = tokenResponse.AccessToken;
                await Untity.StackRedis.Current.Set("ApiToken", token, (int)TimeSpan.FromSeconds(tokenResponse.ExpiresIn).TotalMinutes);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            return token;
        }
    }

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

    public class StackRedis : IDisposable
    {
        #region 配置属性   基于 StackExchange.Redis 封装
        //连接串 (注:IP:端口,属性=,属性=)
        //public string _ConnectionString = "47.99.92.76:6379,password=shenniubuxing3";
        public string _ConnectionString = "47.99.92.76:6379";
        //操作的库(注:默认0库)
        public int _Db = 0;
        #endregion

        #region 管理器对象

        /// <summary>
        /// 获取redis操作类对象
        /// </summary>
        private static StackRedis _StackRedis;
        private static object _locker_StackRedis = new object();
        public static StackRedis Current
        {
            get
            {
                if (_StackRedis == null)
                {
                    lock (_locker_StackRedis)
                    {
                        _StackRedis = _StackRedis ?? new StackRedis();
                        return _StackRedis;
                    }
                }

                return _StackRedis;
            }
        }

        /// <summary>
        /// 获取并发链接管理器对象
        /// </summary>
        private static ConnectionMultiplexer _redis;
        private static object _locker = new object();
        public ConnectionMultiplexer Manager
        {
            get
            {
                if (_redis == null)
                {
                    lock (_locker)
                    {

                        _redis = _redis ?? GetManager(_ConnectionString);
                        return _redis;
                    }
                }

                return _redis;
            }
        }

        /// <summary>
        /// 获取链接管理器
        /// </summary>
        /// <param name="connectionString"></param>
        /// <returns></returns>
        public ConnectionMultiplexer GetManager(string connectionString)
        {
            return ConnectionMultiplexer.Connect(connectionString);
        }

        /// <summary>
        /// 获取操作数据库对象
        /// </summary>
        /// <returns></returns>
        public IDatabase GetDb()
        {
            return Manager.GetDatabase(_Db);
        }
        #endregion

        #region 操作方法

        #region string 操作

        /// <summary>
        /// 根据Key移除
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public async Task<bool> Remove(string key)
        {
            var db = this.GetDb();

            return await db.KeyDeleteAsync(key);
        }

        /// <summary>
        /// 根据key获取string结果
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public async Task<string> Get(string key)
        {
            var db = this.GetDb();
            return await db.StringGetAsync(key);
        }

        /// <summary>
        /// 根据key获取string中的对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public async Task<T> Get<T>(string key)
        {
            var t = default(T);
            try
            {
                var _str = await this.Get(key);
                if (string.IsNullOrWhiteSpace(_str)) { return t; }

                t = JsonConvert.DeserializeObject<T>(_str);
            }
            catch (Exception ex) { }
            return t;
        }

        /// <summary>
        /// 存储string数据
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expireMinutes"></param>
        /// <returns></returns>
        public async Task<bool> Set(string key, string value, int expireMinutes = 0)
        {
            var db = this.GetDb();
            if (expireMinutes > 0)
            {
                return db.StringSet(key, value, TimeSpan.FromMinutes(expireMinutes));
            }
            return await db.StringSetAsync(key, value);
        }

        /// <summary>
        /// 存储对象数据到string
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expireMinutes"></param>
        /// <returns></returns>
        public async Task<bool> Set<T>(string key, T value, int expireMinutes = 0)
        {
            try
            {
                var jsonOption = new JsonSerializerSettings()
                {
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                };
                var _str = JsonConvert.SerializeObject(value, jsonOption);
                if (string.IsNullOrWhiteSpace(_str)) { return false; }

                return await this.Set(key, _str, expireMinutes);
            }
            catch (Exception ex) { }
            return false;
        }
        #endregion

        #region List操作(注:可以当做队列使用)

        /// <summary>
        /// list长度
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public async Task<long> GetListLen<T>(string key)
        {
            try
            {
                var db = this.GetDb();
                return await db.ListLengthAsync(key);
            }
            catch (Exception ex) { }
            return 0;
        }

        /// <summary>
        /// 获取队列出口数据并移除
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public async Task<T> GetListAndPop<T>(string key)
        {
            var t = default(T);
            try
            {
                var db = this.GetDb();
                var _str = await db.ListRightPopAsync(key);
                if (string.IsNullOrWhiteSpace(_str)) { return t; }
                t = JsonConvert.DeserializeObject<T>(_str);
            }
            catch (Exception ex) { }
            return t;
        }

        /// <summary>
        /// 集合对象添加到list左边
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public async Task<long> SetLists<T>(string key, List<T> values)
        {
            var result = 0L;
            try
            {
                var jsonOption = new JsonSerializerSettings()
                {
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                };
                var db = this.GetDb();
                foreach (var item in values)
                {
                    var _str = JsonConvert.SerializeObject(item, jsonOption);
                    result += await db.ListLeftPushAsync(key, _str);
                }
                return result;
            }
            catch (Exception ex) { }
            return result;
        }

        /// <summary>
        /// 单个对象添加到list左边
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public async Task<long> SetList<T>(string key, T value)
        {
            var result = 0L;
            try
            {
                result = await this.SetLists(key, new List<T> { value });
            }
            catch (Exception ex) { }
            return result;
        }

        /// <summary>
        /// 获取List所有数据
        /// </summary>
        public async Task<List<string>> GetAllList(string list)
        {
            var db = this.GetDb();
            var redisList = await db.ListRangeAsync(list);
            List<string> listMembers = new List<string>();
            foreach (var item in redisList)
            {
                listMembers.Add(JsonConvert.DeserializeObject<string>(item));
            }
            return listMembers;
        }


        #endregion

        #region 额外扩展

        /// <summary>
        /// 手动回收管理器对象
        /// </summary>
        public void Dispose()
        {
            this.Dispose(_redis);
        }

        public void Dispose(ConnectionMultiplexer con)
        {
            if (con != null)
            {
                con.Close();
                con.Dispose();
            }
        }

        #endregion

        #endregion
    }

 

 

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

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

        /// <summary>
        /// 消息总线RabbitMQ
        /// </summary>
        private void RegisterEventBus(IServiceCollection services)
        {
            #region 加载RabbitMQ账户
            services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
            {
                var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
                var factory = new ConnectionFactory()
                {
                    HostName = Configuration["EventBusConnection"]
                };

                if (!string.IsNullOrEmpty(Configuration["EventBusUserName"]))
                {
                    factory.UserName = Configuration["EventBusUserName"];
                }

                if (!string.IsNullOrEmpty(Configuration["EventBusPassword"]))
                {
                    factory.Password = Configuration["EventBusPassword"];
                }

                var retryCount = 5;
                if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
                {
                    retryCount = int.Parse(Configuration["EventBusRetryCount"]);
                }

                return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
            });
            #endregion

            var subscriptionClientName = Configuration["SubscriptionClientName"];

            services.AddSingleton<IEventBus, EventBusRabbitMQ.EventBusRabbitMQ>(sp =>
            {
                var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();
                var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
                var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ.EventBusRabbitMQ>>();
                var apiHelper = sp.GetRequiredService<IApiHelperService>();

                var retryCount = 5;
                if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
                {
                    retryCount = int.Parse(Configuration["EventBusRetryCount"]);
                }

                return new EventBusRabbitMQ.EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, apiHelper, subscriptionClientName, retryCount);
            });
        }

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

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

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

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "ConnectionStrings": {
    "ElasticSearchServerAddress": "",
    "Redis": "47.99.92.76:6379"
  },
  "ServiceAddress": {
    "Service.Identity": "http://localhost:7000",
    "Service.Account": "http://localhost:7001",
    "Service.Ocelot": "http://localhost:7003",
    "Service.Picture": "http://localhost:7005"
  },
  "MehtodName": {
    "Account.MiUser.SSOLogin": "/Account/MiUser/SSOLogin", //登录
    "Identity.Connect.Token": "/connect/token", //获取token
    "Picture.QueryPicture.QueryStartProduct": "/Picture/QueryPicture/QueryStartProduct", //查询明星产品
    "Picture.QueryPicture.QuerySlideImg": "/Picture/QueryPicture/QuerySlideImg", //查询轮播图
    "Picture.QueryPicture.QueryHadrWare": "/Picture/QueryPicture/QueryHadrWare" //查询智能硬件表数据
  },
  "EventBusConnection": "******", //RabbitMQ地址
  "EventBusUserName": "guest",
  "EventBusPassword": "guest",
  "EventBusRetryCount": 5,
  "SubscriptionClientName": "RabbitMQ_Bus_MI"
}

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

        private readonly IEventBus _eventBus;
        public LoginController(IEventBus _eventBus)
        {
            this._eventBus = _eventBus;
        }


        public JsonResult RegisterUser(string UserName, string UserPwd)
        {
            try
            {
                if (!string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(UserPwd))
                {
                    RegisterRequest request = new RegisterRequest
                    {
                        UserName = UserName,
                        Password = UserPwd
                    };

                    _eventBus.Publish("UserRegister", request);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "注册失败!");
            }
            return Json("");
        }

 

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

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

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

 

posted @ 2018-12-17 22:39  名字都被注册了  阅读(3313)  评论(7编辑  收藏  举报