文件内容 RabbitMultiConsumerHandler.cs 17 KB 17065 字符
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using PasteApeart.Application.Contracts;
using PasteApeart.Handler.eventmodels;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Text.Json;

namespace PasteApeart.Handler.rabbitmodel
{
    /// <summary>
    /// 多Channel的订阅者模式 - 优化版
    /// </summary>
    public class RabbitMultiConsumerHandler : ISingletonDependency, IAsyncDisposable
    {
        private readonly RabbitConfig _rabbitConfig;
        private readonly ILogger<RabbitMultiConsumerHandler> _logger;
        private IConnection _conn;
        private readonly List<IChannel> _channels = new();
        private readonly int _channelCount = 2; // 根据CPU核心数调整
        private readonly SemaphoreSlim _channelSemaphore;
        private IChannel _defaultChannel; // 用于声明的默认channel
        private readonly IServiceProvider _serviceProvider;
        private readonly ChannelHelper _channelHelper;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="logger"></param>
        /// <param name="rabbitConfig"></param>
        public RabbitMultiConsumerHandler(
            IServiceProvider serviceProvider,
            ILogger<RabbitMultiConsumerHandler> logger,
            ChannelHelper channelHelper,
            IOptions<RabbitConfig> rabbitConfig)
        {
            _serviceProvider = serviceProvider;
            _logger = logger;
            _channelHelper = channelHelper;
            _rabbitConfig = rabbitConfig.Value;
            _channelCount = _rabbitConfig.ReadChannelCount;
            _channelSemaphore = new SemaphoreSlim(_channelCount, _channelCount);
        }

        /// <summary>
        /// 多Channel批量初始化
        /// </summary>
        public async Task InitAsync()
        {
            try
            {
                var stopwatch = Stopwatch.StartNew();

                // 创建连接工厂
                var connFactory = new ConnectionFactory
                {
                    HostName = _rabbitConfig.HostName,
                    Port = _rabbitConfig.Port,
                    UserName = _rabbitConfig.UserName,
                    Password = _rabbitConfig.Password,
                    VirtualHost = _rabbitConfig.VirtualHost,
                    ClientProvidedName = _rabbitConfig.ClientName,
                    //DispatchConsumersAsync = true // 启用异步消费者
                };

                // 创建单个连接
                _conn = await connFactory.CreateConnectionAsync();

                // 创建用于声明的默认channel
                _defaultChannel = await _conn.CreateChannelAsync();

                // 声明交换机和队列
                await DeclareExchangesAndQueues();

                // 创建多个消费channel
                await CreateConsumerChannels();

                stopwatch.Stop();
                _logger.LogWarning($"Multi-Channel Consumer Initialized. Duration: {stopwatch.Elapsed.TotalMilliseconds}ms");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "RabbitMQ multi-channel initialization failed!");
            }
        }

        /// <summary>
        /// 声明交换机和队列(使用默认channel)
        /// </summary>
        private async Task DeclareExchangesAndQueues()
        {
            var arguments = new Dictionary<string, object>
        {
            { "x-dead-letter-exchange", _rabbitConfig.ExpiredExchange },
            { "x-dead-letter-routing-key", _rabbitConfig.ExpireRouteKey }
        };

            await BindExpireListAsync(arguments);

            if (arguments.ContainsKey("x-message-ttl"))
            {
                arguments.Remove("x-message-ttl");
            }

            // 声明交换机
            await _defaultChannel.ExchangeDeclareAsync(
                exchange: _rabbitConfig.ExpiredExchange,
                type: _rabbitConfig.ExpireExchangeType,
                durable: _rabbitConfig.Durable);

            // 声明死信队列
            await _defaultChannel.QueueDeclareAsync(
                queue: _rabbitConfig.ExpiredWatchQueue,
                durable: _rabbitConfig.Durable,
                exclusive: false,
                autoDelete: false,
                arguments: null);

            // 绑定队列
            await _defaultChannel.QueueBindAsync(
                queue: _rabbitConfig.ExpiredWatchQueue,
                exchange: _rabbitConfig.ExpiredExchange,
                routingKey: _rabbitConfig.ExpireRouteKey,
                arguments: null);

            #region 以下是绑定到队列去的 处理订阅广播模式
            if (_rabbitConfig.SubscribeEnable)
            {
                if (string.IsNullOrEmpty(_rabbitConfig.SubscribeQueue))
                {
                    _logger.LogError("广播队列配置错误,没有消费队列名称,不需要消费广播信息");
                    return;
                }

                // 声明交换机
                await _defaultChannel.ExchangeDeclareAsync(
                    exchange: _rabbitConfig.SubscribeExchange,
                    type: "fanout",
                    durable: true);

                // 声明当前实例的专属队列
                await _defaultChannel.QueueDeclareAsync(
                    queue: _rabbitConfig.SubscribeQueue, // 使用专属队列名
                    durable: false, // 广播队列通常不需要持久化,实例挂了队列就可以销毁
                    exclusive: true, // 排他性队列,连接断开后自动删除
                    autoDelete: true,
                    arguments: null);

                // 绑定到广播交换机(Fanout 不需要 routingKey)
                await _defaultChannel.QueueBindAsync(
                    queue: _rabbitConfig.SubscribeQueue,
                    exchange: _rabbitConfig.SubscribeExchange,
                    routingKey: "");

                _logger.LogWarning("广播模式交换机配置完成");
            }
            #endregion

        }

