Redis应用问题解决


概念图

缓存使用流程

1、缓存穿透

1.1、简介

场景:缓存和数据库中都没有的数据,而用户不断发起请求。每次从缓存中都查不到数据,而需要查询数据库,同时数据库中也没有查到该数据,也没法放入缓存。

不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

1.2、解决方案

校验参数(过滤恶意伪造的请求)

比如合法id是15xxxxxx,以15开头的。如果用户传入了16开头的id,比如:16232323,则参数校验失败,直接把相关请求拦截掉。

缓存空值

  1. 如果一个查询返回的数据为空(不管数据是否不存在),仍把空结果(null)进行缓存,设置空结果过期时间会很短,不超过五分钟。
  2. 对空值缓存面对查询的条件不断变化,就会显得无力,比如查询参数一直变化:-1,-2,-3、、、

采用布隆过滤器

布隆过滤器(Bloom Filter)是一个很长的二进制向量(位图)和一系列随即映射函数9哈希函数)。

布隆过滤器用于检索一个元素是否在一个集合中。优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个 bitmaps 拦截,从而避免对底层存储系统的查询压力。

问题:
如果布隆过滤器判断出某个key存在,可能出现误判。如果判断某个key不存在,则它在数据库中一定不存在。

  1. 如果想减少误判率,可以适当增加hash函数,
  2. 布隆过滤器最致命的问题是:如果数据库中的数据更新了,需要同步更新布隆过滤器。但它跟数据库是两个数据源,就可能存在数据不一致的情况。

比如:数据库中新增了一个用户,该用户数据需要实时同步到布隆过滤。但由于网络异常,同步失败了。

这时刚好该用户请求过来了,由于布隆过滤器没有该key的数据,所以直接拒绝了该请求。但这个是正常的用户,也被拦截了。

很显然,如果出现了这种正常用户被拦截了情况,有些业务是无法容忍的。所以,布隆过滤器要看实际业务场景再决定是否使用,它帮我们解决了缓存穿透问题,但同时了带来了新的问题。

设置可访问的名单(白名单)

使用 bitmaps 类型定义一个可以访问的名单,名单 id 作为 bitmaps 的偏移量,每次访问和 bitmap 里面的 id 进行比较,如果访问 id 不在 bitmaps 里面,进行拦截,不允许访问。

实时监控设置黑名单

当发现 Redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,设置黑名单限制服务。

2、缓存击穿

2.1、简介

场景:为了保证访问速度,通常情况下,商城系统会把商品信息放到缓存中。但如果某个时刻,该商品到了过期时间失效了。

此时,如果有大量的用户请求同一个商品,但该商品在缓存中失效了,一下子这些用户请求都直接怼到数据库,可能会造成瞬间数据库压力过大,而直接挂掉。

2.2、解决方案

预先设置热门数据(缓存不失效)

对于很多热门key,其实是可以不用设置过期时间,让其永久有效的。

场景:比如参与秒杀活动的热门商品,由于这类商品id并不多,在缓存中我们可以不设置过期时间。

在秒杀活动开始前,我们先用一个程序提前从数据库中查询出商品的数据,然后同步到缓存中,提前做预热。

等秒杀活动结束一段时间之后,我们再手动删除这些无用的缓存即可。

自动续期

  1. 出现缓存击穿问题是由于key过期了导致的。在key快要过期之前,就自动给它续期即可(用job给指定key自动续期)。

  2. 场景:

在很多请求第三方平台接口时,我们往往需要先调用一个获取token的接口,然后用这个token作为参数,请求真正的业务接口。一般获取到的token是有有效期的,比如24小时之后失效。
如果我们每次请求对方的业务接口,都要先调用一次获取token接口,显然比较麻烦,而且性能不太好。

这时候,我们可以把第一次获取到的token缓存起来,请求对方业务接口时从缓存中获取token。

同时,有一个job每隔一段时间,比如每隔12个小时请求一次获取token接口,不停刷新token,重新设置token的过期时间。

加锁

解决:数据库压力过大的根源是,因为同一时刻太多的请求访问了数据库。只要限制 同一时刻只有一个请求才能访问某个productId的数据库商品信息,即可解决问题。(使用加锁的方式)

  1. 在缓存失效的时候(判断拿出来的值为空),不是立即去load db.

  2. 先使用缓存工具的某些自带成功操作返回值的操作(比如 Redis 的 SETNX)去 set 一个 mutex key。

  3. 当操作返回成功时,再进行 load db 的操作,并回设缓存,最后删除 mutex key

  4. 当操作返回失败,证明有线程在 load db ,当前线程睡眠一段时间再重试整个 get 缓存的方法。

    try {
      String result = jedis.set(productId, requestId, "NX", "PX", expireTime);
      if ("OK".equals(result)) {
        return queryProductFromDbById(productId);
      }
    } finally{
        unlock(productId,requestId);
    }  
    return null;
    

在访问数据库时加锁,防止多个相同productId的请求同时访问数据库。然后还需要一段代码,把从数据库中查询到的结果,重新放入缓存中。

EX PX NX XX概念 http://doc.redisfans.com/string/set.html

3、缓存雪崩

3.1、简介

  1. 有大量的热门缓存,同时失效。会导致大量的请求,访问数据库。而数据库很有可能因为扛不住压力,而直接挂掉。
  2. 缓存服务器down机了,可能是机器硬件问题,或者机房网络问题。总之,造成了整个缓存的不可用。

3.2、解决方案

将缓存失效时间分散开

为了解决缓存雪崩问题,首先要尽量避免缓存同时失效的情况发生。

  1. 首先要看看这个Key过期是不是时点性有关,时点性无关的话,可以随机过期时间解决。

    实际过期时间 = 过期时间 + 1~60秒的随机数

  2. 如果是时点性有关,例如刚刚说的银行某一天改变某系数,那么就要利用强依赖击穿方案,策略是先过去的线程更新一下所有key。
    在后台更新热点key的同时,业务层将进来的请求延时一下,例如短暂的睡几毫秒或者秒,给后面的更新热点key分散压力。

构建多级缓存架构

nginx缓存 + Redis缓存 +其他缓存(ehcache)等

使用锁或队列

用加锁或者队列的方式保证不会有大量线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况

高可用

针对缓存服务器down机的情况,在前期做系统设计时,可以做一些高可用架构。

比如:如果使用了redis,可以使用哨兵模式,或者集群模式,避免出现单节点故障导致整个redis服务不可用的情况。

使用哨兵模式,当某个master服务下线时,自动将该master下的某个slave服务升级为master服务,替代已下线的master服务继续处理请求。

服务降级

如果做了高可用架构,redis服务还是挂了,就需要服务降级了。

我们需要配置一些默认的兜底数据。

程序中有个全局开关,比如有10个请求在最近一分钟内,从redis中获取数据失败,则全局开关打开。后面的新请求,就直接从配置中心中获取默认的数据。

当然,还需要有个job,每隔一定时间去从redis中获取数据,如果在最近一分钟内可以获取到两次数据(这个参数可以自己定),则把全局开关关闭。后面来的请求,又可以正常从redis中获取数据了。

需要特别说一句,该方案并非所有的场景都适用,需要根据实际业务场景决定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值