某业务处理缓存和存储不一致的方案

240 阅读2分钟

C端业务

遇到的问题:redis缓存和mysql数据不统一

背景:为了提升用户体验,展示用户数据的数据来自Redis,而装备与账号的归属关系(背包是否能看到)则落在mysql。

所以当用户更新数据后,因为内部老系统下发服务的关系,可能导致写入数据库失败,最终导致redis中数据和背包数据不一致。为了用户的体验,可以及时看到自己的反馈,经产品评估可以接受5%少量的在一段时间内数据不一致,所以这里选择了先更新缓存,然后再另开线程更新数据库。

在缓存作为又读又写的情况下,我们会遇到以下两个问题:

1.写+读并发:数据库会与缓存存在短暂不一致,但在这之前进来的读请求都能直接命中缓存,获取到最新值对业务影响较小
过程:
1.线程A先更新缓存成功
2.线程B读取数据,此时线程B命中缓存,读取到最新值后返回
3.线程A更新数据库成功

解决方案:保存请求对缓存的读取记录,存action进入消息MQ延时比较,发现不一致后,做业务补偿(重试)

2.写+写并发: 会导致数据库和缓存的不一致,对业务影响较大
过程:
1.线程A和线程B同时更新同一条数据
2.更新缓存的顺序是先A后B
3.更新数据库的顺序是先B后A

解决方案:写请求进来时,针对一个数据资源的修改操作,先加分布式锁,保证同一时间只有一个线程去更新数据库和缓存;没有拿到锁的线程把操作放入到队列中,延时处理。用这种方式保证多个线程操作同一资源的顺序性,以此保证一致性。

分布式锁的例子:
1.乐观锁 : 使用版本号或者updatetime等key存入缓存中,并且只允许高版本覆盖低版本
2.redis setnx : 获取锁:setnx 1成功 0失败;释放锁:del命令/lua脚本
3.hset :利用Redis的Hash结构作为储存单元,将业务指定的名称作为key,将随机UUID和线程ID作为field,最后将加锁的次数作为value来储存,线程安全。