数据还未缓存,大量并发请求
在数据还未缓存的情况下,大量并发请求过来的话,如果处理不好,很容易就打到DB。
解决思路(1):预先缓存
如果可以事先知道需要缓存哪些数据,那么就预先将这些数据缓存起来。处理方式可以是:
程序启动时
人工在后台触发
解决思路(2):排队
把映射到同一个缓存key的请求排队,挨个处理,那么第一个请求从DB获得数据并缓存后,后面的请求就都能直接命中缓存了。
看到有些解决方案是在代码中用synchronize(monitor)
加锁的方式达到排队的效果,这样做有两个问题:
如果key不固定,那么会将所有请求都阻塞,这个办法只有在key固定的时候用。比如
List<Item> queryByName(String name)
这个就不能用这个方法,因为key是不固定的,但是这个List<Item> getAll()
可以用这个方法,因为key是固定的。跨JVM的情况下控制不住,依然会打到DB。不过这个问题一般不大,最坏情况是打到DB的请求数=应用服务器集群节点数量。
映射到不存在key的恶意请求
恶意发出一个不存在的数据的请求,因为这个数据在DB中并不存在,所以在缓存中自然也不存在其对应的Key,于是每次请求都会打到DB。
解决思路
把数据库返回空的结果也缓存下来,这样就不会每次请求都打到DB。
旧缓存失效,但新缓存还未生成时的高并发请求
旧缓存失效了,但此时新缓存还未生成,期间又有高并发的请求过来,那么就会打到DB。
这个问题有和前面讲的“数据还未缓存,大量并发请求”点类似但又有点区别,区别在于前一种情况是数据从来没缓存过,这里的情况是缓存存在过又消失了。
解决思路(1):更新缓存状态,不删除失效缓存
旧缓存失效但不删除缓存,而是更新缓存的失效状态。请求过来之后,不论缓存失效与否都返回缓存数据,即旧数据也返回出去。
另外有一个定时任务,专门负责标记缓存失效(通过过期时间),并刷新失效缓存。
这个方案有两个缺陷:
因为不删除失效缓存,所以缓存数据会越来越多
必须保存更新缓存所需要的参数,因为如果没有这些参数,那个负责刷新失效缓存的定时任务就没法知道如何从DB获得数据。
在简单情况下,如果key本身就包含了如何更新缓存的信息,那就很方便了,比如key=all-item-list
解决思路(2):缓存失效时间错峰
这个问题比较常见的现象是大量缓存同时失效,因为这些缓存是几乎在相同时间里创建的,而失效时间又设成一样。
将缓存的实效时间随机一些,比如在5-10分钟之间波动,那么在单位时间里失效的缓存数量就会大大减少,就能够有效降低单位时间里打到DB的请求数。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。