认识Redis#
1. 什么是Redis?#
Redis是一个开源的内存数据结构存储系统,支持多种数据结构(如字符串、哈希、列表、集合等),常用于缓存、分布式锁。它通过将数据存储在内存中实现高速读写,并支持持久化到磁盘,确保数据安全。Redis还提供主从复制、事务和Lua脚本等功能,适用于高并发场景。
2. 使用 Redis 有哪些好处?#
- 读取速度快,因为数据存在内存中,所以数据获取快;
- 支持多种数据类型,包括字符串、列表、集合、有序集合、哈希等;
- 还拥有其他丰富的功能,队列、主从复制、集群、数据持久化等功能。
3. 为什么MySQL做存储,Redis做缓存?#
MySQL是数据库系统,对于数据的操作需要访问磁盘,而将数据放在Redis中,需要访问就可以直接从内存获取,避免磁盘I/O,提高操作的速度,使用Redis+MySQL结合的方式可以有效提高系统QPS。
4. Redis 常用的业务场景有哪些?#
Redis常用业务场景包括缓存、计数器、分布式锁、排行榜等,其中缓存和分布式锁是最核心和广泛的应用场景。
Redis数据对象#
5. Redis 的数据类型有哪些?#
String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合) 字符串、map、链表、哈希表、有序集合
6. Redis有哪些底层数据结构?#

