.NET Core站点IIS Web Garden跨工作进程共享类/内存方案咨询
解决Web Garden多进程下的重复请求控制问题
你遇到的核心问题是Web Garden中每个工作进程(WP)拥有独立的内存空间,原来单进程里的共享控制类只能在单个WP内生效,跨进程就失去了互斥作用。既然不想用SQL Server,下面给你几个基于内存级共享的可行方案,按推荐程度排序:
1. 优先使用Redis分布式内存缓存(最推荐)
Redis是纯内存键值存储,速度完全能满足你的性能需求,而且天然支持跨进程、跨机器的共享锁和数据存储,是解决这类问题的标准方案。
在.NET Core中,你可以通过IDistributedCache来集成Redis,利用它的原子性操作实现请求的互斥控制:
实现思路
- 针对每个用户的支付请求,生成一个唯一的幂等标识(比如
$"{userId}_payment_{requestId}",其中requestId可以是前端生成的UUID或者请求的唯一哈希)。 - 尝试将这个标识存入Redis,设置一个合理的过期时间(比如5分钟,防止死锁),并且使用原子性的"不存在则写入"操作(Redis的
SETNX命令,对应.NET Core里的StringSet方法加When.NotExists参数)。 - 如果写入成功,说明这是该用户的首次有效请求,执行支付校验逻辑;如果写入失败,直接返回"重复提交"的提示。
代码示例
public class PaymentService { private readonly IDistributedCache _distributedCache; public PaymentService(IDistributedCache distributedCache) { _distributedCache = distributedCache; } public async Task<bool> ProcessPaymentAsync(string userId, string requestId) { // 生成唯一的请求锁键 var lockKey = $"payment_lock:{userId}:{requestId}"; // 设置锁的过期时间,避免异常导致锁一直存在 var lockOptions = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) }; // 原子性写入:只有当键不存在时才写入成功 var lockAcquired = await _distributedCache.StringSetAsync(lockKey, "locked", lockOptions, When.NotExists); if (!lockAcquired) { // 重复请求,直接返回 return false; } try { // 执行你的银行支付校验和余额增加逻辑 // ... return true; } finally { // 执行完成后释放锁(可选,因为有过期时间兜底) await _distributedCache.RemoveAsync(lockKey); } } }
同时,建议把你的会话存储也迁移到Redis(通过AddStackExchangeRedisSession),这样跨进程的会话数据也是共享的,和请求锁配合能更全面地控制重复提交。
2. 使用内存映射文件(进程间共享内存)
如果因为某些原因无法使用Redis,可以考虑用.NET的**内存映射文件(MemoryMappedFile)**实现进程间的共享内存存储。这种方式是直接在操作系统层面开辟一块共享内存区域,所有WP都可以访问。
实现思路
- 创建一个全局唯一命名的内存映射文件,用来存储已提交的请求标识。
- 对共享内存的访问要加进程间互斥锁(Mutex),确保读写操作的原子性,避免并发冲突。
注意事项
- 这种方式实现起来比Redis复杂,需要自己处理内存管理、锁的释放、异常处理等问题。
- 只能在同一台服务器的多个WP间共享,如果以后扩展到多服务器就失效了,扩展性不如Redis。
3. IIS的传统共享缓存(仅适用于传统ASP.NET,不推荐.NET Core)
如果你是从传统ASP.NET迁移过来的,可能知道IIS有基于System.Web.Cache的共享缓存,但在.NET Core中已经被IDistributedCache替代,而且这种共享缓存依赖于IIS的配置,稳定性和扩展性都不如Redis,所以不推荐在.NET Core中使用。
关键提醒
- 无论用哪种方案,幂等性标识的设计很重要:要确保同一个用户的同一请求(比如重复点击提交按钮)生成的标识是相同的,而不同请求的标识是唯一的。
- 一定要设置锁的过期时间:防止因为程序异常、进程崩溃等情况导致锁一直占用,影响后续请求。
内容的提问来源于stack exchange,提问作者Sino
相关产品推荐
相关产品推荐

