面试必备Redis知识 -- Redis面试题(二)

博客首页崇尚学技术的科班人
今天给大家带来的文章是《面试必备Redis知识 -- Redis面试题(二)》
希望各位小伙伴们能够耐心的读完这篇文章
博主也在学习阶段,如若发现问题,请告知,非常感谢
同时也非常感谢各位小伙伴们的支持

文章目录

    • 1、Redis 事务
    • 2、如何实现分布式锁?
      • ① 加锁
      • ② 解锁
    • 3、缓存穿透
    • 4、缓存击穿
    • 5、缓存雪崩
    • 6、Redis 常见性能问题和解决方案
    • 7、Redis 如何做内存优化?
    • 8、如何保证 redis 中的数据都是热点数据?
    • 9、Redis 中如何将指定模式的 key 查找出来?

1、Redis 事务

:说一下你对 redis 事务的了解呗。

Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务功能。

  • 事务流程
  1. 开始事务(MULTI)。
  2. 命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行)。
  3. 执行事务(EXEC)。

你也可以通过 DISCARD 命令取消一个事务,它会清空事务队列中保存的所有命令。

WATCH 命令用于监听指定的键,当调用 EXEC 命令执行事务时,如果一个被 WATCH 命令监视的键被修改的话,整个事务都不会执行,直接返回失败。

Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)

2、如何实现分布式锁?

:高并发下分布式系统中的数据线程安全怎么保障?

① 加锁

    /**
     * 加锁
     * @param key
     * @param value
     * @return
     */
    public boolean lock(String key,String value){
        if(redisTemplate.opsForValue().setIfAbsent(key,value)){
            // 加锁成功
            return true;
        }

        // 如果锁过期
        String currentValue = redisTemplate.opsForValue().get(key);
        if(!StringUtils.isEmpty(currentValue) &&
                Long.parseLong(currentValue) < System.currentTimeMillis()){

            String oldValue = redisTemplate.opsForValue().getAndSet(key,value);
            if(!StringUtils.isEmpty(oldValue) &&
                    oldValue.equals(oldValue)){
                return true;
            }
        }

        // 锁已经被其它线程获取
        return false;
    }
  • setIfAbsent:如果对应的 key 不存在的话,那么就进行相关设置,也就是加锁成功。同时我们设置进行的 value 是对应的 key 的过期时间。
  • 过期时间的判断:如果对应的锁被相关的其它进程获取,我们还要判断对应的锁是否过期。如果过期的话,那么其它的线程就可以进行争抢。
  • getAndSet:获取旧值设置新值,该操作是原子性的。
  • oldValue.equals(oldValue):该判断尤其重要,因为我们此时是高并发状态下,那么就有可能出现对应的线程争抢情况,但是我们只能允许一个线程获取锁,那么我们就要对其进行 oldValue 的判断,如果是相同的话,那么就获取锁成功,否则就是被其它线程争抢过去了。

② 解锁

  • 查询当前“锁”是否还是我们持有,因为存在过期时间,所以可能等你想解锁的时候,“锁”已经到期,然后被其他线程获取了,所以我们在解锁前需要先判断自己是否还持有“锁”
  • 如果“锁”还是我们持有,则执行解锁操作,也就是删除该键值对,并返回成功;否则,直接返回失败。
    /**
     * 解锁
     * @param key
     * @param value
     */
    public void unlock(String key,String value){
        try{
            String currentValue = redisTemplate.opsForValue().get(key);
            if(!StringUtils.isEmpty(currentValue) &&
                    currentValue.equals(value)){
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        }catch (Exception e){
            log.error("【redis 分布式锁】 解锁异常,{}",e);
        }
    }

3、缓存穿透

:简要说一下缓存穿透现象和怎样应对 ?

  • 描述:访问一个缓存和数据库都不存在的 key,此时请求会直接打到数据库上,并且数据库查不到数据,也没办法写入缓存,所以下一次请求同样会打到数据库上。

解决方案

  1. 接口校验:后端接口增加数据合理性校验,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤。
  2. 缓存空值:当访问缓存和DB都没有查询到值时,可以将空值写进缓存,为其设置短点的过期时间,防止同一个 key 被一直攻击。
  3. 布隆过滤器:使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库。

4、缓存击穿

:简要说一下缓存击穿现象和怎样应对 ?

  • 描述:某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。

解决方案

  1. 设置热点数据不过期,或者定时任务定时更新缓存。
  2. 设置互斥锁,在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。(可以使用 Redis 分布式锁

5、缓存雪崩

:简要说一下缓存雪崩现象和怎样应对 ?

  • 描述:大量的热点 key 设置了相同的过期时间,导在缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力骤增,引起雪崩,甚至导致数据库被打挂。

解决方案

  1. 加互斥锁: 该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。
  2. 热点数据不过期:该方式和缓存击穿一样。
  3. 过期时间打散:既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。

6、Redis 常见性能问题和解决方案

:你了解redis 常见性能问题和解决方案吗?

  1. Master 最好不要写内存快照,如果 Master 写内存快照,save 命令调度rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务
  2. 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
  3. 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网
  4. 尽量避免在压力很大的主库上增加从库
  5. 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <-Slave1<- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现 Slave对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。

7、Redis 如何做内存优化?

:redis 如何做内存优化?

尽可能使用散列表,散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面

8、如何保证 redis 中的数据都是热点数据?

:MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?

  • Redis 提供 6 种数据淘汰策略:
  1. volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
  4. allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
  5. allkeys-random:从数据集中任意选择数据淘汰
  6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
  • 4.0 版本后增加以下两种:
  1. volatile-lfu:从已设置过期时间的数据集中挑选最不经常使用的数据淘汰
  2. allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

9、Redis 中如何将指定模式的 key 查找出来?

:假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

  • 使用 keys 指令可以扫出指定模式的 key 列表。

:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?

  1. redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢
  2. 这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

你可能感兴趣的:(面试题,redis,面试,缓存)