String#
7. Set一个已有的数据会发生什么?#
Set一个已有数据会覆盖原有的值,同时会覆盖或者擦除键的过期时间
8. 浮点型在String是用什么表示?#
要将一个浮点数放入字符串对象里面,需要先将这个浮点数转换成字符串值,然后再保存转换所得的字符串值。
浮点数在字符串对象里面是用字符串值表示的,用Raw还是Embstr编码,取决于转换后字符串的长度
9. String可以有多大?#
一个Redis字符串最大为512MB
10. Redis字符串是怎么实现的?#
Redis字符串底层是String对象,String对象有三种编码方式:INT型、EMBSTR型、RAW型。如果是存一个整型,可以用long表示的整数就以INT编码存储;如果存字符串,当字符串长度小于等于一个阈值,使用EMBSTR编码;字符串大于阈值,则用RAW编码。在我用的5.0.5版本中阈值是44。
11. 为什么EMBSTR的阈值是44?#
Redis的字符串对象是由redisObject和sdshdr这两部分组成
redisObject 16字节,sdshdr中已分配、已申请、标记三个字段固定占了3个字节,’\0’占了一个字节,能存放的数据就是 64-(16+4) = 44。
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len;
uint8_t alloc;
unsigned char flags;
char buf[];
};
12. 你知道为什么EMBSTR曾经的阈值是39吗?#
struct sdshdr {
unsigned int len;
unsigned int free;
char buf[];
};
13. SDS有什么用?#
1.SDS包含已使用容量字段,O(1)时间快速返回有字符串长度,相比之下,C原生字符串需要O(n)。
2.有预留空间,在扩容时如果预留空间足够,就不用再重新分配内存,节约性能,缩容时也可以将减少的空间先保留下来,后续可以再使用。
3.不再以‘\0’作为判断标准,二进制安全,可以很方便地存储一些二进制数据。
List#
14. List是完全先入先出吗?#
List是双端操作对象,所以不是完全的先入先出,List也可以后入先出。
15. List对象底层编码方式是什么?#
在3.2版本之前,List对象的编码是ZIPLIST和LINKEDLIST。ZIPLIST适用于元素数量较少、且元素都较短的情况,否则用LINKEDLIST。
3.2版本之后,List对象的编码全部由QUICKLIST实现。QUICKLIST是一个压缩列表组成的双向链表,结合了ZIPLIST和LINKEDLIST两者的优点
在后面比较新的版本(Redis 7.0)中ZIPLIST优化为了LISTPACK。
16. ZIPLIST是怎么压缩数据的?#
连续空间的双端链表
对于一个普通的双向链表,链表中每一项都占用独立的一块内存,各项之间用地址指针(或引用)连接起来。这种方式会带来大量的内存碎片,而且地址指针也会占用额外的内存。而ziplist却是将表中每一项存放在前后连续的地址空间内,一个ziplist整体占用一大块内存。
17. ZIPLIST下List可以从后往前遍历吗?#
可以,List是双端数据结构,无论哪种底层编码,都需要能支持从后往前遍历。 每个节点都保存了上一个节点的长度。
18. 在ZIPLIST数据结构下,查询节点个数的时间复杂度是多少?#
O(1) zllen,记录压缩列表包含的节点数量;也就说如果节点数量超过65535,就失效了,此时只能通过O(n)复杂度的遍历来查节点总数。
19. LINKEDLIST编码下,查询节点个数的时间复杂度是多少?#
LINKEDLIST编码下,查询节点个数的时间复杂度是O(1)。因为LINKEDLIST的表头结构中定义了链表所包含节点数量的字段len。
SET#
20. Set编码方式?#
(IntSet)(Hashtable) Set使用整数集合和字典作为底层编码,当元素都是整数同时元素个数不超过512个,会使用整数集合编码,否则使用字典编码。
21. Set是有序的吗?#
Set的底层实现是整数集合或字典,前者是有序的,后者是无序的。整体来看,但是不应该依赖SET的顺序,业务使用适合始终应该按无序来用
22. Set为什么要用两种编码方式?#
使用整数集合编码,更加的节约内存。元素数量变大会使用字典编码,查找元素的速度会更快。
Hash#
23. Hash的编码方式是什么?#
一个是ZIPLIST,一个是HashTable。ZIPLIST适用于元素较少且单个元素长度较小的情况,其它情况使用HashTable。
24. Hash查找某个key的平均时间复杂度是多少?#
Hash有两种底层结构,ZIPLIST时是O(N),HashTable则是O(1)
25. Redis中HashTable查找元素总数的平均时间复杂度是多少?#
HashTable查找元素总数的平均时间复杂度是O(1),因为HashTable的表头结构中有储存键值对数量的字段,这个字段我记得叫used。
26. 一个数据在HashTable中的存储位置,是怎么计算的?#
首先会通过哈希函数计算出key的哈希值,然后与哈希掩码做与运算得到索引值,索引值就是这个数据在HashTable中的存储位置。
27. HashTable怎么扩容?#
首先程序会为HashTable的1号表分配空间,空间大小是第一个大于等于0号表大小*2的2 ^ n。
在rehash进行期间,标记位rehashidx从0开始,每次对字典的键值对执行增删改查操作后,都会将rehashidx位置的数据迁移到1号表,然后将rehashidx加1,随着字典操作的不断执行,最终0号表的所有键值对都会被rehash到1号表上。
之后,1号表会被设置成0号表,接着在1号表的位置创建一个新的空白表。
28. HashTable怎么缩容?#
首先程序会为HashTable的1号表分配空间,新表大小为第一个大于等于(原表used)的2次方幂。
在rehash进行期间,标记位rehashidx从0开始,每次对字典的键值对执行增删改查操作后,都会将rehashidx位置的数据迁移到1号表,然后将rehashidx加1,随着字典操作的不断执行,最终0号表的所有键值对都会被rehash到1号表上。
之后,1号表会被设置成0号表,接着在1号表的位置创建一个新的空白表。
29. HashTable什么时候扩容,什么时候缩容#
- 第一个是服务器目前没有在执行BGSAVE或者BGREWRITEAOF,并且负载因子 >= 1
- 第二个是服务器目前正在执行BGSAVE或者BGREWRITEAOF,并且负载因子 >= 5
- 缩容的话也是负载因子影响,当哈希表的负载因子小于0.1时,程序会自动开始对哈希表进行收缩操作。
ZSet#
30. ZSet底层有几种编码方式?#
ZSet就是有序集合对象,ZSet对象的底层有两种编码方式:ziplist 或者 skiplist+字典。
如果一个ZSet对象中的所有元素同时满足:元素数量小于128个 以及 所有元素成员的长度都小于64字节,那么会使用ziplist编码,否则使用 skiplist+字典 编码。
31. 跳表编码模式下,查询节点总数的平均时间复杂度是多少?#
跳表编码模式下,查询节点总数的平均时间复杂度是O(1),因为跳表的表头结构中定义了一个保存节点数量的字段length
32. 跳表插入一条数据的平均时间复杂度是多少#
整体平均时间复杂度是O(logN)
33. 为什么跳表和HashTable要配合使用#
为了结合这两种数据结构各自的优势,当ZSet要根据成员来查找分值的时候,将使用字典来实现,时间复杂度为O(1)。 而当ZSet要执行范围操作时,比如ZRANK、ZRANGE等命令时,将使用原本就有序的跳跃表来实现。
34. 跳表中一个节点的层高是怎么决定的?#
跳表在插入新节点之前会计算一个随机的层高,具体来说,跳表的每一个节点一开始默认都是1层,然后每增加一层的概率都是 25%,最高为32层
35. zset为什么用跳表而不用平衡树?#
跳表的代码实现比平衡树来说,实现要简单多了 按照区间来查找数据这个操作,平衡树的效率没有跳表高。
Redis执行#
36. Redis是单线程还是多线程#
某些异步流程从4.0开始用的多线程,例如 UNLINK、FLUSHALL ASYNC、FLUSHDB ASYNC 等非阻塞的删除操作。
网络I/O解包从6.0开始用的是多线程;
37. Redis为什么选择单线程做核心处理#
- 首先如果引入多线程,主要是希望充分利用多核的性能,但Redis的定位,是内存k-v存储,是做短平快的热点数据处理,一般来说执行会很快,执行本身不应该成为瓶颈,而瓶颈通常在网络I/O,处理逻辑多线程并不会有太大收益。
- 同时,支持多线程的话,我们需要付出更大的复杂度、以及多线程上下文切换、同步机制的开销等成本。
38. Redis单线程性能如何?#
Redis单线程的性能是很好的,在普通机器每秒能10多万的读性能、几万的写性能。
39. 为什么单线程还能这么快#
一个是内存存储,这是Redis的定位也是快的前提, 一个是高效的数据结构,Redis的数据结构可以说是追求极致,持续调优, 最后一个是I/O多路复用
40. Redis6.0之后引入了多线程,你知道为什么吗?#
Redis主要瓶颈是I/O而不是CPU,所以针对核心处理流程中的解包、发包这两个CPU耗时操作,进行了多线程优化,充分发挥多核优势
41. Redis6.0的多线程是默认开启的吗?#
默认是关闭的,如果想要开启需要用户在 redis.conf 配置文件中修改。 默认关闭1是为了兼容以前的,毕竟很多用户的认知中,Redis是单线程的, 第二可能也是认为多线程并不是必要的,在大多数场景不开启也是完全够用的。
42. Redis6.0的多线程主要负责命令执行的哪一块#
原来核心流程中的I/O处理,包括解包和回包,也就是读写客户端socket的I/O
Redis持久化#
43. RDB和AOF本质区别是什么?#
本质区别就是 RDB 是保存快照进行持久化,而 AOF 则是 追加日志文件进行持久化。因为这个本质区别,所以还会有文件恢复速度、安全性、操作开销等区别
44. 如果RDB和AOF只能选一种,你选哪个?#
如果从业务需要来看,如果我们能接受分钟级别的数据丢失,可以考虑选择RDB,如果需要尽量保证数据安全,可以考虑混合持久化,如果只用AOF,那么优先选择everysec策略进行刷盘。
从持久化理念来看,始终开启快照是一个推荐的方式,这也是Redis官方为什么默认开启RDB,而不开启AOF,同时官网也明确不推荐只开AOF
45. 介绍一下AOF的三种写回策略?#
- Always是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;
- Everysec每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;
- No就是不控制写回硬盘的时机。每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。
46. 为什么先执行Redis命令,再把数据写入AOF日志呢?#
1.保证正确写入:如果当前的命令语法有问题,错误的命令记录到 AOF 日志里后可能还会进行语法检查。先执行Redis命令,再把数据写入AOF日志可以保证写入的都是正确可执行的命令。
2.不阻塞当前写操作:因为当写操作命令执行成功后才会将命令记录到AOF日志,避免写入阻塞。
47. AOF子进程的内存数据跟主进程的内存数据不一致怎么办?#
Redis设置了一个 AOF 重写缓冲区,这个缓冲区在创建 bgrewriteaof 子进程之后开始使用。在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到AOF 缓冲区和AOF 重写缓冲区。当子进程完成 AOF 重写工作后,会向主进程发送一条信号。主进程收到该信号后,会调用一个信号处理函数,将 AOF 重写缓冲区中的所有内容追加到新的 AOF 的文件中,使得新旧两个 AOF 文件所保存的数据库状态一致;新的 AOF 的文件进行改名,覆盖现有的 AOF 文件。
48. RDB 在执行快照的时候,数据能修改吗?#
RDB 生成快照时,Redis 会通过 fork 创建子进程。
fork 后父子进程共享同一份物理内存,只有发生数据修改时,操作系统才会复制对应内存页,这就是写时复制(Copy-On-Write)。
因此 Redis 可以在不阻塞主线程的情况下生成 RDB,但如果快照期间大量写入,会导致内存占用增大。
49. Redis用RDB持久化时对过期键会如何处理的?#
RDB分为生成阶段和加载阶段
生成阶段会对key进行过期检查,过期的key不会保存到RDB文件中;
如果 Redis 是主服务器运行模式的话,在载入 RDB 文件时,程序会对文件中保存的键进行检查,过期键不会被载入到数据库中。所以过期键不会对载入 RDB 文件的主服务器造成影响;
如果 Redis 是从服务器运行模式的话,在载入 RDB 文件时,不论键是否过期都会被载入到数据库中。但由于主从服务器在进行数据同步时,从服务器的数据会被清空。
50. Redis用AOF持久化时对过期键会如何处理的?#
当 Redis 以 AOF 模式持久化时,如果数据库某个过期键还没被删除,那么 AOF 文件会保留此过期键,当此过期键被删除后,Redis 会向 AOF 文件追加一条 DEL 命令来显式地删除该键值。
执行 AOF 重写时,会对 Redis 中的键值对进行检查已过期的键不会被保存到重写后的 AOF 文件中,因此不会对 AOF 重写造成任何影响。
51. Redis主从模式中,对过期键会如何处理?#
当 Redis 运行在主从模式下时,从库不会进行过期扫描,从库对过期的处理是被动的。也就是即时从库中的 key 过期了,如果有客户端访问从库时,依然可以得到 key 对应的值,像未过期的键值对一样返回。
从库的过期键处理依靠主服务器控制,主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。
52. RDB持久化的触发时机?(几乎不考)#
主要有这么几个地方,一个是调用save或者bgsave命令,一个是根据我们配置周期进行,一个是Redis关闭之前,这三个是比较常见的,其它边缘一点的还有主从全量复制发送RDB文件等。
53. AOF刷盘的触发时机(几乎不考)#
AOF触发流程主要有3个,一个是Redis关闭的时候,另一个是每一次事件循环钩子函数 beforeSleep(),最后一个是每一次事件循环函数servercron里面
54. RDB对主流程有什么影响?(几乎不考)#
当执行阻塞式持久化的时候,由主进程进行 RDB快照保存,会阻塞主进程
当执行后台持久化时,由fork出的子进程来进行RDB快照保存,写时复制技术
55.AOF对主流程有什么影响?(几乎不考)#
AOF 会让 Redis 在执行写命令后额外进行日志追加和 fsync 刷盘,从而增加主线程的 IO 开销并可能造成延迟。
56. AOF混合持久化方案是什么?#
Redis 会先把当前内存数据以 RDB 格式写入新的 AOF 文件, 之后再把 rewrite 期间产生的增量写命令, 以普通 AOF 命令形式追加到文件末尾。
57. 简单描述AOF重写流程#
当aof重写触发那一刻,主进程就会fork出一个子进程,然后这个子进程读取Redis DB中的数据,以字符串命令的格式写入到新AOF文件中
如果这个时候Redis接收到了新的写入命令,那么主进程会将这些"增量数据"写入到AOF重写缓冲区中
在子进程将数据都写入到新AOF文件后,主进程会通过管道将AOF重写缓冲区里面的数据发送给子进程,子进程再将这一份数据追加到新AOF文件中,保证新AOF文件的完整性
58. AOF重写你觉得有什么不足之处么?#
额外的CPU开销:AOF重写缓冲、向子进程发送AOF重写缓冲、写入到新的AOF日志中 额外的内存开销:AOF缓冲 和 AOF重写缓冲 额外的磁盘开销:旧的AOF日志 和 新的AOF日志
59. 针对AOF重写的不足,你有什么优化思路呢?#
AOF rewrite 复杂/危险/大文件问题
MP-AOF(Multi Part AOF)是 Redis 7.0 提出的多段 AOF 方案。 它会把 AOF 拆成: base 文件 + 增量文件 其中:
- base AOF:保存 rewrite 后的基础数据
- incr AOF:保存之后新增的写命令
Redis过期删除策略和内存淘汰策略#
60. Redis 是怎么删除过期 key 的?#
Redis 采用的删除策略是将惰性删除策略和定期删除策略组合使用。
惰性删除策略:每次从数据库取 key 的时候检查 key 是否过期,如果过期则删除,并返回 null,如果 key 没有过期,则直接返回数据。这样做的好处是占用 CPU 的时间比较少。但是缺点是如果 key 很长时间没有被获取,将不会被删除,容易造成内存泄露。
定期删除策略:该策略的作用是每隔一段时间执行一次删除过期 key 的操作,这样做的好处是可以避免惰性删除时出现内存泄露的问题,通过设置删除操作的时长频率,可以减少 CPU 时间的占用(从过期字典中随机抽取 20 个 key;检查这 20 个 key 是否过期,并删除已过期的 key;已过期 key 的数量占比随机抽取 key 的数量大于 25%,则继续重复步骤直到比重小于25%。)
61. Redis有几种内存回收策略?#

