消息队列场景下IMemoryCache缓存并发修改异常及优化方案咨询
解决方案与优化建议
1. 优先采用不可变数据结构解决并发修改问题
C#原生的System.Collections.Immutable库提供的ImmutableList<T>完美适配你的场景,核心优势是:
- 所有修改操作(Add/Remove/Update)都会返回全新的不可变列表实例,原实例永远不会被改动
- 内部采用结构共享实现,性能远高于手动全量拷贝
- 搭配不可变业务对象(类属性全部设为
{get; init;},仅允许初始化时赋值),连内部对象的并发修改问题也能彻底解决
实践方式:缓存中直接存储ImmutableList<消息类型>,每次更新列表时直接替换缓存对应Key的Value,取列表写DB时拿到的天然是当前时间点的快照,不会被后续修改影响,完全不需要手动写拷贝逻辑。
2. 优化锁方案降低性能损耗
如果你暂时不想改不可变结构,可对原锁方案做两层优化:
- 用细粒度Per-Key锁:维护一个
ConcurrentDictionary<string, object>,每个用户ID对应独立的锁对象,仅锁定当前操作的用户列表,不会阻塞其他用户的读写操作 - 缩短锁持有时间:锁内仅完成列表快照拷贝,拷贝完成立即释放锁,无需将耗时的DB写入操作放在锁内执行,锁持有时间控制在微秒级,基本无性能损耗
3. 引入变更队列实现读写链路解耦
结合你的消息队列业务场景,可采用「缓存+异步队列」的架构彻底解决冲突:
- 所有对用户消息列表的增删操作,先写入对应
Channel<消息变更事件>(可按用户ID哈希分片减少队列数) - 后台常驻单线程消费任务,按用户维度拉取变更事件,攒批后同时完成两个操作:更新缓存中的列表、生成操作请求写入Cassandra
- 该方案完全避免多线程并发修改缓存,还能进一步合并DB写入请求,最大化利用Cassandra的批量写入性能
4. Cassandra写入逻辑优化
针对你当前的单用户一行表结构,不需要每次全量写入整个列表:
- 用CQL原生的列表增量操作:新增消息执行
UPDATE 表 SET 消息列 = 消息列 + [?] WHERE 用户ID = ?,删除消息执行UPDATE 表 SET 消息列 = 消息列 - [?] WHERE 用户ID = ? - 增量写入不需要读取DB,也不需要依赖全量列表,写入Payload更小、速度更快,也能避免全量列表被修改导致的写入内容不一致问题
5. 多实例缓存一致性兜底
你当前是2台服务实例部署,本地缓存天然存在跨实例一致性问题,可加轻量失效机制兜底:
- 实例更新某用户的缓存后,通过Redis Pub/Sub或者集群UDP广播发送失效通知
- 其他实例收到通知后删除对应Key的本地缓存,下次访问该用户数据时自动从Cassandra拉取最新值重建缓存,一致性窗口通常小于100ms,完全适配消息队列业务的容忍度
内容的提问来源于stack exchange,提问作者dtln812
相关产品推荐
相关产品推荐

