Redis 面试题汇总,mysql 索引优化面试题常问
[](()slave 宕机
相对简单,slave 启动后会自动同步数据,增量同步。
[](()master 宕机
手动恢复
在从数据库中执行 SLAVEOFNO ONE 命令,断开主从关系并且将从库提升为主库继续服务;
将主库重新启动后,执行 SLAVEOF 命令,将其设置为其他库的从库,这时数据就能更新回来
哨兵功能自动恢复
通过 sentinel 模式启动 redis 后,自动监控 master/slave 的运行状态, 已经被集成在 redis2.4+的版本中如果 Master 异常,则会进行 Master-Slave 切换,将其中一个 Slave 作为 Master,将之前的 Master 作为 Slave
基本原理是:心跳机制+投票裁决
[](()6.缓存问题
[](()缓存穿透
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决办法
对所有可能查询的参数以 hash 形式存储,在控制层先进行校验,不符合则丢弃。还有最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。
也可以采用一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
[](()缓存雪崩
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
解决办法
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。
可以通过缓存 reload 机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存
不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀. 比如我们可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件
做二级缓存,或者双缓存策略。A1 为原始缓存,A2 为拷贝缓存,A1 失效时,可以访问 A2,A1 缓存失效时间设置为短期,A2 设置为长期。
[](()缓存击穿
缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一 key 缓存,前者则是很多 key。
[](()缓存预热
缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
缓存预热解决方案:
直接写个缓存刷新页面,上线时手工操作下;
数据量不大,可以在项目启动的时候自动进行加载;
定时刷新缓存;
[](()缓存更新
我们知道通过 expire 来设置 key 的过期时间,那么对过期的数据怎么处理呢?除了缓存服务器自带的缓存失效策略之外(Redis 默认的有 6 中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
定时去清理过期的缓存;
当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的 key 是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。
[](()缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
*降级的最终目的 Java 开源项目【ali1024.coding.net/public/P7/Java/git】 *是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
警告:有些服务在一段时间内成功率有波动(如在 95~100%之间),可以自动降级或人工降级,并发送告警;
错误:比如可用率低于 90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
[](()缓存热点 key
使用缓存 + 过期时间的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:
当前 key 是一个热点 key( 可能对应应用的热卖商品、热点新闻、热点评论等),并发量非常大。
重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次 IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
热点 key 失效后大量线程重建缓存
要解决这个问题也不是很复杂,但是不能为了解决这个问题给系统带来更多的麻烦,所以需要制定如下目标:
减少重建缓存的次数
数据尽可能一致
较少的潜在危险
1)互斥锁 (mutex key)
此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可,
下面代码使用 Redis 的 setnx 命令实现上述功能,伪代码:
String get(String key) {
//从 redis 中获取 key
String value = redis.get(key);
//如果 value 为空则开始重构缓存
if (value == null) {
//只允许一个线程重构缓存,使用 nx,并设置过期时间 ex
String mutexKey = "mutex:key" + key;
if (redis.set(mutexKey, "1", "ex 180", "nx")) {
//从数据源获取数据
value = db.get(key);
//回写 redis 并设置过期时间
redis.set(key, value, timeout);
//删除 mutexKey
redis.del(mutexKey);
} else {
//其他线程睡眠 50 秒再重试
Thread.sleep(50); get(key);
}
}
return value;
}
从 Redis 获取数据,如果值不为空,则直接返回值。
如果 set(nx 和 ex) 结果为 true,说明此时没有其他线程重建缓存,那么当前线程执行缓存构建逻辑。
如果 setnx(nx 和 ex) 结果为 false,说明此时已经有其他线程正在执行构建缓存的工作,那么当前线程将休息指定时间 (例如这里是 50 毫秒,取决于构建缓存的速度 ) 后,重新执行函数,直到获取到数据。
2)永远不过期
永远不过期”包含两层意思:
从缓存层面来看,确实没有设置过期时间,所以不会出现热点 key 过期后产生的问题,也就是“物理”不过期。
从功能层面来看,为每个 value 设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
从实战看,此方法有效杜绝了热点 key 产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》开源 取决于应用方是否容忍这种不一致。下面代码使用 Redis 进行模拟:
最后
学习视频:
大厂面试真题:
评论