缓存
缓存类别
1.线程级缓存:ThreadLocal
适用场景:同个线程内需要多次请求耗时接口,例如需要根据权限范围查询具体数据,多次查权限。
2.单机缓存:GuavaCache
适用场景:数据量较少,请求频率高。
3.分布式缓存:Redis
适用场景:
缓存遇到的问题
缓存并发
现象:热点key并发量高。当key过期时,同一时刻会有多个请求打到db,影响db性能。
解决方案:
1.当key不存在需要查询db时加锁,获取到锁的线程才能查db。其余线程自旋等待。
2.GuavaCache中expireAfterWrite当某个key失效时,第一个线程会锁住当前key所在的segment,其余线程等待,容易造成多线程阻塞。可以通过refreshAfterWrite解决,定时更新缓存。当缓存失效时,只有第一个线程从db捞数据,其余线程直接返回旧数据。
缓存雪崩
现象:在同一时刻缓存中有多个key失效时,多个请求直接打到db,导致db负载过高。
解决方案:初始化缓存时,过期时间设置成定时+随机时间,避免大量key同一时刻失效。
缓存穿透
现象:当请求缓存中不存在的key值,请求直接打到了db。
解决方案:初始化缓存或者查询缓存时,如果数据不存在,则缓存中设置空值。(疑问:初始化时,如何给不存在的key设置空值)
缓存更新策略(一致性)
1.Cache-Aside
1.先更新缓存在更新数据库。
存在问题:
-缓存更新成功,数据库更新失败,缓存中的值为旧值。
2.先更新数据库在更新缓存。
存在问题:
-key不存在时。线程1读取数据库旧值,线程2更新db,线程2将值放入缓存,线程1将值再次放入缓存,缓存中为旧值
-key不存在时。线程1更新db,线程2更新db,线程2将值放入缓存,线程1将值再次放入缓存,缓存中为旧值
image.png
3.先更新数据库,再失效缓存
存在问题:
-key失效,线程1读取db旧值,线程2更新db,线程2失效缓存,线程1将db旧值放入缓存。
image.png
产生条件
1.读操作读缓存失效
2.有个并发的写操作
3.读操作早于写操作进入数据库,晚于写操作更新缓存
2.Cache-As-SoR systemOfRecord 有cache组件统一更新缓存与db,客户端无感知。
1.Read-through
2.Write-through
3.Wtire-behind
缓存淘汰策略
1.LRU最近最少使用:map中记录访问次数
2.FIFO先进先出。
缓存过期策略
1.定期清理
2.查询时判断key是否过期
3.结合1+2