Redis面试常问此类问题。我们就认真的分析一下。

缓存三连问

缓存穿透(没有key):

  • 问题:
    • 用户请求读取redis中没有数据,又去找数据库中,数据库也没有。现在的情况就相当于直接访问redis又访问数据库,一旦请求量变大,就数据库扛不住,就挂了。
  • 解决办法:
    1. 在用户的请求,对请求参数判断,不合法,就直接return
    2. 数据库找不到,那就再redis弄个虚假的数据,设置TTL。 缺点:占用大量内存空间存储
    3. 使用布隆过滤器。 缺点:不保证100%没问题

缓存击穿(一个热点key失效):

  • 用户访问一个热点的redis的key,但是这个key突然失效,失效瞬间,大量请求数据库就打进来了,直接打崩数据库。
  • 解决办法:
    1. 此热点key设置永不过期,如需要业务判定时,建议存储新对象(属性有Object data,String expireTime)。原数据存放在data中
    2. (互斥)锁。从数据库读取值,插入key的时候,使用Redisson只允许一个线程读取即可!(也可以使用setnx命令实现,此命令读取的时候会判断key是否存在,如果锁存在才会去插入key,即可避免。但是不建议使用此基础版本的互斥锁)

缓存雪崩(多个key失效):

  • 问题:
    • 用户大量的请求到redis中读取数据,但是redis中的key,设置了相同的失效时间,一旦到了失效时间,大量的key失效,请求直接打在数据库中,将数据库打崩。
  • 解决办法:
    1. 设置不同key的失效时间,避免同时key失效,瞬间数据库访问大量并发。
    2. 设置不失效,一旦数据户有变动,就刷新缓存。

如何避免 Redis 缓存崩溃的三个问题?

  • 事前:部署集群
  • 事中:使用本地 ehcache缓存 + hystrix 限流、降级
  • 事后:redis持久化存储(RDB+AOF),一旦重启,自动读取硬盘上的数据,快速恢复。

什么是RDB AOF?

  • RDB半持久化,通过默认配置,设置时间进行持久化新的文件,然后完成替换老的文件存储。会存在数据丢失问题,因为设置60s存储一次,如果存储时间没到,就挂了,那么上一次的存储时间到崩溃时间的数据就会丢失

Redis RDB 配置 在redis.conf文件中

RDB 触发机制配置

RDB会在主动退出时,执行一次save!如果需要额外的触发机制,需要下面配置

# save 可以多设置几个
# 900秒内,如果1个key被修改,就会执行bgsave
save 900 1

# 300秒内,如果10个key被修改,就会执行bgsave
save 300 10

# 60秒内,如果1000个key被修改,就会执行bgsave
save 60 10000

如果彻底禁用RDB 请使用,并注释掉其他save配置
save ""

RDB 其他配置

# 是否开启压缩,不开启on,开启yes。不建议开启,压缩会额外使用CPU
rdbcompression on

# 指定RDB文件名称,如果变更名字,重启不会恢复原来的dump文件中的数据,你可以改原来的文件名后再重启
dbfilename dump.rdb

# 文件保存的路径
dir ./

主动使用RDB保存文件

  • 主进程执行:通过redis-cli,执行save命令! 此方式会阻塞其他redis命令执行
  • 推荐 子线程进行:通过redis-cli,执行bgsave命令!

Redis的bgsave命令执行基本流程

先了解页表概念:是一种特殊的数据结构,放在系统空间的页表区,存放逻辑页(逻辑地址)与物理页帧(物理内存内置)的对应关系。 每一个进程都拥有一个自己的页表。页表可被复制的好处:可以实现:进程之间直接共享真实物理内存。不用完全复制一份真实的物理内容给新的进程使用!

  1. fork过程:主进程得到子进程,子进程共享主进程的(页表)内存数据了。
  2. redis完成fork后:读取内存数据,开始保存到磁盘了!将来新的dump.rdb替换旧得durm.rdb文件

