redis相关面试题

    科技2024-08-01  74

    手写redis锁

    StringRedisTemplate stringRedisTemplate; String clientId= UUID.randomUUID().toString();//uuid里面的值都是不重复的 try{ Boolean result=stringRedisTemplate.opsForValue().setIfAbsent("key","value"+clientId,10, TimeUnit.SECONDS); //(2)超时时间设置为10秒,防止主机崩溃,重启等情况,redis的key无法清理,永远存在的情况, // 并且以原子的方式设置超时时间,避免还未设置超时时间就被重启,redis永远存在,其他redis永远无法设置成功 //(4)因为存在可能有极少情况下超时的情况,也不能把锁的时间无限增长,出现②那种情况等太久也不行 // 可以添加一个多线程,当线程没有停止运行,每过10s给这把锁延长时间 if(result){ //正常执行 }else{ //没有获取锁 } }finally { //(3)高并发场景下,如果有单子超过10秒以上执行完毕,那么会出现非常严重的问题, // 例如A线程超时,自动删除a锁,B线程获取了锁进入代码块并设置b锁,A线程又继续进行执行finally中代码删除锁, //这时删除的锁会会是b锁,以此类推,我们的redis锁就失效了,加完锁马上被前面一个线程删除掉了,非常可怕 //判断当前锁是不是属于当前线程,根据UUID的不重复性质,每个线程只会删除属于他自己的锁 if(stringRedisTemplate.opsForValue().get("key").indexOf(clientId)!=-1) stringRedisTemplate.delete("key"); //(1)在finally中删除rediskey,防止抛出异常情况下rediskey没有被清理 }

    ①在finally中删除rediskey,防止抛出异常情况下rediskey没有被清理; ②超时时间设置为10秒,防止主机崩溃,重启等情况,redis的key无法清理,永远存在的情况,并且以原子的方式设置超时时间,避免还未设置超时时间就被重启,redis永远存在,其他redis永远无法设置成功 ③高并发场景下,如果有单子超过10秒以上执行完毕,那么会出现非常严重的问题, 例子:A线程超时,自动删除a锁,B线程获取了锁进入代码块并设置b锁,A线程又继续进行执行finally中代码删除锁,这时删除的锁会会是b锁,以此类推,我们的redis锁就失效了,加完锁马上被前面一个线程删除掉了; 方案:将UUID设置到value中,确保每一个线程删除的锁,是自己线程的锁; ④因为存在可能有极少情况下超时的情况,也不能把锁的时间无限增长,出现②那种情况等太久也不行 方案: 可以添加一个多线程,当线程没有停止运行,每过一段时间给这把锁延长一定的时间 Redisson暂时不深究、 Redis数据类型 1、最常用的字符串Map jedis.set(“key”,“value”); 2、list数据类型Map<String,List> //清空集合值 jedis.del(“listkey01”,“listkey02”); //设置List集合值,左侧插入 jedis.lpush(“listkey01”,“张三”,“李四”,“王五”); //设置List集合值,右侧插入 jedis.rpush(“listkey02”,“北京”,“上海”,“广州”); //获取List集合值 List listkey01 = jedis.lrange(“listkey01”, 0, -1); for (String item:listkey01){ System.out.println(item); } //更改List集合数据 jedis.lset(“listkey01”,0,“赵六”); 3、hash数据类型Map<String,Map<Object ,Object >> //设置Map Map<String,String> map=new HashMap<>(); map.put(“name”,“张三”); map.put(“age”,“18”); jedis.hmset(“mapkey01”,map); //获取值 List value = jedis.hmget(“mapkey01”, “name”, “age”); for (String item:value){ System.out.println(item); } 4、set数据类型Map<String,Set<Object ,Object >> //设置Set集合 jedis.sadd(“setkey01”,“张三”,“李四”,“王五”); //获取 Set setkey01 = jedis.smembers(“setkey01”); for (String item:setkey01){ System.out.println(item); } 5、zset(sortset)数据类型 //设置SortedSet集合 jedis.zadd(“setkey02”,1,“北京”); jedis.zadd(“setkey02”,1,“上海”); jedis.zadd(“setkey02”,1,“广州”); //获取SortedSet Set setkey02 = jedis.zrange(“setkey02”, 0, -1); for (String item:setkey02){ System.out.println(item); } 新增数据类型有三种,这边说有用过的一种 Redis HyperLogLog: 作用:根据用户名(必须是唯一值,会自动去重)去存储用户的大致数量,实时更新数量,而不需要去通过数据库的操作去去重统计用户数量 原理:伯努利试验; 伯努利试验:每一次抛硬币出现正面的概率为1/2;那么连续出现两次正面就是1/4;我们也可以这样理解,当我们抛硬币遇到0就视为一轮,这样我们可以得出这样的结论,需要通过两轮可以得到一个硬币正面的结果,需要通过4轮才能得到两次硬币为正面(最大值)的结果;以此类推,我们只需要知道我们最正面结果的最大值是多少就能得知我们进行了几轮的抛掷硬币; 利用到Redis HyperLogLog中:我们使用key产生的hash值可以算作一个随机数,也就是把hash二进制值视作一轮抛掷硬币,取出hash二进制值中从第一个数字开始连续为1的数有几个就能反推出,我们有几个key存进来了;这样我们就可以退出用户数量大致为多少; HyperLogLog中也把hash值分成很多段,进行多次的伯努利试验运算;再通过某种调和函数(不想深入了)进行调和平均数运算;误差大概1.1%; 优点:只要12K的存储空间就能存储亿级的数量; 缺点:只是一个预估值,存在偏差; Redis 持久化机制 Rdb:保存快照 分为save和bgsave两种,save保存快照的时候,redis不能处理其他redis命令;bgsave则没关系是异步保存快照 优点:全量备份,由redis子进程进行操作,不会影响到主进程,恢复速度快 缺点:在快照持久化过程中修改的数据会丢失 Aof:每次操作都会保存数据到硬盘中一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作 优点:更好的保存数据,最多丢失1秒钟的数据 缺点:对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大,以前AOF发生过没有完全恢复全部备份的情况

    一般这两个都是结合使用的; 缓存雪崩 概念:原有缓存失效。新缓存还没有生成的期间; 例子:设置好的缓存过期时间,在某个时间点全部过期了,导致大量查询数据库的情况出现; 解决方案:给这些访问加锁,或者限流的形式不让这些数据一次性大量的访问数据库; 缓存穿透 概念:数据库不存在的数据,缓存中自然也没有,这种无效查询过多都会去查询数据库(缓存命中率); 解决方案:布隆过滤器; 布隆过滤器基本概念:一个很长的二进制位数组,把那些查询失败的key做多次hash运算二进制数存入这个二进制数组中(1存进去);当我们再次查询时,把要查询的key做相同的hash运算去这个二进制数组中查询是否每个对应的位置上都有1,如果有直接返回失败查询不到,这样就不需要重复去数据库中查询了; 优点:以极小的空间,时间代价存储大量的数据; 缺定:存在误差; 缓存预热 把一些常用的数据,加载到缓存中,等要用的时候直接去缓存中取,而不是用户再去数据库中查询完,再放到缓存里面; 解决方案:1、直接写个缓存刷新页面,上线时手工操作下; 2、数据量不大,可以在项目启动的时候自动进行加载; 3、定时刷新缓存; 缓存更新 (1)定时去清理过期的缓存;(维护大量缓存的key是比较麻烦的) (2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。(用户请求过来都要判断缓存失效,逻辑相对比较复杂) 缓存降级 当访问量剧增、服务出现问题,保证核心逻辑的缓存,把非核心逻辑的缓存进行降级处理;

    Processed: 0.016, SQL: 8