62. 内存回收是什么时候发起#
实际上,每次进行读写的时候,都会去检查是否需要释放内存,如果需要则会触发。
63. 介绍下Redis LRU回收算法#
LRU,即最近最久未使用,记录每个key的最近访问时间,淘汰最久未没访问到的数据。但是Redis并不是标准的LRU算法,而是做了优化,这块我可以展开讲讲么?
64. Redis LRU算法是标准的吗?为什么不用标准的#
Redis 的 LRU 是一种近似 LRU 淘汰算法。
每个对象会记录最近访问时间,内存不足时 Redis 会随机采样部分 key,淘汰其中最久未访问的数据。 它没有使用真正的双向链表 LRU,而是通过“随机采样 + eviction pool”降低维护成本,提高性能。
65. 什么是LFU算法,为什么Redis要引入LFU算法#
LFU,即将访问频率也加入到影响因素中,具体而言,LFU下同时记录了上一次访问时间戳和访问计数,访问计数同时受上一次访问时间和访问频率的影响,因为每次访问都可能会增加计数。
场景#
66. 你有实际使用过Redis做什么应用么?#
有在实习中/工作中/实验室中涉及过Redis缓存场景、Redis分布式锁场景
67. Redis缓存是如何应用的?#
我们是用作旁路缓存,在我们订单项目中,是先查询Redis,没有查MySQL并将数据加载到Redis。
68. Redis经常作为MySQL的缓存来使用,为什么?#
一些热点数据就可以缓存到Redis,查询时先查Redis,不存在才查MySQL,这种Redis+MySQL结合的方式可以有效提高系统QPS。
69. Redis和Memcached有哪些共同点和不同点#
Memcached适合 纯字符串缓存场景,数据结构只支持字符串,超高并发下性能较优,适用于 CDN、页面缓存、数据库查询结果缓存等。
Redis适合更广泛的场景,支持持久化、多数据结构、事务、分布式锁等,适用于 排行榜、消息队列、实时统计 等。
70. Redis做旁路缓存,如果MySQL更新了,此时何去何从?#
我在项目中使用过期时间来兜底,并且在更新DB后删除缓存来提升一致性的方式。另外,在做方案设计时候我还考虑过订阅binlog的方式,但这种方案额外引入了消息队列和消费服务,成本太高而收益不足,所以还是选择了前者。
71. 如何保证删除缓存操作一定能成功?#
可以引入消息队列,删除缓存的操作由消费者来做,删除失败的话重新去消息队列拉取相应的操作,超过一定次数没有删除成功就像业务层报错。
如果是MySQL、Redis缓存同步场景,为了保证成功率,可以用一个消费服务订阅 MySQL binlog 日志,拿到具体要操作的数据,然后再向Redis执行缓存删除操作。
72. 业务缓存一致性要求高怎么办?#
延迟双删是提高一致性的方案,先删除缓存,然后更新数据库,等待一段时间再删除缓存。
73. 如何避免缓存失效?#
首先业务发现缓存失效,是会去读MySQL数据重新加载进去的,但是为了尽可能避免缓存失效,我们可以由后台线程频繁地检测缓存是否有效,检测到即将失效了马上从数据库读取数据,并更新到缓存。 另外,在业务刚上线的时候,最好提前把数据缓起来,而不是等待用户访问才来触发缓存构建,这就是所谓的缓存预热。
74. Redis做秒杀场景可以吗?讲讲思路#
Redis在秒杀场景主要的应用方式是两个。
一个是把Redis当消息队列用,用于削峰,一个是用Redis记录库存,进行库存的加减。
75. Redis管道有什么用?#
管道技术是客户端提供的一种批处理技术,用于一次处理多个 Redis 命令,从而提高整个交互的性能。
Pipeline的本质,是将请求在客户端打包,然后一次发送给服务端,服务端处理完成之后,会将结果存起来,等Pipeline中所有命令都完成了,再一起回包,这样可以节约很多网络交互的时间。
76. Redis如何处理大key?#
一般而言String 类型的值大于 10 KB;Hash、List、Set、ZSet 类型的元素的个数超过5000个;
当vaule是string时,比较难拆分,则使用序列化、压缩算法将key的大小控制在合理范围内,但是序列化和反序列化都会带来更多时间上的消耗。
当value是string,压缩之后仍然是大key,则需要进行拆分,一个大key分为不同的部分,记录每个部分的key,使用multiget等操作实现事务读取。
分拆成几个key-value,存储在一个hash中,每个field代表一个具体的属性,使用hget,hmget来获取部分的value,使用hset,hmset来更新部分属性
77. Redis支持事务回滚吗?#
不支持,Redis不具备完整的ACID特性,执行的命令都没有回滚之说
78. Redis如何实现延迟队列?#
使用ZSet,ZSet 有一个 Score 属性可以用来存储延迟执行的时间。使用 zadd score1 value1 命令,再利用 zrangebysocre 查询符合条件的所有待处理的任务,通过循环执行队列任务。
Redis 分布式锁#
79. Redis可以做消息队列吗?什么时候能用Redis做消息队列?#
Redis可以作为轻量级消息队列。如果是本身业务轻量级,且团队没有已经接入完备的消息队列,这个时候没有必要引入一个重量消息队列,使用Redis即可满足要求,没有不能用的组件,只有不合适的场景。
80. 什么是分布式锁?#
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在多线程或者多进程并发的情况下,使用锁来保证一个代码块在同一时间内只能由一个线程执行。