注意fork使用copy-on-write技术,但bgsave执行过程中,主线程得到其他命令会有不同的操作

  • 主进程执行读操作,访问共享内容数据
  • 主进程写操作:先拷贝一份共享数据(页表),在新拷贝的内存空间执行写操作。此时内存占用就会翻倍了。fork使用完毕,释放原来内存!

AOFAppend Only File

Redis处理每一个写命令,都会记录在AOF文件中,可以看做写命令的日志文件

AOF原理就是,读取命令,再次执行!

配置文件

# 是否开启AOF功能,默认是no 不开启
appendonly yes

# AOF文件名称
appendfilename "appendonly.aof"

配置:AOF执行频率

# 每一次执行都写一次命令,立刻记录。简称:同步刷盘
appendfsync always

# 写命令先进入AOF缓冲区,然后每秒写入进AOF文件,默认方式。简称:每秒刷盘
appendfsync everysec

# 写命令先放入AOF缓冲区,由操作系统决定缓冲器内容写入磁盘。
appendfsync no

AOF文件会比较大,会保存所有命令,但是RDB文件仅仅保存数据,所以会更小。

bgrewriteaod命令:如果aof命令有set name zhangsan、set name lisi。那么在数据恢复时,仅仅会执行set name lisi。也就是保存文件最后一个数据。

配置AOF自动触发机制

# AOF文件比上次文件,增长超过多少半分 触发重写
auto-aof-rewrite-percentage 100

# AOP文件体积最小多大开始触发重写
auto-aof-rewrite-min-size 64mb

不管怎样,AOF等价RDB文件会更大!

RDB与AOF区别与使用

  • RDB与AOF同时存在:优先听AOF的。
  • 但是RDB性能更好,但是存在丢失的概率更大
  • AOF持续性的IO性能 差一点,但是丢失数据的概率更小
  • 官方建议同时存在:RDB做备用,AOF重写时间长一点,减少性能消耗。
  • RDB恢复速度快,AOF要执行后,才能算恢复数据。
  • RDB存在的意义是:可以做异地备份,方式数据因意外丢失!

Redis键过期的策略有(常用)?

  • 定时删除:设置个过期时间,到时间自动删除
  • 惰性删除:如果过期了不管,一旦调用的时候,再判断,过期了再删除
  • 定期删除:(外部手段干扰)定时间对数据库进行检查,删除里面的过期值

Redis 的超卖问题 & 简单分布式锁

