高流量场景下Memcached/IMemoryCache间歇性返回null问题排查
问题根因
该问题和EnyimMemcachedCore、IMemoryCache的组件本身实现无关,高流量+大体积缓存条目是核心触发诱因,直接故障点为以下三类共性问题:
- 缓存写入静默失效,读取返回null
Memcached服务端默认单条Value大小上限为1MB,存入的大体积业务数据极易触发该阈值,EnyimMemcachedCore写入超限制数据时会直接返回失败,如果自定义的CacheService.AddCacheData方法未捕获异常、未校验写入返回结果,会出现写入实际失效但业务无感知的情况,读取时自然返回null。
对IMemoryCache而言,若应用配置了内存回收阈值(SizeLimit),高流量下内存占用快速攀升时,大体积缓存条目会被标记为最高优先级驱逐对象,可能在写入后极短时间内被后台扫描线程回收,读取时恰好命中回收窗口就会返回null。 - 空值未拦截直接传入下游方法触发异常
抛出的System.ArgumentNullException: Value cannot be null. (Parameter 's')异常,本质是读取到null的缓存返回值后,未做任何判空校验,直接将null传入了Json反序列化、字符串处理类方法——这类方法的字符串参数s不允许传入null,因此直接抛出参数空异常,和缓存组件本身逻辑无关。 - 高并发读写竞态导致非预期null
通过new方式初始化的静态_cacheMemory实例,若内部的读写方法未对同键并发操作加锁控制,高并发下同一个键同时被多个请求覆盖写入时,会出现内存流写入冲突,读取时可能拿到半写入的损坏数据,反序列化时触发空值异常。
此外静态实例如果内部自行初始化Memcached客户端,而非使用DI注入的单例客户端,高并发下会出现连接泄漏、连接池耗尽,导致读写超时返回null。IMemoryCache本身虽然是线程安全的,但热点键同时被覆盖写+读取时,在条目置换的极短窗口内也可能返回null。
修复方案
- 解决写入静默失效问题
- 对EnyimMemcachedCore:根据业务数据大小调整Memcached服务端单条Value上限(建议不超过10MB,单条缓存值过大会大幅降低序列化、网络传输性能),修改
CacheService的写入封装逻辑,捕获写入异常、校验写入返回结果,写入失败时打日志留存上下文,不要静默吞错。 - 对IMemoryCache:如果没有做精确的内存容量管控,暂时移除
SizeLimit配置避免非预期驱逐;如果必须配置内存阈值,给大体积缓存条目设置CacheItemPriority.NeverRemove优先级,避免被优先回收,同时合理配置缓存条目的Size权重,保证内存回收逻辑符合预期。
- 所有缓存读取逻辑强制加空值校验
不管使用哪种缓存组件,读取返回值后先判断是否为空,再做后续反序列化、业务处理,从根源上阻断参数空异常,同时在值为空时走回源逻辑重新加载数据写入缓存,示例代码:
- 所有缓存读取逻辑强制加空值校验
// EnyimMemcachedCore 读取逻辑 var resultsData = _cacheMemory.GetCacheData("flightData_" + _uid); if (resultsData == null) { // 回源加载源数据 resultsData = LoadFlightMainDataFromSource(_uid); _cacheMemory.AddCacheData("flightData_" + _uid, resultsData, 1800); } // IMemoryCache 读取逻辑 var resultsData1 = _memoryCache.Get<string>("flightData_1" + _uid); if (string.IsNullOrEmpty(resultsData1)) { resultsData1 = FlightMainData.Medium; _memoryCache.Set("flightData_1" + _uid, resultsData1, DateTimeOffset.UtcNow.AddMinutes(30)); }
- 修复高并发竞态问题
- 废弃自行new的静态
_cacheMemory实例,将缓存封装服务通过依赖注入注册为单例,复用EnyimMemcachedCore默认提供的单例Memcached客户端,避免多实例导致的连接池耗尽、连接泄漏问题。 - 对访问量极高的热点缓存键,用
SemaphoreSlim做同键的并发限流,避免同一个键同时被大量请求击穿写入,降低读写竞态概率。 - 大体积缓存按业务维度拆分为小粒度键存储,不要将整份大对象存在单个缓存键中,既可以规避单条Value大小超限问题,也能降低内存回收时的驱逐优先级,同时提升序列化、网络传输效率。
- 补充高流量场景防护
给缓存过期时间增加1-5分钟的随机偏移量,避免大量缓存键同一时间集中过期导致的请求突增、回源压力过大问题,进一步降低缓存层的并发压力。
- 补充高流量场景防护
内容的提问来源于stack exchange,提问作者Udaya
相关产品推荐
相关产品推荐