81. 如何理解Redis原子性操作原理?#
Redis提供的API都是单线程串行处理的,所以我们用单条对象操作命令都不用担心被中断,如果是多条命令要实现原子性,通常都是用LUA脚本来支持。
82. 分布式锁实现要点是什么(其实就是怎么加锁、怎么解锁、怎么用)?#
// lock_key 就是 key 键;
// unique_value 是客户端生成的唯一的标识,区分来自不同客户端的锁操作;
// NX 代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;
// PX 10000 表示设置 lock_key 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁
SET lock_key unique_value NX PX 10000
- 加锁是用SET命令,然后带上NX参数,key是锁名字,value是持有者id,再加一个过期时间,这里value要用持有者id的原因是谁申请、谁释放的原则,会在解锁时进行检查,过期时间是为了兜底,防止异常情况下锁被永久占据。
// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
- 解锁的话,主要涉及两步操作,一个是查看是不是自己的锁,如果是接着就是释放锁,为了保证原子性,解锁需要用LUA脚本进行。
83. 为什么需要引入owner的概念呢?#
分布式锁需要保证对称性,假设没有这种对称性,会有问题,举个例子,服务A获取了锁,由于业务流程比较长,或者网络延迟、GC卡顿等原因,导致锁过期,而业务还会继续进行。
这时候,业务B已经拿到了锁,准备去执行,这个时候服务A恢复过来并做完了业务,就会释放锁,而B却还在继续执行,等B完成下次释放的可能又是别人的锁,这种情况是需要避免的。
84. 你提到了lua,用lua一定能保证原子性?#
lua本身不具备原子性,上面提到用lua来保证原子性是因为Redis是单线程执行,一个流程放进lua来执行,相当于是打包在一起,Redis执行他的过程中不会被其他请求打断,所以说保证了原子性。
85. 基于 Redis 实现分布式锁有什么优缺点?#
86. 使用Redis实现分布式锁的优点?#
第一是性能高,速度非常快; 第二是简单易用、简单意味着容易接入和不易出错; 第三是广泛应用,即有丰富的实践经验。
87. 使用Redis实现分布式锁的缺点?#
锁过期时间不好控制:业务没执行完,锁就过期 Redis 主从切换可能导致锁失效
88. 如何为Redis分布式锁设置合理的超时时间?#
比如业务最大执行时间是3s,网络延迟为200毫秒,多给1s缓冲,则超时时间可设置为4.2秒,向上取整为5秒。
89. 除了 Redis 实现分布式锁,还有哪些方案可以实现分布式锁?各有什么优缺点?#
利用数据库的唯一约束或乐观锁机制(如版本号)来实现分布式锁。 例如,创建一个锁表,通过插入一条唯一记录(如锁名称)来获取锁,删除记录来释放锁。
实现简单,直接利用现有数据库,无需引入额外组件。 性能较差,数据库的读写操作在高并发场景下可能成为瓶颈。
90. 怎么用Redis实现可重入的分布式锁?#
可重入锁即同一个线程或客户端可以多次获取同一把锁,核心目的是允许同一客户端多次获取锁,减少不必要的等待,提升效率,比较适用于嵌套调用或递归场景。
可重入锁的核心是计数,即额外维护一个计数器,记录锁的重入次数,我们拿加锁流程举例,即先尝试Set命令(结合NX参数),如果不存在就Set成功,存在的话则查询是否是自己的id,是的话就增加计数,解锁也是一个思路,加锁解锁都需要用LUA保证原子性。
91.#
94. Redis 如何解决集群情况下分布式锁的可靠性?#
分布式锁算法 Redlock(红锁)。基于多个 Redis 节点的分布式锁,即使有节点发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。官方推荐是至少部署 5 个 Redis 节点,而且都是主节点,它们之间没有任何关系,都是一个个孤立的节点。
基本思路:是让客户端和多个独立的 Redis 节点依次请求申请加锁,如果客户端能够和半数以上的节点成功地完成加锁操作,那么就认为,客户端成功地获得分布式锁,否则加锁失败。即使有某个 Redis 节点发生故障,锁的数据在其他节点上也有保存,客户端仍然可以正常地进行锁操作,锁的数据也不会丢失。
Redis 缓存异常#
95. 缓存穿透是什么?怎么解决?#
当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。
布隆过滤器:在缓存层前设置布隆过滤器,快速判断数据是否存在,避免无效查询。
缓存空对象(回写特殊值):即使查询结果为空,也往缓存里写入一个特殊的值,这个值就是标记数据不存在,设置较短过期时间,防止重复查询冲击数据库。
限流策略
96. 布隆过滤器是怎么工作的?#
数据 x 会被 3 个哈希函数分别计算出 3 个哈希值,然后在对这 3 个哈希值对 8 取模,假设取模的结果为 1、4、6,然后把位图数组的第 1、4、6 位置的值设置为 1。
查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。
97. 布隆过滤器有什么缺陷?#
查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。
不支持一个关键字的删除,因为一个关键字的删除会牵连其他的关键字。改进方法就是counting Bloom filter,用一个counter数组代替位数组,就可以支持删除了。
98. 缓存击穿是什么?怎么解决?#
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮。
我们可以通过延长热点数据过期时间、使用互斥锁或分布式锁控制查询频率、缓存永不过期等手段来解决这个问题。
99. 缓存雪崩是什么?怎么解决?#
缓存雪崩是指缓存中大量数据同时过期,导致所有请求直接访问数据库,造成数据库压力激增甚至崩溃。解决方法包括:
- 设置缓存过期时间的随机值,避免同时过期;
- 使用分布式锁或队列控制数据库访问,防止瞬间高并发;
- 实现缓存高可用,如主从复制或集群部署,确保缓存服务不中断;
- 热点数据永不过期,定时异步更新缓存;
- 提前预加载缓存,确保数据在过期前已更新。通过这些措施,可以有效缓解缓存雪崩问题。
Redis 集群#
100. Redis 集群架构模式有哪几种?#
Redis 提供了三种集群模式:主从架构、哨兵集群、切片集群


