面试官经常问的Redis高频题目,我整理出来了,附参考答案,欢迎大家一起交流、补充和指正。
1、Redis为什么这么快?
答:(1)基于内存,数据存在内存中。
(2)数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
(3)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题。(利用队列技术将并发访问变为串行访问)
(4)使用多路I/O复用模型,非阻塞IO;
(5)使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
2、Redis 的数据类型
答:Redis 支持五种数据类型:string(字符串),hash(哈希),list( 列表),set(集合) 及sorted set(有序集合)。
3、Redis的持久化机制
答:Redis 的持久化机制有两种,第一种是RDB快照,第二种是 AOF 日志。
快照是一次全量备份,AOF 日志是连续的增量备份。
快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。
(1)RDB的优缺点
优点:
RDB文件小,非常适合定时备份,用于灾难恢复;Redis加载RDB文件的速度比AOF快很多,因为RDB文件中直接存储的时内存数据,而AOF文件中存储的是一条条命令,需要重演命令。
缺点:
RDB无法做到实时持久化,若在两次bgsave间宕机,则会丢失区间(分钟级)的增量数据,不适用于实时性要求较高的场景;RDB的cow机制中,fork子进程属于重量级操作,并且会阻塞redis主进程;存在老版本的Redis不兼容新版本RDB格式文件的问题。
(2)AOF的优缺点
优点:
AOF只是追加写日志文件,对服务器性能影响较小,速度比RDB要快,消耗的内存较少。
缺点:
AOF方式生成的日志文件太大,需要不断AOF重写,进行瘦身;即使经过AOF重写瘦身,由于文件是文本文件,文件体积较大(相比于RDB的二进制文件);AOF重演命令式的恢复数据,速度显然比RDB要慢。
服务主从数据怎么交互的?
RDB做镜像全量持久化,AOF做增量持久化。因为RDB会耗费较⻓时间,不够实时,在停机的时候会导致⼤量丢失数据,所以需要AOF来配合使⽤。在redis实例重启时,会使⽤RDB持久化⽂件重新构建内存,再使⽤AOF重放近期的操作指令来实现完整恢复重启之前的状态。
(详情参考博文:https://yunfan.blog.csdn.net/article/details/108267493)
4、 缓存穿透、缓存击穿、缓存雪崩的区别和解决方案
答:缓存穿透
访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉。
解决方案
(1)采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;
(2)拦截器,接⼝层增加校验,如给id做校验,id<=0的直接拦截。
(3)从cache和db都取不到,可以将key-value写为key-null,设置较短过期时间,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。
缓存击穿
一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
解决方案
(1)设置热点数据永远不过期。
(2)加互斥锁。
缓存雪崩
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
(1)缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
(2)如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
(3)设置热点数据永远不过期。
5、Redis过期键的删除策略
答:redis采用的是定期删除+惰性删除策略。
(1)为什么不用定时删除策略?
定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.
(2)定期删除+惰性删除是如何工作的呢?
定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
(3)采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。
6、Redis内存淘汰策略
策略 | 说明 |
---|---|
volatile-lru | 从已设置过期时间的数据集( server.db[i].expires)中挑选最近最少使用的数据淘汰; |
olatile-ttl | 从已设置过期时间的数据集( server.db[i].expires)中挑选将要过期的数据淘汰; |
volatile-random | 从已设置过期时间的数据集( server.db[i].expires)中任意选择数据淘汰; |
allkeys-lru | 从数据集( server.db[i].dict)中挑选最近最少使用的数据淘汰; |
allkeys-random | 从数据集( server.db[i].dict)中任意选择数据淘汰 |
no-enviction | 禁止驱逐数据 |
注意这里的6种机制,volatile 和allkeys 规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据, 后面的lru、ttl 以及random 是三种不同的淘汰策略,再加上一种no-enviction 永不回收的策略。
7、数据不一致问题及其解决方案
答:问题:先操作缓存,在写数据库成功之前,如果有读请求发生,可能导致旧数据入缓存,引发数据不一致。
解决方案
方案1:Redis设置key的过期时间。
方案2:采用延时双删策略。
(1)先淘汰缓存
(2)再写数据库(这两步和原来一样)
(3)休眠n毫秒,再次淘汰缓存
这么做,可以将n毫秒内所造成的缓存脏数据,再次删除。(为何是n毫秒?需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。当然这种策略还要考虑redis和数据库主从同步的耗时。)
(详情参考博文:https://blog.csdn.net/sinat _27933301/article/details/88381333)
8、Redis分布式锁原理
答:synchronized或者Lock都是线程锁,对同一个JVM进程内的多个线程有效。因为锁的本质是内存中存放一个标记,记录获取锁的线程是谁,这个标记对每个线程都可见。然而我们启动的多个订单服务,就是多个JVM,内存中的锁显然是不共享的,每个JVM进程都有自己的锁,自然无法保证线程的互斥了,这个时候我们就需要使用到分布式锁了。
最常见的解决方案是Redis实现,实现分布式锁要满足3点:多进程可见,互斥,可重入。
原理:
- 获取锁的时候,使用setnx(SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0)加锁,锁的value值为一个随机生成的UUID,在释放锁的时候进行判断。并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁。
- 获取锁的时候调用setnx,如果返回0,则该锁正在被别人使用,返回1则成功获取锁。 还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
- 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
SET lock_key random_value NX PX 5000
值得注意的是:random_value:是客户端生成的唯一的字符串。NX:代表只在键不存在时,才对键进行设置操作。PX 5000:设置键的过期时间为5000毫秒。
这样,如果上面的命令执行成功,则证明客户端获取到了锁。
解锁:解锁的过程就是将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉。这时候random_value的作用就体现出来。
为了保证解锁操作的原子性,我们用LUA脚本完成这一操作。先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功。
(详情参考博文:https://www.jianshu.com/p/47fd7f86c848)
9、如何解决Redis大key问题?
答:由于Redis主线程为单线程模型,大key也会带来一些问题,如:
- 集群模式在slot分片均匀情况下,会出现数据和查询倾斜情况,部分有大key的Redis节点占用内存多,QPS高。
- 大key相关的删除或者自动过期时,会出现qps突降或者突升的情况,极端情况下,会造成主从复制异常,Redis服务阻塞无法响应请求。
解决方案:
- redis-rdb-tools工具。redis实例上执行bgsave,然后对dump出来的rdb文件进行分析,找到其中的大KEY。
- redis-cli --bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。
10、热key产生原因和后果以及怎么解决?
11、Redis 常见性能问题和解决方案?
答:(1)Master 最好不要写内存快照,如果Master 写内存快照,save 命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务;
(2)如果数据比较重要, 某个Slave 开启AOF 备份数据,策略设置为每秒同步;
(3)为了主从复制的速度和连接的稳定性, Master 和Slave 最好在同一个局域网;
(4)尽量避免在压力很大的主库上增加从;
(5)主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1<- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现Slave 对Master的替换。如果Master 挂了,可以立刻启用Slave1 做Master,其他不变。
12、Redis 的同步机制了解么?
答:Redis 可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb 文件全量同步到复制节点,复制节点接受完成后将rdb 镜像加载到内存。加载完成后, 再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
13、Redis主从复制原理
答:https://www.cnblogs.com/daofaziran/p/10978628.html
14、是否使用过Redis 集群,集群的原理是什么?
答:(1)Redis Sentinal(哨兵模式)着眼于高可用,在master 宕机时会自动将slave 提升为master,继续提供服务。
(2)Redis Cluster(集群)着眼于扩展性,在单个redis 内存不足时, 使用Cluster 进行分片存储。
15、Redis 集群方案什么情况下会导致整个集群不可用?
答: 有A, B,C 三个节点的集群,在没有复制模型的情况下,如果节点B 失败了,那么整个集群就会以为缺少5501-11000 这个范围的槽而不可用。
16、Redis 集群的主从复制模型是怎样的?
答: 为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用, 所以集群使用了主从复制模型,每个节点都会有N-1 个复制品。
17、说说Redis 哈希槽的概念?
答: Redis 集群没有使用一致性hash,而是引入了哈希槽的概念, Redis 集群有16384 个哈希槽,每个key 通过CRC16 校验后对16384 取模来决定放置哪个槽,集群的每个节点负责一部分hash 槽。
18、Key的过期时间和永久有效分别怎么设置?
答: EXPIRE 和PERSIST 命令。
19、Redis 如何做内存优化?
答: 尽可能使用散列表(hashes), 散列表( 是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web 系统中有一个用户对象,不要为这个用户的名称, 姓氏,邮箱, 密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面。
20、Redis 的内存用完了会发生什么?
答:如果达到设置的上限,Redis 的写命令会返回错误信息( 但是读命令还可以正常返回。)或者你可以将Redis 当缓存来使用配置淘汰机制,当Redis 达到内存上限时会冲刷掉旧的内容。
21、Redis 做异步队列
答:一般使用list 结构作为队列,rpush 生产消息,lpop 消费消息。当lpop 没有消息的时候, 要适当sleep 一会再重试。
list 还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。如果生产一次消费多次,使用pub/sub 主题订阅者模式, 可以实现1:N 的消息队列。pub/sub 有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如RabbitMQ等。
22、假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
答:使⽤keys指令可以扫出指定模式的key列表。
如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
redis的单线程的。keys指令会导致线程阻塞⼀段时间,线上服务会停顿,直到指令执⾏完毕,服务才能恢复。这个时候可以使⽤scan指令,scan指令可以⽆阻塞的提取出指定模式的key列表,但是会有⼀定的重复概率,在客户端做⼀次去重就可以了,但是整体所花费的时间会⽐直接⽤keys指令⻓。
23、布隆过滤器的原理?
答:当⼀个元素被加⼊集合时,通过K个散列函数将这个元素映射成⼀个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(⼤约)知道集合中有没有它了:如果这些点有任何⼀个0,则被检元素⼀定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
缺点:bloom filter之所以能做到在时间和空间上的效率⽐较⾼,是因为牺牲了判断的准确率、删除的便利性。
- 存在误判,可能要查到的元素并没有在容器中,但是hash之后得到的k个位置上值都是1。如果bloom filter中存储的是⿊名单,那么可以通过建⽴⼀个⽩名单来存储可能会误判的元素。
- 删除困难。⼀个放⼊容器的元素映射到bit数组的k个位置上是1,删除的时候不能简单的直接置为0,可能会影响其他元素的判断。可以采⽤Counting Bloom Filter。