正确做法是全局单例复用 IConnection,每次业务操作创建独立 IModel 并用 using 确保 Dispose;接收端用 AsyncEventingBasicConsumer 支持异步处理,配合手动 Ack、持久化和幂等设计保障可靠性。

怎么用 RabbitMQ.Client 发送消息(Publish)
发送消息本身不难,但容易卡在连接复用和通道生命周期上。直接 new 一个 Connection 再发完就 Close(),看似干净,实则性能极差——TCP 握手开销大,且 RabbitMQ 会拒绝高频短连接。
正确做法是:全局单例复用 IConnection,每个业务操作(如一次 HTTP 请求处理)申请独立的 IModel(即 channel),用完立即 Dispose(),不要手动 Close()。
-
IConnection必须线程安全,建议用Lazy<iconnection></iconnection>或 DI 容器注册为 Singleton -
IModel不是线程安全的,绝不能跨线程共享或池化;用using确保释放 - 发送前检查
model.IsOpen,避免因网络闪断导致的AlreadyClosedException - 启用
ConfirmSelect()+WaitForConfirmsOrDie()可确保消息到达 Broker(适合关键业务)
using var channel = connection.CreateModel();
channel.ExchangeDeclare("order.exchange", "topic", durable: true);
channel.BasicPublish(
exchange: "order.exchange",
routingKey: "order.created",
basicProperties: new BasicProperties { DeliveryMode = 2 }, // 持久化
body: Encoding.UTF8.GetBytes(json));
怎么可靠地接收消息(Consume)并支持异步处理
用 BasicConsume 注册事件回调看起来简单,但默认是同步执行、阻塞 channel 的——如果回调里有 await 但没用 async void(或用了却没处理异常),消息会丢失、channel 会死锁。
根本解法是:不用事件回调,改用 AsyncEventingBasicConsumer(v6.0+)或手动拉取 + Task.Run 调度(v5.x 兼容方案)。
- v6.0+ 推荐用
AsyncEventingBasicConsumer,它的Received事件支持async委托,内部自动 await 并正确 Nack/Ack - 务必设置
channel.BasicQos(0, 1, false)控制预取数量,否则消费者积压时内存暴涨 - 处理失败时别直接 throw,要显式调用
model.BasicNack(deliveryTag, multiple: false, requeue: false)避免无限重试 - 反序列化错误、数据库超时等应归入 dead-letter queue,而不是让消息卡在队列里
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.Received += async (ch, ea) =>
{
try
{
var msg = Encoding.UTF8.GetString(ea.Body.Span);
await ProcessOrderAsync(msg); // 真正的异步业务逻辑
ch.BasicAck(ea.DeliveryTag, multiple: false);
}
catch
{
ch.BasicNack(ea.DeliveryTag, multiple: false, requeue: false);
}
};
为什么你的 ConnectionFactory 总连不上 Docker 里的 RabbitMQ
本地开发连不上,90% 是因为 Host 配置写成了 localhost 或 127.0.0.1——Docker 容器内访问宿主机的 localhost 并不指向你本机的 RabbitMQ,而是它自己的回环。
- Docker for Windows/Mac:用
host.docker.internal替代localhost - Linux Docker:需加
--add-host=host.docker.internal:host-gateway启动容器 - 确认端口映射:Docker run 是否暴露了
5672(AMQP)而非只暴露15672(Management UI) - 检查
ConnectionFactory.Uri是否包含guest/guest用户——RabbitMQ 3.3+ 默认禁用该用户远程登录,需显式创建用户并赋权
异步处理中怎么保证消息不丢、不重复
“至少一次”靠的是手动 Ack + 持久化配置;“恰好一次”在应用层无法真正实现,只能靠幂等设计来收敛重复影响。
- Exchange、Queue、Message 三者都必须设
durable: true,否则重启 RabbitMQ 后配置丢失 - Ack 必须在业务逻辑成功提交后调用,比如 DB 事务
Commit()成功之后,不能提前 - 避免在 Ack 前做耗时 I/O(如发邮件),否则 channel 长时间被占,影响吞吐
- 为每条消息生成唯一
MessageId,消费端用 Redis 或 DB 记录已处理 ID,重复消息直接 skip
最常被忽略的一点:消费者进程意外退出(如 OOM、kill -9)时,未 Ack 的消息会自动重回队列,但如果没设 autoAck: false,消息就真的丢了。


















