1. Redis集群面试题
从Redis 3.0发布提供Redis Cluster以后,经历Redis 4.x、Redis5.x和Redis 6.x一系列版本,Redis Cluster更加成熟、稳定,推荐企业使用此种架构,通常公司也是使用此种架构。如果使用Redis Cluster集群,面试中碰到的问题有一些坑,还望注意。
- 问题一:Redis的多数据库机制,了解多少?
- 问题二:懂Redis的批量操作么?
- 问题三:Redis集群机制中,你觉得有什么不足的地方吗?
- 问题四:在Redis集群模式下,如何进行批量操作?
- 问题五:懂Redis事务么?
2. Redis高频面试题
在应用程序和MySQL数据库中建立一个中间层:Redis缓存,通过Redis缓存可以有效减少查询数据库的时间消耗,但是引入redis又有可能出现缓存穿透、缓存击穿、缓存雪崩等问题。
2.1 缓存穿透
缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。
一言以蔽之:查询Key,缓存和数据源都没有,频繁查询数据源
比如用一个不存在的用户id获取用户信息,无论论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
解决缓存穿透的方案主要有两种:
-
方案一:当查询不存在时,也将结果保存在缓存中。但是这可能会存在一种问题:大量没有查询结果的请求保存在缓存中,这时我们就可以将这些请求的key设置得更短一些;
-
方案二:提前过滤掉不合法的请求,可以使用Redis中布隆过滤器;
2.2 缓存击穿
缓存击穿:key对应的数据库存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
一言以蔽之:查询Key,缓存过期,大量并发,频繁查询数据源
业界比较常用的做法:使用互斥锁。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db(查询数据库),而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据。
String get(String key) {
String value = redis.get(key);
if (value == null) {
// 如果key不存在,则设置为1
if (redis.setnx(key_mutex, "1")) {
// 设置key的过期时间为3分钟
redis.expire(key_mutex, 3 * 60)
// 从db中加载数据,但注意:只有一个线程能进入到这里,其他线程访问的时候已有课key_mutex
value = db.get(key);
// 从数据库中加载成功,则设置对应的数据
redis.set(key, value);
redis.delete(key_mutex);
} else {
//其他线程休息50毫秒后重试
Thread.sleep(50);
get(key);
}
}
}
2.3 缓存雪崩
缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
一言以蔽之:缓存不可用(服务器重启或缓存失效),频繁查询数据源
与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。缓存正常从Redis中获取,示意图如下:
缓存失效瞬间示意图如下:
缓存失效时的雪崩效应对底层系统的冲击非常可怕!大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
2.4 Redis的命名规范是?
- 使用统一的命名规范
一般使用业务名(或数据库名)为前缀,用冒号分隔,例如,业务名:表名:id。
例如:shop:usr:msg_code(电商:用户:验证码)
- 控制key名称的长度,不要使用过长的key
在保证语义清晰的情况下,尽量减少Key的长度。有些常用单词可使用缩写,例如,user缩写为u,messages缩写为msg。
- 名称中不要包含特殊字符
包含空格、单双引号以及其他转义字符