5、Reids缓存问题与解决
5.1、背景
Redis作为一种内存性数据库,当查询的数据在redis缓存中时就不需要在到真实的数据库中去查,加快了查询速度和保护了真实数据库的安全,但是同时也引入了一些新的问题,比如查询的数据不在内存和数据库中,同一时间大量的key失效等情况,为此需要解决这些问题,才可以更好的使用redis作为缓存。
5.2、缓存穿透
5.2.1、何为缓存穿透
缓存穿透:简言之就是查询时穿过了缓存,直接在真实的数据库中进行查找,但是真实的数据库中也不存在该数据,此时就造成了缓存穿透。
因为当系统中引入redis 缓存后,一个请求进来后,会先从redis 缓存中查询,缓存有就直接返回,缓存中没有就去真实的数据库中查询,真实的数据库中如果有就会将其丢到缓存中,但是有些key 对应的数据在真实的数据库中并不存在,所以不会把数据丢到缓存中,故每次针对此次key 的请求从缓存中取不到,请求都会压到真实对的数据库,从而可能压垮真实的数据库。有些黑客就是利用缓存穿透来击垮你的数据库,导致业务不能正常运行。
5.2.2、如何解决
-
对空值进行缓存
如果一个查询返回的数据为空(不管数据库是否存在),我们仍然把这个结果(null)进行缓存,给其设置一个很短的过期时间,最长不超过五分钟.这样可以防止针对同一个无效key的重复查询。
-
设置黑名单
采用对空值进行缓存来解决缓存穿透时会带来数据的冗余,因为非法的key有很多个,当采用对空值进行缓存来解决穿透时,需要多每个非法key对应的值赋予空值,虽然我们可以设置很短的过期时间,但是还是会浪费内存空间,为了解决这个问题,我们可以利用set集合的特点来设置一个黑名单,即只有不在名单中的key可以继续向后访问,在名单中的key进行拦截。
-
采用布隆过滤器
布隆过滤器(Bloom Filter )是1970年有布隆提出的,它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。其中0代表不存在,1代表存在,拥有 k 个独立的哈希函数映射 ,通过要判断的字符分别计算出哈希值 ,当 k 个下标获取到的值都为1时,则认为当前字符存在,否则不存在。
布隆过滤器是一种数据结构,能够高效地判断一个元素是否存在于集合中,可以用来处理缓存穿透问题。将所有可能的有效key都存储在布隆过滤器中,每次请求到来时,先进行判断,如果不在布隆过滤器中,即可直接过滤掉,而无需查询缓存或数据库。
-
优点:速度非常快,占用空间极少,操作的是机器底层二进制向量。
-
缺点:有一定的误识别率和删除困难。
-
-
实时监控
当发现redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制对其提供服务(比如:IP 黑名单
-
限流控制
通过在系统层面实施限流机制,限制单个IP或用户的请求频率,防止频繁发送大量无效请求,从而减轻缓存层和后端服务的压力。
-
基础校验
在业务层进行基础的参数校验,例如对传入参数进行合法性验证,排除掉无效的请求。这样可以在请求到达Redis之前,就能够过滤掉大部分恶意或无效的请求。
5.3、缓存击穿
5.3.1、何为缓存击穿
缓存击穿:简言之就是击穿缓存(Redis),直接访问真实的数据库,给真实的数据库造成了巨大的压力。
缓存击穿的原因,主要是因为热点key同时过期,造成大量请求拿不到缓存中的key,直接将这些请求转发到真实的数据库中,造成数据库崩溃。缓存击穿常常发生在热门数据失效时的短时间窗口。
5.3.2、如何解决
-
预热热点key
在系统启动时或者在缓存失效之前主动加载热门数据到缓存中,提前进行缓存预热。即提前把热点数据放入缓存中,同时实时监控,这样可以保证缓存中始终存在热门的数据,减少缓存击穿的可能性。
-
适当调整热点key的过期时间
可以在设置缓存过期时间时,增加一定的随机性。这样可以让缓存在不同的时间点失效,减少大量缓存同时失效的概率
-
热点数据隔离
我们可以将热门的数据隔离开来,使用不同的缓存实例或集群来存储。这样可以减少热点数据集中在一个缓存节点上的概率,降低缓存击穿的风险
-
互斥锁
在缓存失效的时候,使用互斥锁来防止多个请求同时访问数据库。当一个请求发现缓存失效时,先去获取互斥锁,然后再查询数据库并将结果放入缓存,其他请求在获取到锁之前等待,从而避免了多个请求同时击穿缓存。
-
分布式锁
缓存中拿不到数据的时候,此时不是立即去db中查询,而是去获取分布式锁(比如redis 中的setnx),拿到锁再去db中load数据;没有拿到锁的线程休眠一段时间再重试整个获取数据的方法。
-
后台异步更新
当缓存中的数据即将失效时,立即返回旧的缓存数据,并异步地更新缓存和数据库。这样可以保证用户快速获取到旧的缓存数据,避免因为数据库查询时间较长而导致的缓存击穿。同时,在数据更新完成后,再将新数据放入缓存中。
5.4、缓存雪崩
5.4.1 何为缓存雪崩
缓存雪崩:指的是在某个时间段,缓存中的大量数据同时过期,而此时又有大量的请求到达,导致请求无法命中缓存,需要去请求数据库,从而造成数据库瞬时的压力过大。缓存雪崩往往是由于缓存系统故障、节点宕机或者某些异常情况导致的。
缓存雪崩与缓存击穿的区别在于:前者是大量的key 集中过期,而后者是某个热点key 过期。
5.4.2、如何解决
-
key失效时间间隔开
合理设置缓存的过期时间,避免所有缓存同时失效所导致的数据库压力过载。可以采用随机过期时间、过期时间的分级设置等策略,让缓存的失效时间具有一定的随机性和均匀分布。
-
多级缓存
引入多级缓存架构,将热门数据放置在更靠近应用的缓存层,并采用分层的方式进行缓存数据的存储。可以使用本地缓存(如内存)作为第一级缓存,再结合Redis作为第二级缓存,甚至可以引入更高级的缓存层,如分布式缓存。这样可以在缓存失效时,从更低级的缓存层补充获取数据,减少对后端数据库的直接访问。
-
缓存数据分散
仅仅依靠一个Redis实例来存储所有缓存数据存在单点故障的风险。可以将缓存数据分散到多个Redis实例上,避免单一实例故障导致全部缓存数据不可用。可以使用Redis集群、主从复制等机制实现数据的分散存储和高可用性。
-
限流
通过限制接入系统的请求流量,控制单个时间段内的请求并发量,减轻对缓存和后端系统的压力。可以使用限流算法,如令牌桶、漏桶等来对请求进行限制和平滑处理。
-
锁或队列
用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,不适用高并发情况。
-
监控和预警
建立系统的监控和预警机制,实时监控缓存命中率、缓存过期率等指标,一旦发现异常情况,及时采取措施进行处理。可以借助工具、系统日志、指标监控等手段来实现。