Redis面试问题
一、Redis简介
Redis是一个key-vakue存储系统,支持五种存储结构:String,Hash,List,Set,Sorted Set。与memcached一样为了保证效率,将数据储存在内存中。区别的是Redis会周期的把更新的数据写入磁盘或者把修改操作写入追加的文件中,并在此基础上实现了主从同步。
二、Redis详细介绍
2.1 Redis每种数据类型的使用场景
- String:最常规的set/get 操作,value可以说string也可以是数字。一般做一些复杂的计数功能的缓存。
- hash:这里value放的是结构化的对象,比较方便的就是操作其中的某个字段。
- list:使用List的数据结构,可以做简单的消息队列功能。另外还可以利用lrang命令,做基于redis的分页功能。
- set:因为set堆放的是一些不重复值的集合。所以可以做全局去重的功能。可以利用交集,并集,差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
- sorted set:sorted set 多了一个权重参数score,集合中的元素能够按score进行排序。可以做排行榜应用,取Top N操作。Sorted set 可以用来做延时任务,最后一个应用就是可以做范围查找。
2.2 Redis的优缺点
使用Redis主要考虑两个角度(优点):性能和并发。
(1)我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合价格运行结果放入缓存,这样,后面的请求就去缓存中读取,使得请求能够迅速响应。
(2)在大并发的情况下,所有请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到Redis,而不是直接访问数据库。
使用Redis也有一些缺点:
(1)缓存和数据库双写写一致性问题;
(2)缓存雪崩问题;
(3)缓存击穿问题;
(4)缓存的并发竞争问题
2.3 Redis的速度为什么这么快?
(1)纯内存操作
(2)单线程操作,避免了频繁的上下文切换
(3)采用了非阻塞IO多路复用机制
2.4 redis的过期策略以及内存淘汰机制
采用定期删除和惰性删除的策略。
定期删除,Redis默认没个100ms检查是否有过期的key需要删除,有过去则删除,需要说明的是,Redis不是每隔100ms将所有的key检查一次,而是随机抽查进行检查(如果每隔100ms将全部key积蓄检查,Redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除就派上用场了。大概我们获取一个key的时候,Redis会检查一下,这个key有没有过期,过期的话就删除了。
采用定期删除+惰性删除也会有问题。如果定期删除没有删除key,然后也没有即时请求key 也就是惰性杀出没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。
在redis.conf中有一行配置
#maxmemory-policy volatile-lru
该配置就是配置内存淘汰策略的。
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。不推荐。
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用。
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。不推荐。
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐
2.5 Redis和数据库双写一致性问题
分析:一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。
回答:首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。
2.6 如何应对缓存穿透和缓存雪崩问题
缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
解决方案:
- 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
- 采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
- 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。
缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。
解决方案:
- 给缓存的失效时间,加上一个随机值,避免集体失效。
- 使用互斥锁,但是该方案吞吐量明显下降了。
- 双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操。
2.7 如何解决redis的并发竞争key问题
分析:这个问题大致就是,同时有多个子系统去set一个key。这个时候要注意什么呢?大家思考过么。回答:如下所示
- 如果对这个key操作,不要求顺序
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
- 如果对这个key操作,要求顺序
假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.
期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下
系统A key 1 {valueA 3:00}
系统B key 1 {valueB 3:05}
系统C key 1 {valueC 3:10}
那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。
2.8 Redis的持久化
Redis 提供了多种不同级别的持久化方式:
- RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
- AOF (Append-only file)持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。
- Redis 还可以同时使用 AOF 持久化和 RDB 持久化。 在这种情况下, 当 Redis 重启时, 它会优先使用 AOF 文件来还原数据集, 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。
你甚至可以关闭持久化功能,让数据只在服务器运行时存在。
RDB
工作原理:每隔一定时间给内存照一个快照,将内存中的数据写入文件(rdb文件)
配置参数:redis.conf文件
RDB示例测试:可以使用redis-benchmark进行压力测试
./bin/redis-benchmark -n 100000 表示执行100000个操作
RDB的缺点:
在两次快照之间,如果发生断电,数据会丢失
举例:在生成rdb后,插入新值。突然断电,数据可能会丢失
AOF:通过日志的方式
工作原理:记录操作的命令
配置参数:
什么是AOF的重写:rewrite
将内存中的key逆向生成命令,如同一个key,反复操作了100次,aof文件会记录100次操作,这样会导致AOF文件过大
例如:
set age 0
incr age
incr age
… 100次
最后 age的值是100
经过重写后,直接执行: set age 100
可以通过观察aof日志文件的大小
3、Redis持久化注意的问题
- RDB恢复的速度快
- 如果RDB和AOF都有,默认使用AOF进行恢复