数据还未缓存,大量并发请求

在数据还未缓存的情况下,大量并发请求过来的话,如果处理不好,很容易就打到DB。

解决思路(1):预先缓存

如果可以事先知道需要缓存哪些数据,那么就预先将这些数据缓存起来。处理方式可以是:

  1. 程序启动时

  2. 人工在后台触发

解决思路(2):排队

把映射到同一个缓存key的请求排队,挨个处理,那么第一个请求从DB获得数据并缓存后,后面的请求就都能直接命中缓存了。

看到有些解决方案是在代码中用synchronize(monitor)加锁的方式达到排队的效果,这样做有两个问题:

  1. 如果key不固定,那么会将所有请求都阻塞,这个办法只有在key固定的时候用。比如List<Item> queryByName(String name)这个就不能用这个方法,因为key是不固定的,但是这个List<Item> getAll()可以用这个方法,因为key是固定的。

  2. 跨JVM的情况下控制不住,依然会打到DB。不过这个问题一般不大,最坏情况是打到DB的请求数=应用服务器集群节点数量。

映射到不存在key的恶意请求

恶意发出一个不存在的数据的请求,因为这个数据在DB中并不存在,所以在缓存中自然也不存在其对应的Key,于是每次请求都会打到DB。

解决思路

把数据库返回空的结果也缓存下来,这样就不会每次请求都打到DB。

旧缓存失效,但新缓存还未生成时的高并发请求

旧缓存失效了,但此时新缓存还未生成,期间又有高并发的请求过来,那么就会打到DB。

这个问题有和前面讲的“数据还未缓存,大量并发请求”点类似但又有点区别,区别在于前一种情况是数据从来没缓存过,这里的情况是缓存存在过又消失了。

解决思路(1):更新缓存状态,不删除失效缓存

旧缓存失效但不删除缓存,而是更新缓存的失效状态。请求过来之后,不论缓存失效与否都返回缓存数据,即旧数据也返回出去。

另外有一个定时任务,专门负责标记缓存失效(通过过期时间),并刷新失效缓存。

这个方案有两个缺陷:

  1. 因为不删除失效缓存,所以缓存数据会越来越多

  2. 必须保存更新缓存所需要的参数,因为如果没有这些参数,那个负责刷新失效缓存的定时任务就没法知道如何从DB获得数据。
    在简单情况下,如果key本身就包含了如何更新缓存的信息,那就很方便了,比如key=all-item-list

解决思路(2):缓存失效时间错峰

这个问题比较常见的现象是大量缓存同时失效,因为这些缓存是几乎在相同时间里创建的,而失效时间又设成一样。

将缓存的实效时间随机一些,比如在5-10分钟之间波动,那么在单位时间里失效的缓存数量就会大大减少,就能够有效降低单位时间里打到DB的请求数。

参考链接:


chanjarster
4.2k 声望244 粉丝