【面试复习】数据库 - redis(下)

1、缓存设计可能遇见的问题与解决方案

  1.1、缓存穿透: 当一个请求数据,在缓存中查不到数据,请求就会跑去db查找;如果同时有大量的无效请求过来,在缓存中都查不到数据,就会穿透缓存来到db,最终造成db负载过重,拖垮db,这就是缓存穿透。  解决缓存穿透的方案本质上是找出这类无效请求:(1)对于数据key值比较固定的数据,可以考虑使用布隆过滤器;(2)添加验证码去掉部分机器请求;(3)通过引入安全旁路,或者风控,对于多次重复的请求,将这些请求的特征值在redis中缓存下来;(4)降级

  1.2、缓存失效: 多个数据从db缓存到redis时,设计了相同的过期时间,这可能会导致,在某一个时刻,很多key同时失效,然后一瞬间众多情况会同时来到db,又会给db造成压力。

  1.3、缓存雪崩: 当缓存层宕机了以后,所有的请求就会直接打到db层,直接会压垮db层,像雪崩一样引发连锁反应。解决方案:redis变为高可用的,使用redis cluster;组件限流并降级;提前压测

  1.4、热点缓存key重建问题:当某个key突然成为热点key,即大量请求来访问db,并且去redis重建热点key的缓存,可能会造成后端程序负载过大,可能会让应用崩溃。解决方案:利用分布式互斥锁,只允许一个线程执行,取db数据,重建redis缓存。伪代码如下:

String get(String key){
    String value = redis.get(key);
    if(value == null){
        String lockName = "mutex:key:" + key;
        if(redis.setNx(lockName, "1", "ex 180")){
            value = db.get(key);
            redis.setex(key, value, timeout);
            redis.delete(lockName);
        }else{
            sleep(50);
            get(key);
        }
    }
    return value;
}

  1.5 布隆过滤器

    布隆过滤器将db中所有的key值,通过多个hash函数,映射到一个bitmap数组中。最终达成通过布隆过滤器的key,db中不一定有,但是没通过布隆过滤器的db里面一定没有。

 

2、redis分布式锁

  考虑利用redis设计一个分布式锁:利用 setnx 或者 hsetnx 命令,setnx、hsetnx 往缓存中存值,如果缓存中已经有值,则返回失败。可以很简单设计出如下代码: 

String doSomething(String key){
    String mutexName = "mutex:key:" + key;
    String mutexValue = ToString(time(null)) + RandNum(10); 
    int timeout = 30000;
    
    while(!redis.setNx(mutexName, mutexValue, timeout)){
        sleep(50);
    }
    try{
        
        //... do something ...
        
    }catch{
        ...
    }finally{
        // 每一次拿锁时都设置一个随机value,释放锁时确认下value值,防止释放了别的程序的锁
        if(redis.get(mutexName) == mutexValue){
            redis.delete(mutexName);
        }
    }
    ...
}

    有意思的是,此时的timeout 如何设计?考虑到实际的业务代码可能执行时间可能长于锁的过期时间,可以考虑将锁设置较长的过期时间,可是这样会面临另一个问题,如果程序加锁成功后宕机了,此时其他的线程则不得不等待锁自动过期,这样看来锁超时时间也不能设计的过于长。

  可以看下redisson是如何设计解决这个问题的:

      

 

  在主线程加锁的同时,后台新开一个线程一直再给锁续命,如果过期时间为30s,则每隔10秒给锁续命一次。

 

3、redis开发规则与性能优化

  3.1、key名的设计

    可读性和可管理性: 业务名:表名:id;简洁性:key长度不宜过长,内存很贵的;不要包含特殊字符;

  3.2、value的设计

    拒绝bigkey;关于bigkey的定义:字符串类型:单个value值超过10k就是bigkey;非字符串类型:元数个数超过5000就是bigkey;

    对于非字符串的bigkey(比较常见),不要执行整个key的del操作,也不要设置这个key的超时时间(超时时间到了有可能会触发del操作),取值时尽量不要key下面的全体,例如不要用hgetall,去使用hmget;还有一种就是将bigkey进行拆分。

  3.3、选择合适的数据类型

  3.4、控制key的生命周期

  3.5、O(N) 命令关注N的数量,hgetall、Irange、smembers、zrange、sinter这些命令尽量使用 hscan、sscan、zscan 代替

  3.6、禁用命令:keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan方式渐进式处理。

  3.7、使用批量命令提高效率  原生:mget、mset;非原生:pipeline

  3.8、redis事务较弱建议使用lua代替

  3.9、客户端使用连接池

    连接池参数的设置: 

      maxTotal:一个客户端与redis服务器之间的最大连接数

      maxIdle、minIdle:  maxIdle是业务需要的最大连接数,maxTotal是为了给出余量,一般推荐maxIdle设置为业务期望QPS所需连接数,maxTotal可以放大一倍。minIdle:最小空闲连接数。

      如果系统马上会有很多请求过来,可以考虑给redis连接池做预热

  3.10、高并发下可以考虑给客户端添加熔断功能

  3.11、redis 过期键的三种清除策略:

      被动删除:当一个过期key被读写时,会触发惰性删除策略,直接删除这个过期key

      主动删除:redis会定期删除一批已经过期的key

      主动清理:当内存超过maxmemory时,触发主动清理

  

 

posted @ 2020-07-22 18:17  Demo12138  阅读(22)  评论(0编辑  收藏  举报