MassTransit UseBusOutbox() + EF Core + MySQL 场景下消息偶发不投递,OutboxMessage/OutboxState 已插入但未进入 SendObserver
问题概述
我在一个 ASP.NET Core + MassTransit + RabbitMQ + EF Core + MySQL 的项目中,使用了:
MassTransit.EntityFrameworkCoreMassTransit.RabbitMQUseBusOutbox()
遇到一个比较诡异的问题:
- 业务代码里
publishEndpoint.Publish(...)能正常返回 - EF Core 日志能看到
OutboxState/OutboxMessage已成功插入 CommitAsync没有异常- 但消息偶发不会真正发到 RabbitMQ
- 此时我挂的
SendObserver/ReceiveObserver都没有任何日志
如果去掉 UseBusOutbox(),同样的消息链路连续测试很多次都正常,没有这个现象。
所以我目前基本确认问题只出现在 MassTransit EF Outbox 这一层。
环境信息
- .NET:
net10.0 - EF Core:
9.0.12 - Pomelo.EntityFrameworkCore.MySql:
9.0.0 - MassTransit.EntityFrameworkCore:
8.5.5 - MassTransit.RabbitMQ:
8.5.5 - RabbitMQ: 使用 RabbitMQ transport
- 数据库: MySQL
相关包版本:
<PackageReference Include="MassTransit.EntityFrameworkCore" Version="8.5.5" />
<PackageReference Include="MassTransit.RabbitMQ" Version="8.5.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.12" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
Outbox 配置
配置方式如下:
services.AddMassTransit(x => {
x.SetEndpointNameFormatter(
new KebabCaseEndpointNameFormatter(prefix: endpointNamePrefix, includeNamespace: true)
);
x.AddConsumers(consumerAssemblies);
x.AddEntityFrameworkOutbox<TDbContext>(o => {
o.UseMySql();
o.UseBusOutbox();
// 我后续也尝试过调小 QueryDelay,例如 500ms
// o.QueryDelay = TimeSpan.FromMilliseconds(500);
});
x.UsingRabbitMq((context, cfg) => {
cfg.UsePublishFilter(typeof(TenantPublishFilter<>), context);
cfg.UseConsumeFilter(typeof(TenantConsumeFilter<>), context);
cfg.ConnectPublishObserver(new TrackingPublishObserver(...));
cfg.ConnectSendObserver(new TrackingSendObserver(...));
cfg.ConnectReceiveObserver(new TrackingReceiveObserver(...));
cfg.ConfigureEndpoints(context);
});
});
业务场景
消息类型是一个普通 contract:
public record ContractReviewAnalyzeRequestedContract(long FileId);
发布方式有两种:
1. HTTP 测试接口内直接 Publish
[HttpPost("{id:long}/test")]
public async Task<IActionResult> Test(long id) {
await unitOfWork.ExecuteAsync(async () => {
await publishEndpoint.Publish(new ContractReviewAnalyzeRequestedContract(id));
});
return Ok();
}
2. 领域事件处理器内 Publish
public async override Task<ContractReviewFileUploadedDomainEvent> HandleAsync(
ContractReviewFileUploadedDomainEvent command,
CancellationToken cancellationToken = default
) {
var fileId = command.File.Id;
var messageId = NewId.NextGuid();
logger.LogInformation(
"准备发布合同分析请求。fileId={FileId}, messageId={MessageId}",
fileId,
messageId
);
await publishEndpoint.Publish(
new ContractReviewAnalyzeRequestedContract(fileId),
publishContext => {
publishContext.MessageId = messageId;
publishContext.CorrelationId = messageId;
},
cancellationToken
);
logger.LogInformation(
"合同分析请求 Publish 调用完成。fileId={FileId}, messageId={MessageId}",
fileId,
messageId
);
return await base.HandleAsync(command, cancellationToken);
}
现象
现象 1:偶发不发
同样一条消息,连续发多次时:
- 大概 10 次里有 4 次正常
- 大概 10 次里有 6 次“看起来没发出去”
所谓“没发出去”指的是:
- 发布前后业务日志都有
- EF Core 能看到 Outbox 插入 SQL
- 但
TrackingSendObserver没有PreSend/PostSend TrackingReceiveObserver也没有PreReceive/PostReceive- 消费者没有收到消息
现象 2:去掉 UseBusOutbox() 后稳定正常
我把 UseBusOutbox() 去掉后,连续测试很多次:
SendObserverReceiveObserver- Consumer
都稳定触发,没有复现问题。
已确认的事实
1. 消息确实写入了 Outbox
EF Core SQL 日志能明确看到:
INSERT INTO `OutboxState` ...
INSERT INTO `OutboxMessage` ...
实际日志里 OutboxMessage.Body 中能看到完整消息体,例如:
{
"messageId": "50860000-5f41-aa39-d9b2-08de9b88fe12",
"destinationAddress": "rabbitmq://.../ContractReview.Domain.Contracts:ContractReviewAnalyzeRequestedContract",
"messageType": [
"urn:message:ContractReview.Domain.Contracts:ContractReviewAnalyzeRequestedContract"
],
"message": {
"fileId": 795229726203973
}
}
2. 事务提交没有报错
我的 UnitOfWork 大致如下:
public async Task<T> ExecuteAsync<T>(Func<Task<T>> func, CancellationToken cancellationToken = default) {
await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
try {
var result = await func();
if (dbContext.ChangeTracker.HasChanges()) {
logger.LogInformation("发生了变更,进行SaveChanges");
await dbContext.SaveChangesAsync(cancellationToken);
}
await transaction.CommitAsync(cancellationToken);
return result;
} catch (Exception ex) {
logger.LogError(ex, "工作单元异常");
await transaction.RollbackAsync(cancellationToken);
throw;
}
}
出现问题时:
- 能看到
发生了变更,进行SaveChanges - 看不到
工作单元异常
所以至少从应用日志看,SaveChanges/Commit 没有抛异常。
3. Delivery 线程确实在轮询
EF Core SQL 日志还能看到周期性执行:
SELECT * FROM `OutboxState` ORDER BY `Created` LIMIT 1 FOR UPDATE SKIP LOCKED
说明 Outbox Delivery 线程并不是完全没跑。
4. 但有些时候始终不进入 SendObserver
正常时会看到:
[发信探针 - PreSend]
[发信探针 - PostSend]
[收信探针 - PreReceive]
消费者日志
异常时则只有:
- 业务侧 Publish 前后日志
- EF Core 的 Outbox 插入日志
- OutboxState 轮询 SQL
但没有任何 PreSend/PostSend。
关键日志片段
异常场景
业务日志:
准备发布合同分析请求。fileId=795269460717637, messageId=08490000-5f41-aa39-d7db-08de9b85beaf
合同分析请求 Publish 调用完成。fileId=795269460717637, messageId=08490000-5f41-aa39-d7db-08de9b85beaf
EF Core SQL:
INSERT INTO `OutboxState` ...
INSERT INTO `OutboxMessage` ...
之后仅看到:
SELECT * FROM `OutboxState` ORDER BY `Created` LIMIT 1 FOR UPDATE SKIP LOCKED
但没有:
TrackingSendObserver.PreSend
TrackingSendObserver.PostSend
TrackingReceiveObserver.PreReceive
Consumer.Consume
正常场景
同样的 Publish 链路,正常时可以看到:
>>> [发信探针 - PreSend]
<<< [发信探针 - PostSend]
>>> [收信探针 - PreReceive]
收到合同分析请求。fileId=...
已排除项
1. RabbitMQ 基础连通性问题
去掉 UseBusOutbox() 后稳定正常,因此不是 RabbitMQ 基础连通性问题。
2. 消费者逻辑异常
有些时候连 ReceiveObserver 都不触发,所以不是消费逻辑导致的“处理失败后看起来像没收到”。
3. CommitAsync 抛异常
没有进入 UnitOfWork 的 catch。
4. 消息根本没入库
EF Core SQL 明确能看到 OutboxState/OutboxMessage 插入。
额外现象:RabbitMQ 里出现过一个异常队列
我曾在 RabbitMQ 管理页面看到一个队列:
contract-review-contract-review-infrastructure-consumers-contract-review-analyze-test
现象是:
- 我每次
Publish,这个队列的Ready数量也会增加 - 但当前代码里并没有显式定义这个
...-test端点
这让我怀疑是否有历史遗留绑定、旧实例、测试端点绑定未清理等问题。不过即使不考虑这个队列,UseBusOutbox() 偶发不发的问题依然存在。
我目前最想确认的问题
MassTransit 8.5.5 + EntityFrameworkOutbox + MySQL + EF Core 9是否已知存在类似问题?- 有没有可能是
OutboxState/OutboxMessage被某种竞争条件处理掉了,但没有真正进入 transport send? SELECT ... FOR UPDATE SKIP LOCKED能看到,但没有SendObserver,这种现象更可能出现在什么执行路径上?- 有没有推荐的源码断点位置,适合直接确认消息是卡在:
- Outbox delivery 查询后
- transport send 前
- 还是某个内部状态更新逻辑中
如果有人建议升级
我目前不能简单升级到 MassTransit.EntityFrameworkCore 8.5.6+,因为它引出的 EF Core 依赖版本在我的项目里暂时无法接受,所以如果有建议,最好是在:
MassTransit 8.5.5EF Core 9.0.12Pomelo 9.0.0
这个约束下给出排查方向。
感谢
如果有人遇到过类似问题,或者知道应该优先打哪些源码断点/看哪些内部日志,非常感谢指点。