缓存是用空间换时间,来解决性能问题的一种架构设计模式。
Redis 用作缓存,我们需要注意两点:
1、从客户端的角度来说,缓存数据的特点一定是有原始数据来源,且允许丢失,当数据丢失后,我们需要从原始数据重新加载数据,不能认为缓存系统是绝对可靠的,更不能认为缓存系统不会删除没有过期的数据。
2、从 Redis 服务端的角度来说,缓存系统可以保存的数据量一定是小于原始数据的。首先,我们应该限制 Redis 对内存的使用量,也就是设置 maxmemory 参数。
由于缓存系统的 IOPS 比数据库高很多,因此要特别小心短时间内大量缓存失效的情况。这种情况一旦发生,可能就会在瞬间有大量的数据需要回源到数据库查询,对数据库造成极大的压力,极限情况下甚至导致后端数据库直接崩溃。这就是我们常说的缓存失效,也叫作缓存雪崩。
从广义上说,产生缓存雪崩的原因有两种:
1、缓存系统本身不可用,导致大量请求直接回源到数据库;
2、应用设计层面大量的 Key 在同一时间过期,导致大量的数据回源。
解决缓存 Key 同时大规模失效需要回源,导致数据库压力激增问题的方式有两种。
1、差异化缓存过期时间,不要让大量的 Key 在同一时间过期。比如,在初始化缓存的时候,设置缓存的过期时间是 30 秒 +10 秒以内的随机延迟(扰动值)。
2、让缓存不主动过期。初始化缓存数据的时候设置缓存永不过期,然后启动一个后台线程 30 秒一次定时把所有数据更新到缓存,而且通过适当的休眠,控制从数据库更新数据的频率,降低数据库压力。
但是我们需要特别注意以下三点:
1、方案一和方案二是截然不同的两种缓存方式,如果无法全量缓存所有数据,那么只能使用方案一
2、即使使用了方案二,缓存永不过期,同样需要在查询的时候,确保有回源的逻辑。因为我们无法确保缓存系统中的数据永不丢失。
3、不管是方案一还是方案二,在把数据从数据库加入缓存的时候,都需要判断来自数据库的数据是否合法,比如进行最基本的判空检查。
在某些key属于极端热点数据,且并发量很大的情况下,如果这个Key过期,可能会在某个瞬间出现大量的并发请求同时回溯,相当于大量的并发请求直接打到了数据库。这就是缓存击穿或者缓存并发问题。
缓存穿透和缓存击穿的区别:
缓存穿透是指,缓存没有起到压力缓冲的作用;
而缓存击穿是指,缓存失效时瞬时的并发打到数据库。
解决缓存穿透的方案:
1、对不存在的数据,同样设置一个特殊的 Value 到缓存中,比如当数据库中查出的用户信息为空时,设置 NODATA 这样具有特殊含义的字符串到缓存中。这样下次请求缓存的时候还是可以命中缓存,即直接从缓存返回结果,不查询数据库。(存在问题;可能会把大量无效的数据加入缓存中,如果担心大量无效数据占满缓存,使用方案2)
2、使用布隆过滤器做前置过滤。把所有可能的值保存在布隆过滤器中,从缓存读取数据前先过滤一次:如果布隆过滤器认为值不存在,那么值一定是不存在的,无需查询缓存也无需查询数据库;对于极小概率的误判请求,才会最终让非法 Key 的请求走到缓存或数据库。