主从:选择一台作为主服务器,将数据同步多台从服务器上,构建一主多从的模式,主从之间读写分离。主服务器可读可写,发生写操作会同步给从服务器,而从服务器一般是只读,并接受主服务器同步过来写操作命令。主从服务器之间的命令复制是异步进行的,所以无法实现强一致性保证(主从数据时时刻刻保持一致)。
哨兵:当 Redis 的主从服务器出现故障宕机时,需要手动进行恢复,为了解决这个问题,Redis 增加了哨兵模式,哨兵监控主从服务器,并且提供主从节点故障转移的功能。
切片集群:当数据量大到一台服务器无法承载,需要使用Redis切片集群(Redis Cluster)方案,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,提高 Redis 服务的读写性能。
101. Redis主从复制过程是怎样的?#
当从节点初次连接到主节点,或者掉线重连后进度落后较多时会进行一次全量数据同步。 当从节点掉线重连后,如果进度落后的不多,将会进行增量同步。 当主从节点完成初次同步后,将会建立长连接进行命令传播。
102. Redis 的主从复制模式有什么优缺点?#
主从复制的模式相对于单节点的好处在于,实行读写分离提高了系统的读写效率,提高了网站数据的读取加载速度。 但是缺点是由于写数据主要在主节点上操作,主节点内存空间有限,并且主节点存在单点风险。
103. 哨兵机制是什么?#
因为在主从架构中读写是分离的,如果主节点挂了,将没有主节点来响应客户端的写操作请求,也无法进行数据同步。 哨兵作用是实现主从节点故障转移。哨兵会监测主节点是否存活,如果发现主节点挂了,会选举一个从节点切换为主节点,并且把新主节点的相关信息通知给从节点和客户端。
104. 哨兵机制的工作原理?#
判断节点是否存活: 哨兵会周期性给所有主节点发送PING命令来判断其他节点是否正常运行。如果PING命令响应失败哨兵会将节点标记为主观下线,然后该哨兵会向其他节点发出投票命令,当票数达到设定的阈值之后这个主节点就被标记为客观下线。然后哨兵会从从节点中选择一个作为主节点。
投票: 哨兵集群中会选择一个leader来负责主从切换,选举是一个投票过程:判断主节点为客观下线的是候选者,候选者向其他哨兵发送命令表示要成为leader,其他哨兵会进行投票,每个哨兵只有一票,可以投给自己或投给别人,但是只有候选者才能把票投给自己。候选者之后拿到半数以上的赞成票并且票数大于设置的阈值,就会成为leader。
选出新主节点: 把网络状态不好的从节点给排除:先把已经下线的从节点过滤掉,然后把以往网络连接状态不好的从节点排除掉。接下来要对所有从节点进行三轮考察:优先级、复制进度、ID号。
更换主节点 选出新主节点之后,哨兵leader让已下线主节点属下的所有从节点指向新主节点。
通知客户的主节点已更换 客户端和哨兵建立连接后,客户端会订阅哨兵提供的频道。主从切换完成后,哨兵就会向 +switch-master 频道发布新主节点的 IP 地址和端口的消息,这个时候客户端就可以收到这条信息,然后用这里面的新主节点的 IP 地址和端口进行通信了。
将旧主节点变为从节点 继续监视旧主节点,当旧主节点重新上线时,哨兵集群就会向它发送SLAVEOF命令,让它成为新主节点的从节点。
105. Redis sentinel(哨兵)模式优缺点有哪些?#
Redis 哨兵的好处在于可以保证系统的高可用,各个节点可以对故障自动转移。 但缺点是使用的主从模式,主节点单点风险高,主从切换过程可能会出现丢失数据的问题。
106. 说说 Redis 哈希槽的概念?#
Redis 集群并没有使用一致性 hash,而是引入了哈希槽的概念。Redis 集群有 16384(2^14)个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
107. 哈希槽和Redis节点是如何对应的?#
主要有自动分配和手动分配两种方式。 自动分配是集群创建或者节点添加减少时,Redis自动将哈希槽平均分配到集群节点上; 手动分配是使用命令指定每个节点上面的哈希槽数目,使用手动分配时要把16384个槽位给分完,否则集群不会正常工作。
108. 主从模式的同步过程?#
主要分为建立连接协商、主从数据同步、发送新操作三个步骤 连接协商主要确定复制偏移量等关键数据,为同步建立基础;首次主从同步数据是通过RDB文件传递来同步 期间的命令是利用复制缓冲区同步,完成首次同步之后,后续写入操作持续同步给Redis从节点,保证增量数据也是同步的。
109. 从服务重新上线之后,主服务器如何知道要将哪些增量数据发送给从服务器?#
网络断开从服务器重新上线之后,会发送自己的复制偏移量到主服务器,主服务器根据偏移量之间的差距判断要执行的操作:如果从服务器要读的数据在repl_backlog_buffer中,则采用增量复制;如果不在,采用全量复制。
110. Redis如何减少主从数据的不一致?#
同机房部署、外部监控程序
111. 主从模式是同步复制还是异步复制?#
异步复制。因为主节点收到写命令之后,先写到内部的缓冲区,然后再异步发送给从节点。这样做的好处是对主流程影响小,不干扰Redis的高性能。
Reply by Email