        /// <summary>
        /// 创建多个消费channel
        /// </summary>
        private async Task CreateConsumerChannels()
        {

            for (int i = 0; i < _channelCount; i++)
            {
                var channelNumber = i;
                try
                {
                    var channel = await _conn.CreateChannelAsync();

                    // 每个channel单独设置QoS
                    await channel.BasicQosAsync(0, 1, false);

                    var consumer = new AsyncEventingBasicConsumer(channel);
                    consumer.ReceivedAsync += async (sender, e) =>
                    {
                        await Consumer_Received(sender, e, channelNumber);
                    };

                    // 每个channel都消费同一个队列,实现负载均衡
                    await channel.BasicConsumeAsync(
                        queue: _rabbitConfig.ExpiredWatchQueue,
                        autoAck: false,
                        consumer: consumer);


                    _channels.Add(channel);

                    _logger.LogInformation($"Consumer Channel {channelNumber} started.");
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, $"Failed to create consumer channel {channelNumber}");
                }
            }
        }

        /// <summary>
        /// 消费者接收处理(带channel编号)
        /// </summary>
        private async Task Consumer_Received(object sender, BasicDeliverEventArgs e, int channelNumber)
        {
            var channel = ((AsyncEventingBasicConsumer)sender).Channel;
            var msg = string.Empty;
            try
            {
                // 获取信号量,控制并发
                await _channelSemaphore.WaitAsync();
                msg = Encoding.UTF8.GetString(e.Body.ToArray());
                if (!string.IsNullOrEmpty(msg))
                {
                    //_logger.LogInformation($"Channel {channelNumber} received: {msg}");
                    await HandlerEventMessage(msg);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Channel {channelNumber} processing failed. Message: {msg}");
                // 处理失败时 直接忽略 所以下面注释了
            }
            finally
            {
                _channelSemaphore.Release();
                if (!cancellation.IsCancellationRequested)
                {
                    // 确认消息 异常当成完成,所以下面代码生效
                    await channel.BasicAckAsync(e.DeliveryTag, false);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private CancellationTokenSource cancellation { get; set; } = new();

        /// <summary>
        /// 优雅关闭
        /// </summary>
        public async Task ShutdownAsync()
        {
            _logger.LogInformation("Shutting down multi-channel consumer...");
            cancellation.Cancel();

            // 关闭所有消费channel
            foreach (var channel in _channels)
            {
                if (channel.IsOpen)
                {
                    await channel.CloseAsync();
                }
                channel.Dispose();
            }

            // 关闭默认channel
            if (_defaultChannel?.IsOpen == true)
            {
                await _defaultChannel.CloseAsync();
                _defaultChannel.Dispose();
            }

            // 关闭连接
            if (_conn?.IsOpen == true)
            {
                await _conn.CloseAsync();
                _conn.Dispose();
            }

            _channels.Clear();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dicArgs"></param>
        /// <returns></returns>
        private async Task BindExpireListAsync(Dictionary<string, object> dicArgs)
        {
            #region 新版本,基于数据库模式
            //using var scope = _serviceProvider.CreateScope();
            //using var _dbContext = scope.ServiceProvider.GetRequiredService<IPasteApeartDbContext>();

            //var queueList = await _dbContext.QueueInfo.Where(x => x.IsEnable).AsNoTracking().ToListAsync();
            //if (queueList?.Any() == true)
            //{
            //    _rabbitConfig.ExpireTimeList = queueList.Select(x => new RabbitExcepireItem { ExpireExchange = x.Exchange, ExpireQueue = x.Queue, ExpireTime = x.ExpireSecond * 1000, ExpireSecond = x.ExpireSecond, Key = x.Name, StayTime = x.CurrentValue, Zindex = x.Sort }).ToList();
            //}
            //else
            //{
            //    if (_rabbitConfig.ExpireTimeList != null)
            //    {
            //        foreach (var item in _rabbitConfig.ExpireTimeList)
            //        {
            //            item.ExpireSecond = item.ExpireTime / 1000;
            //        }
            //    }
            //}
            if (_rabbitConfig.ExpireTimeList?.Count > 0)
            {
                foreach (var item in _rabbitConfig.ExpireTimeList)
                {
                    var tasks = new List<Task>();
                    var millSecond = item.ExpireSecond * 1000;
                    if (dicArgs.ContainsKey("x-message-ttl")) { dicArgs["x-message-ttl"] = millSecond; } else { dicArgs.Add("x-message-ttl", millSecond); }
                    tasks.Add(_defaultChannel.ExchangeDeclareAsync(exchange: item.ExpireExchange, type: _rabbitConfig.ExpireExchangeType, durable: _rabbitConfig.Durable));
                    tasks.Add(_defaultChannel.QueueDeclareAsync(queue: item.ExpireQueue, durable: _rabbitConfig.Durable, exclusive: false, autoDelete: false, arguments: dicArgs));
                    tasks.Add(_defaultChannel.QueueBindAsync(queue: item.ExpireQueue, exchange: item.ExpireExchange, routingKey: _rabbitConfig.ExpireRouteKey, arguments: null));
                    await Task.WhenAll(tasks);
                }
            }
            #endregion
        }


        private readonly ConcurrentDictionary<string, Type> _eventTypeCache = new();
        private readonly ConcurrentDictionary<Type, (Type HandlerType, MethodInfo HandleMethod)> _handlerCache = new();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        private async Task HandlerEventMessage(string msg)
        {
            if (string.IsNullOrWhiteSpace(msg))
            {
                return;
            }
            object input = null;
            try
            {
                // 1. 只反序列化一次,使用 System.Text.Json 性能更好
                using var jsonDoc = JsonDocument.Parse(msg);
                var root = jsonDoc.RootElement;
                //if (!root.TryGetProperty("EventName", out var eventNameProperty) ||
                //    eventNameProperty.GetString() is not string eventName)
                //{
                //    _logger.LogError($"消息缺少 EventName 字段: {msg}");
                //    return;
                //}
                JsonElement eventNameProperty;
                // 使用不区分大小写的匹配,更健壮
                if (!root.TryGetProperty("eventName", out eventNameProperty))
                {
                    root.TryGetProperty("EventName", out eventNameProperty);
                }
                // 获取字符串值并验证
                string eventName = eventNameProperty.GetString();
                if (string.IsNullOrEmpty(eventName))
                {
                    _logger.LogError($"EventName 字段值为空: {msg}");
                    return;
                }

                // 2. 获取事件类型(带缓存)
                var modelType = _eventTypeCache.GetOrAdd(eventName, name =>
                {
                    return typeof(AbsEventModel).Assembly.GetTypes()
                        .FirstOrDefault(t => t.Name == name);
                });
                //&& typeof(EventBaseModel).IsAssignableFrom(t)
                if (modelType == null)
                {
                    _logger.LogError($"未找到事件类型: {eventName}");
                    return;
                }
                // 3. 反序列化为具体类型
                input = JsonSerializer.Deserialize(msg,modelType,JsonConfig.Options);
                if (input == null)
                {
                    _logger.LogError($"无法将消息反序列化为类型 {eventName}: {msg}");
                    return;
                }
                using var scope = _serviceProvider.CreateScope();
                if (input is IEventModel tryModel)
                {
                    if (tryModel.ExpireSecond > 0)
                    {
                        //var _channelHandler = scope.ServiceProvider.GetRequiredService<ChannelHelper>();
                        await _channelHelper.WriteAutoQueueAsync(tryModel);
                        return;
                    }
                }

                _logger.LogWarning($"begin handler {eventName}");

                //EventTryHandler
                // 4. 获取处理器信息(带缓存)
                var (handlerType, handleMethod) = _handlerCache.GetOrAdd(modelType, type =>
                {
                    var hType = typeof(IEventHandler<>).MakeGenericType(type);
                    var method = hType.GetMethod("HandleEventAsync");
                    return (hType, method);
                });
                if (handleMethod == null)
                {
                    _logger.LogError($"处理器 {handlerType.Name} 没有 HandleEventAsync 方法");
                    return;
                }
                var handler = scope.ServiceProvider.GetRequiredService(handlerType);
                if (handler == null)
                {
                    _logger.LogError($"没有找到 {eventName} 的 IEventHandler 实现");
                    return;
                }
                // 5. 执行处理
                var task = handleMethod.Invoke(handler, new[] { input }) as Task;
                if (task != null)
                {
                    await task;
                }
                else
                {
                    _logger.LogError($"无法调用 {eventName} 的处理方法");
                }
                if (handler is IDisposable disposable)
                {
                    disposable.Dispose();
                }
                if (handler is IAsyncDisposable disposable1)
                {
                    await disposable1.DisposeAsync();
                }

            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"处理消息时发生错误: {msg}");
                //if (input != null)
                //{
                //    if (input is AbsEventModel emodel)
                //    {
                //        if (emodel.LimitTime > 0 && emodel.CurrentTime < emodel.LimitTime)
                //        {
                //            emodel.CurrentTime++;
                //            emodel.ExpireSecond = EventHandlerExtensions.NextSecond(emodel.CurrentTime);
                //            await _channelHelper.WriteAutoQueueAsync(emodel);
                //        }
                //    }
                //}
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public async ValueTask DisposeAsync()
        {
            await ShutdownAsync();
        }
    }
}
​ ​