基于原生 RabbitMQ.Client 实现,包含高可用、高可靠、可监控、可扩展四大核心特性,适配分布式系统生产部署要求;
一、核心依赖
bash
# 必须依赖
Install-Package RabbitMQ.Client -Version 6.8.1
Install-Package Newtonsoft.Json -Version 13.0.3
# 可选:日志框架(生产环境建议使用)
Install-Package Serilog -Version 3.1.1
Install-Package Serilog.Sinks.Console -Version 4.1.0
Install-Package Serilog.Sinks.File -Version 5.0.0
二、生产级封装代码
1. 配置类(支持多环境配置)
cs
using System;
using System.Collections.Generic;
using RabbitMQ.Client;
/// <summary>
/// RabbitMQ生产环境配置
/// 建议从配置文件读取(如appsettings.json)
/// </summary>
public class RabbitMqProductionConfig
{
/// <summary>
/// 集群节点(支持多个,逗号分隔:host1:port1,host2:port2)
/// </summary>
public string Nodes { get; set; } = "localhost:5672";
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; set; } = "guest";
/// <summary>
/// 密码
/// </summary>
public string Password { get; set; } = "guest";
/// <summary>
/// 虚拟主机
/// </summary>
public string VirtualHost { get; set; } = "/";
/// <summary>
/// 交换机名称
/// </summary>
public string ExchangeName { get; set; } = "prod.exchange";
/// <summary>
/// 交换机类型(direct/fanout/topic)
/// </summary>
public string ExchangeType { get; set; } = ExchangeType.Direct;
/// <summary>
/// 队列名称
/// </summary>
public string QueueName { get; set; } = "prod.queue";
/// <summary>
/// 路由键
/// </summary>
public string RoutingKey { get; set; } = "prod.key";
/// <summary>
/// 是否持久化(生产环境必须true)
/// </summary>
public bool Durable { get; set; } = true;
/// <summary>
/// 预取数(控制单消费者并发数,生产环境建议5-20)
/// </summary>
public ushort PrefetchCount { get; set; } = 10;
/// <summary>
/// 消息过期时间(毫秒)
/// </summary>
public int MessageTtl { get; set; } = 300000; // 5分钟
/// <summary>
/// 队列最大长度(防止消息堆积撑爆磁盘)
/// </summary>
public int QueueMaxLength { get; set; } = 100000;
/// <summary>
/// 连接超时时间(毫秒)
/// </summary>
public int ConnectionTimeout { get; set; } = 30000;
/// <summary>
/// 心跳间隔(秒)
/// </summary>
public int HeartbeatSeconds { get; set; } = 60;
/// <summary>
/// 最大重试次数(消费失败后)
/// </summary>
public int MaxRetryCount { get; set; } = 3;
/// <summary>
/// 死信交换机后缀(自动拼接:{ExchangeName}.dlx)
/// </summary>
public string DeadLetterExchangeSuffix { get; set; } = ".dlx";
/// <summary>
/// 死信路由键后缀(自动拼接:{RoutingKey}.dlk)
/// </summary>
public string DeadLetterRoutingKeySuffix { get; set; } = ".dlk";
}
/// <summary>
/// 生产环境消息体(带校验、链路追踪)
/// </summary>
/// <typeparam name="T"></typeparam>
public class RabbitMqProductionMessage<T>
{
/// <summary>
/// 消息唯一ID(幂等性校验)
/// </summary>
public string MessageId { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>
/// 业务数据
/// </summary>
public T Data { get; set; } = default!;
/// <summary>
/// 链路追踪ID
/// </summary>
public string TraceId { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>
/// 生产者服务名称
/// </summary>
public string ProducerService { get; set; } = string.Empty;
/// <summary>
/// 发送时间(UTC)
/// </summary>
public DateTime SendTimeUtc { get; set; } = DateTime.UtcNow;
/// <summary>
/// 校验消息有效性
/// </summary>
/// <returns></returns>
public bool Validate()
{
return !string.IsNullOrEmpty(MessageId) &&
!string.IsNullOrEmpty(TraceId) &&
Data != null;
}
}
2. 核心工具类(生产级)
cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;
using Serilog;
/// <summary>
/// RabbitMQ生产级操作类
/// 特性:高可用、自动重连、死信队列、幂等消费、链路追踪
/// </summary>
public class RabbitMqProductionClient : IDisposable
{
#region 私有字段
private readonly RabbitMqProductionConfig _config;
private readonly ILogger _logger;
private readonly ConcurrentDictionary<string, IConnection> _connectionPool;
private readonly object _lockObj = new object();
private readonly JsonSerializerSettings _jsonSettings;
private IModel? _publishChannel;
private IModel? _consumeChannel;
private AsyncEventingBasicConsumer? _consumer;
private CancellationTokenSource? _consumeCts;
private bool _isConsuming;
private bool _isDisposed;
// 死信队列配置
private string _deadLetterExchange => $"{_config.ExchangeName}{_config.DeadLetterExchangeSuffix}";
private string _deadLetterRoutingKey => $"{_config.RoutingKey}{_config.DeadLetterRoutingKeySuffix}";
#endregion
#region 事件定义(生产环境监控)
/// <summary>
/// 消费成功事件
/// </summary>
public event EventHandler<string>? ConsumeSucceeded;
/// <summary>
/// 消费失败事件(超过重试次数)
/// </summary>
public event EventHandler<string>? ConsumeFailed;
/// <summary>
/// 连接异常事件
/// </summary>
public event EventHandler<Exception>? ConnectionError;
#endregion
#region 构造函数
/// <summary>
/// 初始化生产级RabbitMQ客户端
/// </summary>
/// <param name="config">配置</param>
/// <param name="logger">日志(生产环境必须传入)</param>
public RabbitMqProductionClient(RabbitMqProductionConfig config, ILogger logger)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_connectionPool = new ConcurrentDictionary<string, IConnection>();
// 序列化配置(生产环境建议禁用循环引用)
_jsonSettings = new JsonSerializerSettings
{
DateFormatString = "yyyy-MM-dd HH:mm:ss.fff",
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Formatting = Formatting.None // 禁用格式化,减小消息体积
};
// 初始化发布信道
InitPublishChannel();
// 声明死信交换机和队列(生产环境必须)
DeclareDeadLetterTopology();
}
#endregion
#region 连接管理(集群支持+自动重连)
/// <summary>
/// 获取连接(支持集群节点切换)
/// </summary>
/// <returns></returns>
private IConnection GetConnection()
{
var nodes = _config.Nodes.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(node => node.Trim())
.ToList();
foreach (var node in nodes)
{
var (host, port) = node.Split(':') is { Length: 2 } parts
? (parts[0], int.Parse(parts[1]))
: (node, 5672);
var connectionKey = $"{host}:{port}:{_config.VirtualHost}";
if (_connectionPool.TryGetValue(connectionKey, out var connection) && connection.IsOpen)
{
return connection;
}
lock (_lockObj)
{
if (_connectionPool.TryGetValue(connectionKey, out connection) && connection.IsOpen)
{
return connection;
}
try
{
var factory = new ConnectionFactory
{
HostName = host,
Port = port,
UserName = _config.UserName,
Password = _config.Password,
VirtualHost = _config.VirtualHost,
RequestedHeartbeat = TimeSpan.FromSeconds(_config.HeartbeatSeconds),
ConnectionTimeout = _config.ConnectionTimeout,
AutomaticRecoveryEnabled = true, // 自动恢复连接
NetworkRecoveryInterval = TimeSpan.FromSeconds(10), // 恢复间隔
TopologyRecoveryEnabled = true, // 自动恢复拓扑
ClientProvidedName = $"{_config.ProducerService}-{Environment.MachineName}" // 客户端名称(便于RabbitMQ管控台识别)
};
connection = factory.CreateConnection();
_logger.Information($"RabbitMQ连接成功:{connectionKey}");
// 连接事件监听(生产环境监控必备)
connection.ConnectionShutdown += (s, e) =>
{
_logger.Warning($"RabbitMQ连接关闭:{e.ReplyText}");
_connectionPool.TryRemove(connectionKey, out _);
if (_isConsuming) _ = RestartConsumeAsync();
};
connection.CallbackException += (s, e) =>
{
_logger.Error(e.Exception, "RabbitMQ回调异常");
ConnectionError?.Invoke(this, e.Exception);
if (_isConsuming) _ = RestartConsumeAsync();
};
connection.ConnectionBlocked += (s, e) =>
{
_logger.Warning($"RabbitMQ连接被阻塞:{e.Reason}");
ConnectionError?.Invoke(this, new Exception($"连接被阻塞:{e.Reason}"));
};
_connectionPool[connectionKey] = connection;
return connection;
}
catch (Exception ex)
{
_logger.Error(ex, $"RabbitMQ连接失败:{node}");
continue;
}
}
}
throw new Exception("所有RabbitMQ节点连接失败");
}
/// <summary>
/// 初始化发布信道
/// </summary>
private void InitPublishChannel()
{
if (_publishChannel != null && _publishChannel.IsOpen) return;
var connection = GetConnection();
_publishChannel = connection.CreateModel();
// 声明业务交换机(幂等操作,重复声明无影响)
_publishChannel.ExchangeDeclare(
exchange: _config.ExchangeName,
type: _config.ExchangeType,
durable: _config.Durable,
autoDelete: false,
arguments: new Dictionary<string, object>
{
{"x-message-ttl", _config.MessageTtl},
{"x-max-length", _config.QueueMaxLength}
});
// 声明业务队列(绑定死信)
_publishChannel.QueueDeclare(
queue: _config.QueueName,
durable: _config.Durable,
exclusive: false,
autoDelete: false,
arguments: new Dictionary<string, object>
{
{"x-message-ttl", _config.MessageTtl},
{"x-max-length", _config.QueueMaxLength},
{"x-dead-letter-exchange", _deadLetterExchange},
{"x-dead-letter-routing-key", _deadLetterRoutingKey},
{"x-queue-mode", "lazy"} // 惰性队列:消息直接写入磁盘,减少内存占用(生产环境建议开启)
});
// 绑定队列到交换机
_publishChannel.QueueBind(
queue: _config.QueueName,
exchange: _config.ExchangeName,
routingKey: _config.RoutingKey);
}
/// <summary>
/// 声明死信交换机和队列(生产环境必须)
/// 用于存储消费失败超过重试次数的消息
/// </summary>
private void DeclareDeadLetterTopology()
{
var connection = GetConnection();
using var channel = connection.CreateModel();
// 声明死信交换机
channel.ExchangeDeclare(
exchange: _deadLetterExchange,
type: _config.ExchangeType,
durable: _config.Durable,
autoDelete: false);
// 声明死信队列
channel.QueueDeclare(
queue: $"{_config.QueueName}.dlq",
durable: _config.Durable,
exclusive: false,
autoDelete: false,
arguments: new Dictionary<string, object>
{
{"x-message-ttl", 86400000} // 死信消息保存24小时
});
// 绑定死信队列
channel.QueueBind(
queue: $"{_config.QueueName}.dlq",
exchange: _deadLetterExchange,
routingKey: _deadLetterRoutingKey);
_logger.Information($"死信队列初始化完成:{_config.QueueName}.dlq");
}
/// <summary>
/// 重启消费(连接断开后自动恢复)
/// </summary>
private async Task RestartConsumeAsync()
{
if (_isDisposed) return;
_logger.Warning("开始重启RabbitMQ消费...");
await Task.Delay(5000); // 延迟5秒,避免频繁重试
try
{
StopConsume();
_logger.Information("RabbitMQ消费重启成功");
}
catch (Exception ex)
{
_logger.Error(ex, "RabbitMQ消费重启失败,将在10秒后重试");
await Task.Delay(10000);
await RestartConsumeAsync();
}
}
#endregion
#region 消息发布(生产级可靠发布)
/// <summary>
/// 发布消息(同步,生产环境建议用异步)
/// </summary>
/// <typeparam name="T">业务数据类型</typeparam>
/// <param name="message">消息体</param>
/// <param name="routingKey">自定义路由键</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public void Publish<T>(RabbitMqProductionMessage<T> message, string? routingKey = null)
{
PublishAsync(message, routingKey).Wait();
}
/// <summary>
/// 发布消息(异步,生产环境推荐)
/// 支持发布确认(生产环境建议开启)
/// </summary>
/// <typeparam name="T">业务数据类型</typeparam>
/// <param name="message">消息体</param>
/// <param name="routingKey">自定义路由键</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public async Task PublishAsync<T>(RabbitMqProductionMessage<T> message, string? routingKey = null)
{
if (_isDisposed) throw new InvalidOperationException("客户端已释放");
if (message == null) throw new ArgumentNullException(nameof(message));
if (!message.Validate()) throw new ArgumentException("消息无效", nameof(message));
try
{
if (_publishChannel == null || !_publishChannel.IsOpen)
{
InitPublishChannel();
}
// 序列化消息(压缩可选,生产环境大消息建议压缩)
var messageBody = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message, _jsonSettings));
// 消息属性(生产环境必须设置)
var properties = _publishChannel.CreateBasicProperties();
properties.DeliveryMode = 2; // 持久化
properties.MessageId = message.MessageId;
properties.CorrelationId = message.TraceId;
properties.Timestamp = new AmqpTimestamp(Convert.ToInt64((message.SendTimeUtc - new DateTime(1970, 1, 1)).TotalSeconds));
properties.Headers = new Dictionary<string, object>
{
{"ProducerService", message.ProducerService},
{"SendTimeUtc", message.SendTimeUtc.ToString("o")}
};
// 开启发布确认(生产环境必须,确保消息到达交换机)
_publishChannel.ConfirmSelect();
await Task.Run(() =>
{
_publishChannel.BasicPublish(
exchange: _config.ExchangeName,
routingKey: routingKey ?? _config.RoutingKey,
mandatory: true, // 强制路由,失败触发Return事件
basicProperties: properties,
body: messageBody);
// 等待确认(超时10秒)
if (!_publishChannel.WaitForConfirms(TimeSpan.FromSeconds(10)))
{
throw new Exception("消息发布未收到确认");
}
});
_logger.Information($"消息发布成功 | MessageId:{message.MessageId} | TraceId:{message.TraceId}");
}
catch (Exception ex)
{
_logger.Error(ex, $"消息发布失败 | MessageId:{message.MessageId}");
throw;
}
}
#endregion
#region 消息消费(生产级持续消费)
/// <summary>
/// 启动持续消费(生产环境核心方法)
/// </summary>
/// <typeparam name="T">业务数据类型</typeparam>
/// <param name="handleFunc">消费逻辑(业务代码)</param>
/// <param name="autoAck">是否自动确认(生产环境建议false)</param>
/// <returns></returns>
public async Task StartConsumeAsync<T>(Func<RabbitMqProductionMessage<T>, Task> handleFunc, bool autoAck = false)
{
if (_isConsuming)
{
_logger.Warning("消费已启动,无需重复调用");
return;
}
if (handleFunc == null) throw new ArgumentNullException(nameof(handleFunc));
_isConsuming = true;
_consumeCts = new CancellationTokenSource();
try
{
var connection = GetConnection();
_consumeChannel = connection.CreateModel();
_consumeChannel.BasicQos(0, _config.PrefetchCount, false); // 限流:每次最多处理PrefetchCount条消息
_consumer = new AsyncEventingBasicConsumer(_consumeChannel);
_consumer.Received += async (sender, args) =>
{
if (_consumeCts.Token.IsCancellationRequested) return;
var messageId = args.BasicProperties.MessageId ?? Guid.NewGuid().ToString("N");
var traceId = args.BasicProperties.CorrelationId ?? Guid.NewGuid().ToString("N");
try
{
// 反序列化消息
var messageBody = Encoding.UTF8.GetString(args.Body.ToArray());
var message = JsonConvert.DeserializeObject<RabbitMqProductionMessage<T>>(messageBody, _jsonSettings);
if (message == null || !message.Validate())
{
_logger.Warning($"消息无效,直接丢弃 | MessageId:{messageId}");
_consumeChannel.BasicNack(args.DeliveryTag, false, false);
ConsumeFailed?.Invoke(this, messageId);
return;
}
// 获取重试次数
var retryCount = args.BasicProperties.Headers?.ContainsKey("RetryCount") == true
? Convert.ToInt32(args.BasicProperties.Headers["RetryCount"])
: 0;
// 幂等性校验(生产环境必须,防止重复消费)
if (await CheckIdempotentAsync(message.MessageId))
{
_logger.Information($"消息已消费,跳过 | MessageId:{messageId} | TraceId:{traceId}");
_consumeChannel.BasicAck(args.DeliveryTag, false);
return;
}
// 执行业务消费逻辑
await handleFunc(message);
// 手动确认消息
if (!autoAck)
{
_consumeChannel.BasicAck(args.DeliveryTag, false);
}
// 记录幂等标识
await RecordIdempotentAsync(message.MessageId);
_logger.Information($"消息消费成功 | MessageId:{messageId} | TraceId:{traceId}");
ConsumeSucceeded?.Invoke(this, messageId);
}
catch (Exception ex)
{
_logger.Error(ex, $"消息消费失败 | MessageId:{messageId} | TraceId:{traceId}");
var retryCount = args.BasicProperties.Headers?.ContainsKey("RetryCount") == true
? Convert.ToInt32(args.BasicProperties.Headers["RetryCount"]) + 1
: 1;
if (retryCount <= _config.MaxRetryCount)
{
// 更新重试次数,重新入队
args.BasicProperties.Headers ??= new Dictionary<string, object>();
args.BasicProperties.Headers["RetryCount"] = retryCount;
_consumeChannel.BasicNack(args.DeliveryTag, false, true);
_logger.Warning($"消息重试 {retryCount}/{_config.MaxRetryCount} | MessageId:{messageId}");
}
else
{
// 超过重试次数,进入死信队列
_consumeChannel.BasicNack(args.DeliveryTag, false, false);
_logger.Error($"消息超过最大重试次数,进入死信队列 | MessageId:{messageId}");
ConsumeFailed?.Invoke(this, messageId);
}
}
};
// 处理消息无法路由的情况
_consumer.HandleReturn += (sender, args) =>
{
var messageId = args.BasicProperties?.MessageId ?? "未知";
_logger.Error($"消息无法路由 | MessageId:{messageId} | 路由键:{args.RoutingKey}");
ConsumeFailed?.Invoke(this, messageId);
};
// 开始消费(永久阻塞)
_consumeChannel.BasicConsume(
queue: _config.QueueName,
autoAck: autoAck,
consumer: _consumer);
_logger.Information("RabbitMQ消费已启动,持续监听队列...");
// 阻塞当前任务,保持消费线程存活
await Task.Delay(Timeout.Infinite, _consumeCts.Token);
}
catch (OperationCanceledException)
{
_logger.Information("消费已取消");
}
catch (Exception ex)
{
_logger.Error(ex, "消费启动失败");
throw;
}
}
/// <summary>
/// 停止消费(仅在程序退出时调用)
/// </summary>
public void StopConsume()
{
if (!_isConsuming) return;
_isConsuming = false;
_consumeCts?.Cancel();
_consumer?.Model?.BasicCancel(_consumer.ConsumerTag);
_consumeChannel?.Close();
_consumeChannel?.Dispose();
_consumeChannel = null;
_logger.Information("RabbitMQ消费已停止");
}
#region 幂等性实现(生产环境必须,以下为示例,需根据业务调整)
/// <summary>
/// 幂等性校验(检查消息是否已消费)
/// </summary>
/// <param name="messageId">消息ID</param>
/// <returns></returns>
private async Task<bool> CheckIdempotentAsync(string messageId)
{
// 生产环境实现:
// 1. 基于Redis:EXISTS {messageId}
// 2. 基于数据库:查询消费记录表
await Task.CompletedTask;
return false;
}
/// <summary>
/// 记录幂等标识(标记消息已消费)
/// </summary>
/// <param name="messageId">消息ID</param>
/// <returns></returns>
private async Task RecordIdempotentAsync(string messageId)
{
// 生产环境实现:
// 1. 基于Redis:SET {messageId} 1 EX 86400
// 2. 基于数据库:插入消费记录表
await Task.CompletedTask;
}
#endregion
#endregion
#region 资源释放(生产环境必须)
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_isDisposed) return;
if (disposing)
{
// 停止消费
StopConsume();
_consumeCts?.Dispose();
// 释放信道
_publishChannel?.Close();
_publishChannel?.Dispose();
// 释放连接池
foreach (var connection in _connectionPool.Values)
{
if (connection.IsOpen) connection.Close();
connection.Dispose();
}
_connectionPool.Clear();
_logger.Information("RabbitMQ客户端资源已释放");
}
_isDisposed = true;
}
~RabbitMqProductionClient()
{
Dispose(false);
}
#endregion
}
三、生产环境使用示例
1. 生产者示例
cs
using System;
using System.Threading.Tasks;
using Serilog;
class ProducerExample
{
static async Task Main(string[] args)
{
// 1. 配置日志
var logger = SerilogConfig.CreateProductionLogger();
// 2. 配置RabbitMQ
var config = new RabbitMqProductionConfig
{
Nodes = "rabbitmq-node1:5672,rabbitmq-node2:5672", // 集群节点
UserName = "prod_user",
Password = "prod_password",
ExchangeName = "order.exchange",
QueueName = "order.create.queue",
RoutingKey = "order.create",
PrefetchCount = 10,
MaxRetryCount = 3
};
// 3. 创建客户端
using var client = new RabbitMqProductionClient(config, logger);
// 4. 发布消息
for (int i = 1; i <= 1000; i++)
{
var message = new RabbitMqProductionMessage<OrderDto>
{
Data = new OrderDto
{
OrderId = Guid.NewGuid().ToString("N"),
UserId = "user_001",
Amount = i * 10,
CreateTime = DateTime.UtcNow
},
ProducerService = "OrderService",
TraceId = Guid.NewGuid().ToString("N")
};
await client.PublishAsync(message);
await Task.Delay(100); // 每秒发布10条
}
Console.WriteLine("消息发布完成");
Console.ReadKey();
}
}
// 业务DTO
public class OrderDto
{
public string OrderId { get; set; } = string.Empty;
public string UserId { get; set; } = string.Empty;
public decimal Amount { get; set; }
public DateTime CreateTime { get; set; }
}
2. 消费者示例(Windows 服务 / Linux 守护进程)
cs
using System;
using System.Threading.Tasks;
using Serilog;
class ConsumerExample
{
static async Task Main(string[] args)
{
// 1. 配置日志
var logger = SerilogConfig.CreateProductionLogger();
// 2. 配置RabbitMQ
var config = new RabbitMqProductionConfig
{
Nodes = "rabbitmq-node1:5672,rabbitmq-node2:5672",
UserName = "prod_user",
Password = "prod_password",
ExchangeName = "order.exchange",
QueueName = "order.create.queue",
RoutingKey = "order.create",
PrefetchCount = 10,
MaxRetryCount = 3
};
// 3. 创建客户端
using var client = new RabbitMqProductionClient(config, logger);
// 4. 订阅监控事件
client.ConsumeSucceeded += (s, messageId) =>
{
// 生产环境:上报监控平台(如Prometheus/Grafana)
logger.Information($"监控-消费成功:{messageId}");
};
client.ConsumeFailed += (s, messageId) =>
{
// 生产环境:发送告警(钉钉/邮件/短信)
logger.Error($"监控-消费失败:{messageId}");
};
client.ConnectionError += (s, ex) =>
{
logger.Error(ex, "监控-连接异常");
};
// 5. 启动持续消费
await client.StartConsumeAsync<RabbitMqProductionMessage<OrderDto>>(async (message) =>
{
// 生产环境业务逻辑:
// 1. 更新订单状态
// 2. 扣减库存
// 3. 发送短信通知
logger.Information($"处理订单:{message.Data.OrderId} | 金额:{message.Data.Amount}");
await Task.Delay(100); // 模拟业务处理
}, autoAck: false);
// 6. 阻止程序退出(生产环境作为服务运行时无需)
Console.WriteLine("消费者已启动,按任意键退出...");
Console.ReadKey();
// 7. 停止消费(程序退出时调用)
client.StopConsume();
}
}