面试题-Redis

简单说明

用户在请求一个复杂的查询接口时,通常在进行一次请求之后会将数据存入Redis中,方便后续的请求可以直接通过Redis快速获取数据 ,从而减少数据库的请求压力。

一、缓存雪崩

1.什么是缓存雪崩

Redis的缓存雪崩指的是大量请求无法命中Redis的缓存数据,所以请求都发送到了数据库。

正常情况下是

客户端 -> request -> Redis (存在缓存)

客户端 <- response <- Redis (返回缓存数据)

如果出现缓存雪崩 :

客户端 -> request -> xRedis(无数据) -> 数据库

数据库无法同时处理大量请求,所以可能会导致数据库所在服务器宕机

2.发生缓存雪崩的原因

① 大量Redis缓存数据同时过期,导致所有发送到Redis的请求都无法命中数据,只能到数据库中进行查询。

② Redis服务器宕机,所有请求都无法请求到Redis服务器,最后只能请求到数据库。

3.如何避免缓存雪崩

① 针对数据添加一个随机的过期时间,避免大量缓存数据同时过期。例如给原有的过期时间添加一个范围内的随机值。

② 针对服务器宕机这个问题,可以提前搭建好主从服务器进行数据同步,并配置哨兵机制。这样在主服务器宕机时,可以由哨兵将从服务器该改为主服务器继续提供服务。

二、缓存击穿

1.什么是缓存击穿

类似于雪崩,但是这里特指热点数据,热点数据过期的话请求会直接打在服务器上。

2.如何解决

① 最简单的方法:

不对热点数据添加过期时间

②互斥锁

在查询数据库的业务上添加一个互斥锁,只有在获得锁的请求时才能进行数据库的查询,其他没有获取锁的请求只有等待数据就绪。

优点:无额外内存消耗、保证了一次性、无额外的内存消耗。

缺点:没获取到锁的线程需要等待,性能受影响。

代码示例:

@Component
public class RedisUtil {

    private static StringRedisTemplate stringRedisTemplate;

    @Resource
    public static void setRedisTemplate(StringRedisTemplate redisTemplate) {
        RedisUtil.stringRedisTemplate = redisTemplate;
    }

    /**
     * 获取互斥锁
     * 这里的锁key不是业务的key,而是互斥锁的专用key
     */
    public static boolean tryLock(String key){
        //获取互斥锁,然后十秒的一个过期时间
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        //里面有NullAble注解,所以判断一下空
        if (flag!=null && flag){
            return flag;
        }
        return false;
    }

    /**
     * 释放锁 其实就是删除key
     */
    public static void delLock(String key){
        stringRedisTemplate.delete(key);
    }
}

业务类

   @Override
    public Result echoUserTarget(IdVo idVo) {
        Long id = idVo.getId();
        if (id==null){
            return Result.paramError();
        }
        //案例
        String key = "userTarget:echo";
        String lockKey= "userTarget:echo:lock";
        try {
            //先去Redis查询
            String res = stringRedisTemplate.opsForValue().get(key);
            //如果没有该key值,就要去查库
            if (res == null){
                return Result.ok(JSON.parseObject(res,EchoUserTargetResults.class));
            }
            //没有数据就先获取互斥锁
            boolean bol = RedisUtil.tryLock(lockKey);
            //获取成功就去查询数据库,然后存到redis
            if (bol){
                EchoUserTargetResults result = userTargetsDao.echoUserTarget(id);
                if (result == null){
                    //数据库也没有就是缓存穿透 给Redis设置一个空字符串以控制该情况
                    stringRedisTemplate.opsForValue().set(key,"", 30,TimeUnit.MINUTES);
                    return Result.ok(null);
                }
                stringRedisTemplate.opsForValue().set(key,JSON.toJSONString(result), 30,TimeUnit.MINUTES);
                return Result.ok(result);
            }
            //如果没有获取到互斥锁就进行休眠 知道更够返回数据
            return echoUserTarget(idVo);
        } catch (Exception e) {
            throw new RuntimeException();
        } finally {
            //最后要释放互斥锁
            RedisUtil.delLock(lockKey);
        }
    }

③逻辑过期时间

