MediatR不能直接当消息总线用,因其仅为进程内调用封装,缺乏队列缓冲、持久化、重试等能力,高并发下易阻塞线程池并引发异常;应仅用于轻量副动作,可靠投递需外接RabbitMQ等真实消息队列。

MediatR 在高并发下为什么不能直接当消息总线用
MediatR 本质是进程内同步/异步方法调用的封装,INotificationHandler 虽然支持多订阅者,但所有 handler 都在同一线程上下文(或 Task.Run 启动的新线程)里串行或并行执行,不提供队列缓冲、持久化、重试、死信等能力。高并发写入时,如果某个 handler 执行慢(比如发邮件、调第三方 API),会阻塞后续通知,甚至压垮线程池。
常见错误现象:TaskCanceledException 频发、ThreadPool.GetAvailableThreads 返回值持续接近 0、GC 压力陡增。
- 别把
INotification当 RabbitMQ 用;它适合“主流程完成后的轻量副动作”,比如清本地缓存、记录审计日志(且日志写入本身要异步非阻塞) - 真正需要解耦+可靠投递的场景,必须外接真实消息队列(如 RabbitMQ、Kafka),用 MediatR 只做 CQRS 内部命令/查询的协调胶水
- 若坚持用 MediatR 处理耗时通知,至少包裹一层
Task.Run(() => { ... })并配熔断(如 Polly),但这是权宜之计,不是架构解法
CQRS 中 Command Handler 如何避免数据库写竞争
C# 高并发下,多个相同 UpdateUserCommand 同时到达,若都走“查-改-存”流程,极易引发脏写或乐观并发异常。CQRS 不自动解决这个问题,得靠设计约束。
关键点在于:Command Handler 必须是幂等的,且数据库操作需带并发控制语义。
- 优先用基于版本号的乐观并发:实体含
RowVersion或int Version字段,EF Core 中配置IsConcurrencyToken(),更新时 WHERE 条件强制校验版本,失败则抛DbUpdateConcurrencyException - 避免在 Handler 里先
FindAsync再修改——这引入窗口期;改用ExecuteSqlRaw或 EF Core 的Update+Where构建原子更新语句 - 对强一致性要求极高的操作(如库存扣减),加分布式锁(如 Redis Lock)或用数据库行锁(
SELECT ... FOR UPDATE),但要严格控制锁粒度和超时
MediatR Pipeline Behavior 怎么安全做并发限流
全局限流不能只靠中间件(如 ASP.NET Core 的 RateLimitingMiddleware),因为 MediatR 管道可拦截所有 Command/Query,更适合业务维度的细粒度控制。
用 IPipelineBehavior 实现限流时,注意 .NET 的 SemaphoreSlim 是单机有效的,集群部署需换 Redis。
mallcloud商城基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba并采用前后端分离vue的企业级微服务敏捷开发系统架构。并引入组件化的思想实现高内聚低耦合,项目代码简洁注释丰富上手容易,适合学习和企业中使用。真正实现了基于RBAC、jwt和oauth2的无状态统一权限认证的解决方案,面向互联网设计同时适合B端和C端用户,支持CI/CD多环境部署,并提
public class RateLimitBehavior: IPipelineBehavior { private readonly IConnectionMultiplexer _redis; private readonly string _keyPrefix; public async TaskzuojiankuohaophpcnTResponseyoujiankuohaophpcn Handle(TRequest request, RequestHandlerDelegatezuojiankuohaophpcnTResponseyoujiankuohaophpcn next, CancellationToken ct) { var key = $"{_keyPrefix}:{request.GetType().Name}:{GetUserId(request)}"; var db = _redis.GetDatabase(); var current = await db.StringIncrementAsync(key, 1, ct); if (current == 1) await db.KeyExpireAsync(key, TimeSpan.FromMinutes(1), ct); if (current > 100) // 每分钟最多 100 次 throw new InvalidOperationException("Rate limit exceeded"); return await next(); }}
- 别用静态
SemaphoreSlim做跨请求限流——它不跨线程上下文,且无法在 WebHost 多实例时协同- 限流 Key 必须包含能区分调用方的字段(如用户 ID、租户 ID),否则全站共用一个桶就失去意义
- 限流逻辑必须放在管道最外层,确保所有入口(Controller、BackgroundService、gRPC)都被覆盖
为什么 CQRS 查询模型不该直接复用领域实体
高并发读场景下,用 EF Core 直接查
User领域实体返回给前端,等于把聚合根完整加载、映射、序列化——字段冗余、N+1 查询、JSON 序列化开销大,拖慢吞吐。查询模型必须是“面向前端/客户端”的扁平结构,且数据获取路径要彻底绕过领域层。
- 查询 Handler 应直接访问只读数据库视图或物化查询表(Materialized View),用 Dapper 执行
QueryAsync,跳过 EF Core ChangeTracker 和导航属性加载- DTO 字段名与数据库列名严格对应,避免 ORM 映射开销;必要时用
AsNoTracking()+ 投影(Select)代替全实体加载- 禁止在查询 Handler 里调用任何领域服务或仓储的写方法——CQRS 分离的核心是意图分离,不是物理分层
实际落地时,最难的不是写对 MediatR 的注册代码,而是判断哪条通知该进 Kafka、哪条能走内存通知,以及如何让查询 DTO 的变更不影响命令侧的领域契约。这些边界一旦模糊,高并发下的性能毛刺和数据不一致就会变成常态。