单机部署的项目,简单的加jvm锁(也就是传说中的(synchronized)就行了。但是目前是分布式项目,加锁只能保证单机服务问题,就无法保证多机的数据安全问题。我们就需要一个 分布式锁

我们采用redis 来做分布式锁,当然分布式锁有很多方式,redis比较方便一点。

调用的是Redis的 API setnx key value 也就是K 不在 在设置V,K在,不设置V

setnx key value   
# 当key 不存在的时候 才会set 值
在 stringRedisTemplate 中 调用的是.opsForValue().setIfAbsent("key",value); 返回一个布尔值

如果单纯这样涉及,就会有所谓的死锁情况,比如项目加锁后,停电,嘎,没有释放锁,锁的key永久都在,所以务必加上key的失效时间

在刁钻一点
加上失效时间 如果写法不对也不能保证100%的没问题

流程图图下

有个问题,服务器挂了,标记未删除

如果 在没有删除 标记之前,服务器挂了,重启后,所有的 请求都去设置标记,但每次都是获得,已经存在标记了,就会带来新的问题。

我们需要对一个key 设置一下 超时时间

// 格式:   stringRedisTemplate.expire(key,value,time,时间单位)
// 设置 10 秒 失效时间
stringRedisTemplate.expire("flag",10,TimeUtils.Second);

上面的也有问题,如果 添加key 之后 在设置失效时间,就会带来新的问题,添加的时候,没设置失效之前挂了。我们就需要在设置key的时候,同时 指定时间

    超高并发依旧有问题!!!!

    超高并发依旧有问题!!!!

    超高并发依旧有问题!!!!


        // 添加标记10秒失效
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("key",value,10, TimeUtils.Second);

        try {
            if (!result) {
                System.out.println();
            } else {
                System.out.println();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 删除key标记
            stringRedisTemplate.delete("key");
        }

还有个问题 如果 redis 设置key后 网络超时了,10秒后,网络正常了 或者 业务 响应了 10秒 即 分布式锁永久失效(自己加的锁,被别人释放了)

我们设置标记的value 为我们当前操作的 UUID,我们删除的时候,都要去读取匹配 我们的UUID ,匹配成功,我们才取删除!

还有问题 查到自己的UUID,匹配成功,去删除的时候,10秒失效时间到了,其他线程,一样拿到线程,霍霍数据 (锁续命问题)

开辟新线程,去检验 主线程 是否持有锁,如果有所,重新设置 锁时间,手写太要命,用Redis 官方推荐的 Redisson

Redissonhttps://github.com/redisson/redisson

他是对 jedis 增强,虽然没有直接关系!!! redis son 即 redis的儿子 ,被官方承认的!

用途:

Redisson 里的lock 机制,是开启定时器 (根据存储键的时间/3),重新设置 redis 中键的失效时间并嵌套调用

Redisson锁的可重入性

这是Redisson锁的数据类型以及数据

特别说明

由于Redis才有主从复制的情况,切换子节点作为主节点时,难免出现主从复制延迟情况,进而造成锁失效。如果Redis是单主节点,就不会有这个情况!

Redisson使用教程

Maven依赖

        <!-- Redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.15.0</version>
        </dependency>

注入一个 Bean

    @Bean
    public Redisson redisson() {
        Config config = new Config();
        // Redis是单机服务
        // 如果是集群服务请使用config.useClusterServers().addNodeAddress("redis://127.0.0.1:7181","rediss://127.0.0.1:7182");
        // 如果是云托管redis自己去redisson官网查询用法即可!
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }

快速使用 伪代码,请查看下方更为详尽的代码!

    @Autowired
    Redisson redisson;

    @Test
    public void Redisson入门Demo() {
        String lockKey = "自定义一个key(一般是用户Id锁自己的操作)";
        RLock redissonLock = redisson.getLock(lockKey);
        boolean doLockRes = redissonLock.tryLock();
        if (doLockRes) {
            try {
                // TODO 执行业务操作
            } finally {
                if (doLockRes) {
                    redissonLock.unlock();
                }
            }
        } else {
            System.out.println("加锁失败!");
        }
    }

Redisson 更详细的代码

    @Autowired
    private Redisson redisson;

    @SneakyThrows
    @Override
    public void XXXXX(String[] args) {
        String lockKey = "自定义一个key(一般是用户Id锁自己的操作)";
        RLock redissonLock = redisson.getLock(lockKey);
        boolean flag = false;
        //todo 使用Redisson 获得一个5秒的分布式锁,如果加载try里,拿锁出现异常,执行释放锁,就会释放了寂寞
        flag = redissonLock.tryLock(5, TimeUnit.SECONDS);
        try {
            if (flag) {
                // TODO 执行自己的业务逻辑 先查询订单数量,再下单。
            } else {
                System.out.println("没有加锁成功,业务不执行执行!!");
            }
        } catch (Exception e) {
            // todo 自己做自己业务的异常处理
        } finally {
            // todo 一旦加锁成功,方法结束必须要释放锁
            if (flag) {
                redissonLock.unlock();
            }
        }
    }

Redis 主从架构锁失效问题

Redlock

Redis的多级缓存一致性问题

Redis与数据库缓存不一致问题

特殊说明:
上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取最新全部资料 ❤