在数据中添加一个逻辑过期时间的字段,而不真正的设置数据的过期时间,在获取的时候判断逻辑过期时间是否过期,false就直接返回。如果已经过期了,就开启另一个线程去更新Redis的数据,然后当前的请求就返回当前的数据。

三、缓存穿透

1.什么是缓存穿透

就是查询的数据,即在Redis中没有,也在数据库没有,导致请求一定会直达数据库,造成对数据库服务器的一个请求压力。

2.为什么会发生缓存穿透

① 用户恶意攻击。

② 数据被误删除。

③ 确实没有该数据,例如一个数据为空的列表。

3.如何避免缓存穿透

① 在Redis中设置空值获取默认值(缺省值)

如果Redis打在数据库的查询数据为空,就可以为Redis设置一个默认的值,但是切记有真实数据缠产生的时候记得将Redis的默认缓存数据进行修改,也可以设置过期时间。

但是如果用户一直高并发的使用不同的不存在的数据进行请求,还是会出现该问题。

②布隆过滤器

组成部分有两个: bit数组 = N个hash函数

Redis本身就支持布隆过滤器,所以我们可以直接使用Redis布隆过滤器,而不用自己去实现,非常方便。

 布隆过滤器类似于hash结构,可以做一些无重复数据推荐的业务。

具体理论是这样,但是在代码中实现还需要很多锻炼。

Redis持久化

一、AOF

采用日志形式记录每个写的操作,追加到AOF文件的末尾

默认情况不开启AOF,重启时再执行AOF文件中的命令来恢复数据。

是命令执行完再记录日志。原因:日志记录时不会对语法进行检查,会记录错误的命令,所以要在命令执行结束后再向日志中记录命令。

会遇到两个问题:
1.当执行命令还未完成时服务器就宕机了。

2.AOF不会阻塞当前命令,但是会阻塞下一个操作。

解决方法:使用不同的写回策略

1.always : 同步写回,每个子命令执行完,都会立即写回磁盘。

2.everysec : 每个命令执行完,会将命令写到AOF缓冲区,然后每隔一秒写入磁盘。

3.no: 只把命令写入到缓存区,根据操作系统的去决定何时写入磁盘。

如果接受的命令越来越多,AOF文件也会越来越大,文件过大还是会带来性能问题。日志文件过大怎么办呢?AOF重写机制!就是随着时间推移,AOF文件会有一些冗余的命令如:无效命令、过期数据的命令等等,AOF重写机制就是把它们合并为一个命令(类似批处理命令),从而达到精简压缩空间的目的。

AOF重写会阻塞嘛?AOF日志是由主线程会写的,而重写则不一样,重写过程是由后台子进程bgrewriteaof完成。

  • AOF的优点:数据的一致性和完整性更高,秒级数据丢失。

  • 缺点:相同的数据集,AOF文件体积大于RDB文件。数据恢复也比较慢。

二、RDB

以快照的形式将数据记录到磁盘上。和AOF相比,他记录的是某一个时刻的数据,而不是记录的命令。

RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来恢复数据。RDB触发机制主要有以下几种:

1.自动触发: save m n   m秒有n次修改操作 就执行 bgsave

2.手动触发: save: 同步 - 会阻塞当前的Redis服务器

                      bgsave 异步,Redis 进行执行fork操作创建子进程。

快照时,数据能修改嘛?  Redis接住操作系统的写时复制技术(copy-on-write,COW),在执行快照的同时,正常处理写操作。

虽然bgsave执行不会阻塞主线程,但是频繁执行全量快照也会带来性能开销。比如bgsave子进程需要通过fork操作从主线程创建出来,创建后不会阻塞主线程,但是创建过程是会阻塞主线程的。可以做增量快照

  • RDB的优点:与AOF相比,恢复大数据集的时候会更快,它适合大规模的数据恢复场景,如备份,全量复制等

  • 缺点:没办法做到实时持久化/秒级持久化。

Redis4.0开始支持RDB和AOF的混合持久化,就是内存快照以一定频率执行,两次快照之间,再使用AOF记录这期间的所有命令操作

如何选择RDB和AOF

  • 如果数据不能丢失,RDB和AOF混用

  • 如果只作为缓存使用,可以承受几分钟的数据丢失的话,可以只使用RDB。

  • 如果只使用AOF,优先使用everysec的写回策略。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值