一、缓存更新策略
主动更新策略
先删除缓存时,由于更新数据库耗时较多,在此期间如有另一线程查询缓存/数据库并写入缓存,则当前线程更新数据库后会导致缓存和数据库数据不一致,而反之出现不一致的概率要小得多。
二、缓存会出现的问题
1、缓存穿透
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,于是缓存就永远不会生效,这些请求会被直接打到数据库上。
解决方案
2、缓存雪崩
缓存雪崩是指在同一时段大量的缓存key同时失效或者redis服务宕机,导致大量请求到数据库。
解决方案
3、缓存击穿
缓存击穿也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
解决方案
(1)示意图:
(2)优缺点对比
互斥锁保证了一致性,牺牲了可用性,逻辑过期则相反。
三、超卖问题
解决方案
乐观锁的关键是判断之前得到的数据是否被修改过,常见的方式有版本号法和CAS法。乐观锁的缺点是失败率高,需要做业务控制,如改成where id=10 and stock>0即可。
注意:更新数据时才用乐观锁
四、一人一单问题(防止一个用户抢多个券)
为了防止同一用户抢到多个券,可以先查询前查询当前用户-券的订单是否已经存在,但在多线程下,多个查询的结果可以均为0,然后做添加订单操作,这仍会导致部分用户抢到多个券。
解决方案
单机情况下通过给用户加synchronized锁(内部维护锁监视器),但是集群模式下不行,多个JVM会有各自的锁,所以仍会出现一个用户抢了多个券的情况。解决方案是让多个JVM使用同一把锁,即分布式锁。
分布式锁:满足分布式系统或集群模式下多进程可见并互斥的锁。
基于redis的分布式锁
(1)可能出现的问题1
当线程1由于业务阻塞而锁时间到期释放锁,线程2就可以获取锁,在线程2执行业务时,线程1业务完成并释放了锁,就会出现新的并发问题。解决方案是在释放锁的时候,进行标识(线程id、uuid等)判断。
(2)可能出现的问题2
当线程1业务执行完并判断锁标识是否一致后,这时候却由于gc而出现阻塞,导致超时而释放了锁,于是线程2就可以获取锁。在线程2执行业务时,线程1的gc完成。由于线程1已经判断了锁因而可以直接释放锁,但此时redis锁是线程2的,这样就释放了线程2的锁,又会出现新的并发问题。
解决方案是在释放锁的时候,进行标识(线程id、uuid等)判断。