Redis基础概念数据结构、持久化机制、高可用、内存管理淘汰策略

3 阅读28分钟

一、基础概念&核心特性(必问)

1. 什么是Redis?它的核心定位和应用场景是什么?

核心知识点

Redis(Remote Dictionary Server)是开源的、高性能的键值对(Key-Value)NoSQL数据库,基于内存运行,支持持久化,采用单线程核心IO模型,提供丰富数据结构和扩展功能。

原理详解

  1. 核心定位:内存数据库(优先操作内存,性能极高)+ 持久化存储(避免内存数据丢失)+ 分布式协调工具(支持分布式锁、发布订阅等)。

  2. 核心优势(原理层面):

  • 高性能:内存操作无磁盘IO瓶颈,读QPS可达10万+/s,写QPS可达5万+/s;单线程模型避免多线程上下文切换、锁竞争开销,同时用IO多路复用(epoll/kqueue)处理大量并发连接,解决单线程无法应对多连接的问题。

  • 灵活扩展:支持主从复制、哨兵、集群模式,可实现高可用和水平扩展。

  • 功能丰富:原生支持多种数据结构、持久化、事务、Lua脚本、发布订阅等,无需额外开发。

  1. 典型应用场景:
  • 缓存:热点数据(商品、用户信息)缓存,减少数据库压力;

  • 会话存储:分布式会话(如Spring Session集成Redis);

  • 分布式锁:基于SET NX EX实现,解决分布式系统并发竞争问题;

  • 计数器/限流:incr/decr实现点赞、阅读量计数,结合过期时间实现接口限流;

  • 消息队列:Stream/List结构实现简单的消息生产消费(替代轻量级MQ);

  • 地理位置:Geo结构实现附近的人、门店定位功能。

2. Redis支持哪些数据结构?每种结构的底层实现原理是什么?(高频重点)

核心知识点

Redis支持8种核心数据结构:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、ZSet(有序集合)、Bitmap(位图)、HyperLogLog(基数统计)、Geo(地理位置),每种结构底层有专属实现,决定其性能和适用场景。

原理详解(分结构拆解)

(1)String(字符串)

  • 底层实现:简单动态字符串(SDS) ,而非C语言原生字符串(char*)。

  • SDS原理:SDS由「len(已用长度)、free(空闲长度)、buf(字节数组)」三部分组成,优势如下:

  1. 避免缓冲区溢出:修改字符串时,先检查len+新增长度是否超过buf容量,不足则自动扩容(扩容规则:小于1MB时翻倍,大于1MB时每次增加1MB);

  2. 快速获取长度:直接读取len属性,时间复杂度O(1)(C语言需遍历,O(n));

  3. 减少内存分配:free属性记录空闲空间,支持预分配(减少频繁扩容)和惰性释放(删除字符串时不立即释放内存,留作后续复用);

  4. 二进制安全:buf存储的是字节流,可存储图片、视频等二进制数据(C语言字符串以\0结尾,无法存储含\0的二进制数据)。

  • 适用场景:缓存、计数器、分布式锁、存储简单字符串(如token)。

(2)Hash(哈希)

  • 底层实现:两种结构自适应切换——ziplist(压缩列表)hashtable(哈希表)

  • 切换条件(Redis 7.0前):当哈希中元素个数 ≤ 512 且每个元素的key和value长度 ≤ 64字节时,用ziplist;否则切换为hashtable。(Redis 7.0后,ziplist被listpack替代,逻辑类似,更高效)

  • 各自原理:

  1. ziplist(压缩列表):一种紧凑的内存结构,将所有键值对紧凑存储在连续内存中,减少内存碎片,适合小数据量场景;缺点是插入/删除需移动元素,时间复杂度O(n)。

  2. hashtable(哈希表):Redis的核心哈希结构,由「数组+链表」组成(类似Java的HashMap),解决哈希冲突的方式是「链地址法」(冲突的元素挂在同一个数组下标对应的链表上);Redis 3.0后引入「渐进式rehash」,解决扩容时的性能瓶颈(避免一次性rehash导致单线程阻塞)。

  • 适用场景:存储对象(如用户信息、商品详情),可单独操作对象的某个字段(hset/hget),比String存储对象更节省内存、更灵活。

(3)List(列表)

  • 底层实现:同样自适应切换——ziplist(压缩列表)quicklist(快速列表)

  • 切换条件:元素个数 ≤ 512 且每个元素长度 ≤ 64字节时用ziplist;否则用quicklist。(Redis 3.2后,用quicklist替代了原有的linkedlist,优化内存和性能)

  • 原理:

  1. quicklist:由多个ziplist组成的双向链表,每个ziplist是一个紧凑的内存块,既保留了链表的高效插入/删除(两端操作O(1)),又通过ziplist减少了内存碎片;

  2. 核心特性:双向链表,支持从头部(lpush/lpop)、尾部(rpush/rpop)插入/删除元素,按索引访问元素(lindex)时间复杂度O(n)。

  • 适用场景:消息队列(简单生产者-消费者模型)、最新消息列表(如朋友圈点赞列表)、栈/队列实现。

(4)Set(集合)

  • 底层实现:自适应切换——intset(整数集合)hashtable(哈希表)

  • 切换条件:当集合中所有元素都是整数,且元素个数 ≤ 512时,用intset;否则用hashtable。

  • 原理:

  1. intset:紧凑的整数存储结构,元素按升序排列,支持快速查找(二分查找,O(log n)),插入/删除需移动元素(O(n));

  2. hashtable:用哈希表存储元素,key是集合元素,value为null,利用哈希表的特性实现元素的快速查找、插入、删除(O(1)),且自动去重。

  • 核心特性:无重复元素、支持交集(sinter)、并集(sunion)、差集(sdiff)操作。

  • 适用场景:去重(如用户标签去重)、共同好友、抽奖活动(srandmember/spop)。

(5)ZSet(有序集合)

  • 底层实现:自适应切换——ziplist(压缩列表)skiplist(跳表)+ hashtable

  • 切换条件:元素个数 ≤ 128 且每个元素的key和value长度 ≤ 64字节时用ziplist;否则用skiplist+hashtable。

  • 原理(重点,面试高频):

  1. ziplist:元素按分数升序存储,每个元素由「分数+成员」组成,紧凑存储,适合小数据量;

  2. skiplist+hashtable(核心):

  • skiplist(跳表):一种有序数据结构,通过“分层”实现快速查找,类似平衡树但实现更简单;每个节点有多个层级,高层节点是低层节点的“索引”,查找时从高层开始,快速定位到目标范围,时间复杂度O(log n);用于维护元素的有序性,支持按分数范围查询(zrangebyscore);

  • hashtable:用于快速根据成员查找分数(zscore),时间复杂度O(1);两者结合,既保证了有序性,又保证了快速查询。

  • 核心特性:元素有序(按分数排序)、无重复成员、支持按分数/排名查询。

  • 适用场景:排行榜(如商品销量、用户积分)、带权重的消息队列、范围查询(如查询分数在80-100的用户)。

(6)Bitmap(位图)

  • 底层实现:基于String实现,本质是字节数组(每个字节的8个bit位存储0/1),利用bit位节省内存。

  • 原理:每个bit位对应一个状态(0=未发生,1=已发生),通过bitop(与/或/非/异或)、bitcount(统计1的个数)、setbit/getbit(设置/获取bit位)等命令操作,时间复杂度O(1)。

  • 适用场景:签到统计(每天一个bit位,一年仅需45字节)、用户在线状态(1=在线,0=离线)、连续活跃天数统计。

(7)HyperLogLog(基数统计)

  • 底层实现:基于概率算法,通过存储元素的哈希值的高位特征,估算集合的基数(不重复元素个数),占用内存固定(约12KB),与集合大小无关。

  • 原理:核心是「伯努利试验」,通过统计哈希值中“前导0的最大个数”,估算基数;存在极小误差(约0.81%),但无需存储所有元素,适合海量数据的基数统计。

  • 适用场景:UV统计(网站独立访客)、用户行为去重统计(如浏览商品的独立用户数)。

(8)Geo(地理位置)

  • 底层实现:基于ZSet实现,将经纬度转换为「geohash值」(一个64位整数)作为ZSet的分数,成员为地理位置名称;通过geohash值的特性,实现附近的人、距离计算等功能。

  • 原理:geohash算法将二维的经纬度( longitude,latitude)转换为一维的整数,相邻的地理位置的geohash值也相邻,因此可以通过ZSet的范围查询(zrangebyscore),找到附近的地理位置。

  • 适用场景:附近的人、门店定位、距离计算(geodist)。

3. Redis和Memcached的核心区别是什么?(高频对比)

核心知识点

两者都是内存缓存工具,但Redis功能更全面、支持持久化和更多数据结构,Memcached更轻量、专注于简单缓存,核心区别集中在数据结构、持久化、原子性等维度。

原理详解(对比表格+重点补充)

对比维度RedisMemcached
数据结构支持String、Hash、List等8种结构,功能丰富仅支持String类型,功能单一
持久化支持RDB、AOF两种持久化方式,可将内存数据落盘,重启不丢失不支持持久化,数据仅存于内存,重启即丢失
原子性原生支持单个操作原子性,支持事务(MULTI/EXEC)、Lua脚本,保证复杂操作原子性仅支持简单的CAS原子操作,不支持事务和Lua脚本
内存管理采用动态内存分配,支持内存淘汰策略(如LRU),可设置最大内存采用预分配内存池机制,内存分配后无法回收(易产生内存碎片),不支持内存淘汰
并发连接单线程IO模型+IO多路复用,支持海量并发连接多线程模型,处理并发连接的方式与Redis不同
高可用支持主从复制、哨兵、集群,可实现故障自动切换和水平扩展不支持原生高可用,需依赖第三方工具(如Keepalived)实现主从切换
适用场景复杂缓存、分布式锁、会话存储、消息队列等多场景简单的字符串缓存(如静态页面缓存),追求极致轻量

补充:Redis单线程 vs Memcached多线程——Redis单线程避免了多线程的上下文切换和锁竞争,而Memcached多线程可利用多核CPU,但多线程的开销可能抵消部分性能优势;在高频简单读写场景,两者性能接近,复杂场景Redis更有优势。

二、持久化机制(核心重点,必问)

1. Redis的持久化机制有哪些?各自的实现原理、优缺点是什么?

核心知识点

Redis提供两种持久化方式:RDB(Redis Database)和AOF(Append Only File),可单独使用,也可组合使用;核心目的是将内存中的数据落盘,避免Redis重启后数据丢失。

原理详解(分两种方式拆解)

(1)RDB(快照持久化)

  • 定义:在指定的时间间隔内,将Redis内存中的所有数据生成一份快照(二进制文件,.rdb),保存到磁盘中;Redis重启时,加载.rdb文件,恢复数据。

  • 实现原理(触发机制+执行流程):

  1. 触发机制:
  • 手动触发:执行save(同步)、bgsave(异步)命令;

  • 自动触发:通过redis.conf配置触发,如「save 900 1」(900秒内有1次写操作,触发bgsave)、「save 300 10」(300秒内有10次写操作)、「save 60 10000」(60秒内有10000次写操作);

  • 其他触发:Redis关闭(shutdown)时,会自动执行save(同步),保证数据不丢失;主从复制时,主节点会执行bgsave,将快照发送给从节点。

  1. 执行流程(bgsave,核心异步流程):
    1. 客户端发送bgsave命令,Redis主进程fork(创建子进程),子进程会复制主进程的内存数据(写时复制,COW);
    1. 子进程将内存中的数据写入临时的.rdb文件,写入完成后,替换原有的.rdb文件;
    1. 子进程执行完成后,向主进程发送信号,主进程更新持久化状态;
  • 注:fork过程会阻塞主进程(时间很短,取决于内存大小),子进程执行期间,主进程可正常处理客户端请求,写入的数据会被记录到缓冲区,待子进程完成后,再同步到新的.rdb文件。

  • 优点:

  • 快照文件体积小,加载速度快(二进制文件,直接解析加载);

  • 异步触发(bgsave)时,不影响主进程处理请求(仅fork时短暂阻塞);

  • 适合全量备份、灾难恢复(如定期备份.rdb文件)。

  • 缺点:

  • 数据一致性差:RDB是间隔性快照,若Redis在两次快照之间崩溃,会丢失这段时间内的所有写操作;

  • fork子进程开销大:若内存数据量大(如10GB),fork过程会阻塞主进程,影响服务可用性;

  • 不适合实时持久化场景(如金融类数据,要求数据零丢失)。

(2)AOF( Append Only File,追加文件持久化)

  • 定义:将Redis的每一条写操作命令(如set、hset、incr),以文本格式追加到.aof文件中;Redis重启时,重新执行.aof文件中的所有命令,恢复数据。

  • 实现原理(触发机制+执行流程+重写机制):

  1. 触发机制:
  • 开启AOF:在redis.conf中配置「appendonly yes」(默认关闭);

  • 命令追加:每执行一条写操作命令,Redis会将命令追加到.aof缓冲区(而非直接写入磁盘);

  • 缓冲区同步到磁盘:通过「appendfsync」配置同步策略(核心,面试高频):

  • appendfsync always:每执行一条写命令,立即将缓冲区数据写入磁盘(最安全,数据零丢失,但IO开销最大,性能最差);

  • appendfsync everysec:每秒将缓冲区数据写入磁盘(默认配置,平衡安全性和性能,最多丢失1秒数据);

  • appendfsync no:由操作系统决定何时将缓冲区数据写入磁盘(最不安全,可能丢失大量数据,性能最好)。

  1. 执行流程:
  • 客户端执行写命令 → 命令被追加到AOF缓冲区 → 按appendfsync策略同步到.aof文件 → Redis重启时,读取.aof文件,逐行执行命令,恢复数据。
  1. AOF重写(核心优化,解决.aof文件过大问题):
  • 问题:AOF文件会随着写操作增多而不断增大,不仅占用磁盘空间,还会导致Redis重启时加载速度变慢;

  • 原理:重写时,Redis会扫描内存中的所有数据,生成对应的写命令(如将100次incr key命令,合并为set key 100),替换原有的.aof文件,减少文件体积;

  • 触发机制:

  • 手动触发:执行bgrewriteaof命令(异步,不阻塞主进程);

  • 自动触发:通过配置「auto-aof-rewrite-percentage 100」(当AOF文件体积比上次重写后增大100%)和「auto-aof-rewrite-min-size 64mb」(AOF文件体积至少64MB),满足两个条件自动触发重写。

  • 优点:

  • 数据一致性高:可配置为每秒同步或每命令同步,最多丢失1秒数据(甚至零丢失);

  • 恢复精度高:记录每一条写命令,重启时可完整恢复数据;

  • 重写机制可优化文件体积,避免磁盘占用过大。

  • 缺点:

  • AOF文件体积比RDB大(文本格式,命令冗余),加载速度比RDB慢;

  • 同步IO开销大(尤其是appendfsync always),性能比RDB略差;

  • 可能出现AOF文件损坏(如磁盘故障),导致数据恢复失败(可通过redis-check-aof工具修复)。

(3)RDB和AOF的对比总结

  • 数据一致性:AOF > RDB(AOF可实现近实时持久化);

  • 文件体积:RDB < AOF;

  • 加载速度:RDB > AOF;

  • 性能开销:RDB(异步)< AOF(同步IO);

  • 适用场景:RDB适合全量备份、灾难恢复;AOF适合实时持久化、数据零丢失场景;生产环境通常组合使用(RDB做全量备份,AOF做增量备份,重启时优先加载AOF,保证数据完整性)。

2. Redis重启时,如何选择加载RDB还是AOF文件?

核心知识点

Redis重启时,加载持久化文件的优先级由配置和文件存在性决定,核心原则是「优先加载AOF文件,保证数据完整性」。

原理详解

  1. 若AOF功能开启(appendonly yes),且.aof文件存在:Redis优先加载AOF文件,因为AOF文件的数据更完整(丢失数据更少);

  2. 若AOF功能关闭(appendonly no),或.aof文件不存在、损坏且无法修复:Redis会加载.rdb文件;

  3. 若既没有.aof文件,也没有.rdb文件:Redis重启后为空白,无任何数据;

补充:若AOF文件损坏,可通过「redis-check-aof --fix 文件名」工具修复,修复后再重启Redis加载。

三、内存管理&淘汰策略(高频)

1. Redis的内存淘汰策略有哪些?各自的原理和适用场景是什么?

核心知识点

Redis是内存数据库,当内存使用达到「maxmemory」(配置文件中设置的最大内存)时,会触发内存淘汰策略,删除部分数据,释放内存,避免内存溢出;Redis 4.0后支持8种淘汰策略,分为三大类。

原理详解(分大类拆解,重点记常用策略)

(1)淘汰策略分类(按淘汰范围)

  1. 只淘汰过期键(3种):
  • volatile-lru:淘汰「过期键」中,最近最少使用(LRU,Least Recently Used)的键;

  • volatile-ttl:淘汰「过期键」中,剩余过期时间(TTL)最短的键;

  • volatile-random:淘汰「过期键」中,随机选择的键;

  • 适用场景:仅希望淘汰过期数据,保留未过期数据(如缓存热点数据,未过期的需保留)。

  1. 淘汰所有键(4种):
  • allkeys-lru:淘汰「所有键」中,最近最少使用的键(最常用策略);

  • allkeys-random:淘汰「所有键」中,随机选择的键;

  • allkeys-lfu:淘汰「所有键」中,最近最不常用(LFU,Least Frequently Used)的键(Redis 4.0新增,比LRU更精准,考虑使用频率);

  • volatile-lfu:淘汰「过期键」中,最近最不常用的键;

  • 适用场景:allkeys-lru适合缓存场景(优先保留热点数据);allkeys-lfu适合使用频率不均匀的场景(如用户访问,部分数据高频访问,部分低频)。

  1. 不淘汰(1种):
  • noeviction:不淘汰任何键,当内存达到maxmemory后,拒绝所有写操作(返回OOM错误),读操作正常;

  • 适用场景:不允许丢失任何数据,如Redis作为数据库使用(非缓存),需手动处理内存溢出。

(2)核心策略原理补充(LRU和LFU,面试重点)

  1. LRU(最近最少使用):
  • 原理:假设“最近使用过的数据,未来被使用的概率更高”,淘汰最久未被使用的数据;

  • Redis实现:Redis并未维护完整的LRU链表(会占用大量内存),而是采用「采样LRU」——每次淘汰时,随机采样N个键(默认5个),淘汰其中最久未使用的键;通过配置「maxmemory-samples」调整采样数量,采样越多,淘汰越精准,但性能开销越大。

  1. LFU(最近最不常用):
  • 原理:在LRU的基础上,增加“使用频率”维度,假设“使用频率越高的数据,未来被使用的概率更高”,淘汰使用频率最低的数据;

  • Redis实现:每个键的内存结构中,用24位记录「使用频率(counter)」,每次访问键时,counter会增加(增加幅度随频率变化),同时counter会定期衰减(避免旧数据长期占用高频率);淘汰时,采样N个键,淘汰counter最小的键。

(3)生产环境常用配置

  • 缓存场景:优先配置「allkeys-lru」(最通用,保留热点数据);

  • 数据访问频率差异大的场景:配置「allkeys-lfu」(更精准淘汰低频数据);

  • 不允许淘汰未过期数据的场景:配置「volatile-lru」。

2. Redis的内存碎片是怎么产生的?如何解决?

核心知识点

内存碎片是指Redis占用的内存大于实际存储数据的内存,导致内存利用率降低;产生原因与内存分配、数据删除、数据结构变化有关,可通过主动整理或配置优化解决。

原理详解

(1)内存碎片产生的原因

  1. 内存分配机制:Redis使用jemalloc(默认)、tcmalloc等内存分配器,这些分配器会按固定大小的内存块(如8B、16B、32B)分配内存;当存储的数据大小与分配的内存块大小不匹配时,会产生碎片(如存储10B数据,分配16B内存块,剩余6B为碎片)。

  2. 数据频繁删除/修改:

  • 删除数据时,Redis采用「惰性释放」(不立即回收内存,留作后续复用),若后续复用的内存块大小与碎片大小不匹配,碎片会一直存在;

  • 数据修改(如String从10B扩容到20B),原内存块无法容纳,需分配新的内存块,原内存块变为碎片。

  1. 数据结构变化:如Hash从ziplist切换为hashtable,原ziplist占用的内存块变为碎片。

(2)解决方法

  1. 主动整理内存:执行「memory purge」命令(Redis 4.0+),主动回收惰性释放的内存碎片(需配合内存分配器支持,如jemalloc);

  2. 重启Redis:重启后,Redis会重新分配内存,碎片会被清除(简单直接,但会导致短暂服务不可用,需提前做好备份);

  3. 优化配置:

  • 合理设置「maxmemory」,避免内存过度使用导致碎片增多;

  • 优化数据结构,尽量使用紧凑的数据结构(如小数据量用ziplist、intset),减少内存分配不匹配的情况;

  • 避免频繁删除、修改大量数据,减少碎片产生。

四、高可用机制(主从、哨兵、集群,高频重点)

1. Redis主从复制的原理是什么?有哪些复制模式?

核心知识点

主从复制是Redis高可用的基础,核心是「主节点(master)数据同步到从节点(slave)」,实现数据备份和读写分离(主写从读);支持全量复制和增量复制两种模式。

原理详解(分复制模式+执行流程)

(1)核心作用

  • 数据备份:从节点是主节点的副本,主节点故障时,从节点可作为备用节点,避免数据丢失;

  • 读写分离:主节点负责写操作,从节点负责读操作,分担主节点压力,提升并发读性能;

  • 水平扩展:通过增加从节点,提升读并发能力。

(2)复制模式(全量复制+增量复制)

1. 全量复制(首次同步,核心流程)

当从节点首次连接主节点,或从节点与主节点断开连接后重新连接(超过复制偏移量),会触发全量复制,流程如下:

  1. 从节点发送「slaveof masterIP masterPort」命令,向主节点发起同步请求;

  2. 主节点收到请求后,执行bgsave命令,生成RDB快照文件,同时将期间的所有写操作记录到「复制缓冲区」;

  3. 主节点将RDB快照文件发送给从节点,从节点接收完成后,清空自身内存,加载RDB文件,恢复数据;

  4. 主节点将复制缓冲区中的写操作命令,逐一向从节点发送,从节点执行这些命令,实现与主节点数据一致;

  5. 同步完成后,主节点后续的写操作,会实时发送给从节点,保持数据同步。

缺点:全量复制会占用大量网络带宽(RDB文件传输)和主/从节点资源(主节点bgsave、从节点加载RDB),适合首次同步。

2. 增量复制(后续同步,核心流程)

全量复制完成后,主节点与从节点保持连接,主节点会将所有写操作实时同步到从节点,这就是增量复制;核心依赖「复制偏移量」和「复制缓冲区」:

  1. 主节点和从节点各自维护一个「复制偏移量」(offset):主节点每执行一次写操作,offset+1;从节点每接收并执行一次主节点的命令,offset+1;

  2. 主节点维护一个「复制缓冲区」(环形缓冲区),存储最近执行的写操作命令;

  3. 若从节点因网络波动短暂断开连接,重新连接后,会向主节点发送自己的offset;

  4. 主节点判断从节点的offset是否在复制缓冲区的范围内:

  • 若在范围内:主节点将offset之后的命令发送给从节点,从节点执行,完成增量同步;

  • 若不在范围内(缓冲区已覆盖该offset):触发全量复制。

(3)注意事项

  • 主节点默认开启写操作,从节点默认只读(slave-read-only yes),不可执行写操作;

  • 一个主节点可对应多个从节点,一个从节点只能对应一个主节点(不支持链式复制,Redis 4.0+支持从节点作为其他从节点的主节点,但不推荐);

  • 复制过程中,主节点的写操作不会阻塞(bgsave异步执行,复制缓冲区缓存命令),从节点加载RDB时会阻塞,无法处理读请求。

2. Redis哨兵(Sentinel)的原理和作用是什么?核心流程有哪些?

核心知识点

哨兵是Redis的高可用解决方案,核心作用是「监控主从节点、自动故障切换、通知客户端」,解决主从复制中主节点故障后需手动切换的问题;哨兵本身是一个分布式集群,至少需要3个哨兵节点(避免单点故障)。

原理详解

(1)核心作用

  1. 监控(Monitoring):哨兵节点实时监控主节点(master)和从节点(slave)的运行状态(是否在线、是否正常响应);

  2. 故障检测(Failure Detection):当主节点故障时,哨兵集群会通过“投票”机制,判定主节点为「客观下线」(ODOWN);

  3. 自动故障切换(Automatic Failover):主节点下线后,哨兵集群从所有从节点中选举一个作为新的主节点,将其他从节点切换为新主节点的从节点,同时通知客户端新的主节点地址;

  4. 通知(Notification):当节点状态变化(主节点下线、故障切换完成)时,哨兵会通过配置的脚本,通知管理员或其他系统(如监控系统)。

(2)核心流程(故障检测+故障切换)

1. 故障检测(主观下线+客观下线)
  1. 主观下线(SDOWN):单个哨兵节点检测到主节点未响应(如ping命令无回复),且超过「down-after-milliseconds」配置的时间(默认30000ms),则判定主节点为主观下线;

  2. 客观下线(ODOWN):单个哨兵判定主节点主观下线后,会向其他哨兵节点发送请求,询问是否也认为该主节点下线;当超过「quorum」(配置的法定人数,默认1)个哨兵判定主节点主观下线时,主节点被判定为客观下线(确认故障)。

2. 故障切换(核心步骤)
  1. 选举哨兵leader:哨兵集群中,通过Raft算法选举一个哨兵作为leader,由leader负责执行故障切换(避免多个哨兵同时执行切换,导致混乱);

  2. 选择新主节点:leader哨兵从所有健康的从节点中,选择一个作为新的主节点,选择规则(优先级从高到低):

  • 优先选择「slave-priority」(从节点优先级)最高的从节点;

  • 优先级相同,选择复制偏移量最大的从节点(数据最完整);

  • 偏移量相同,选择运行时间最久的从节点;

  • 以上都相同,选择节点ID最小的从节点。

  1. 执行切换:
  • 向新主节点发送「slaveof no one」命令,让其成为主节点;

  • 向其他从节点发送「slaveof 新主节点IP 新主节点端口」命令,让它们切换为新主节点的从节点;

  • 记录原主节点的信息,当原主节点恢复后,将其切换为新主节点的从节点(不再作为主节点)。

  1. 通知客户端:哨兵将新主节点的地址通知给所有连接的客户端,客户端后续的写操作会指向新主节点。

(3)哨兵集群配置要点

  • 哨兵集群至少3个节点(奇数个,避免投票平局),每个哨兵节点配置相同的监控规则;

  • 配置「quorum」:建议设置为哨兵节点数的一半+1(如3个哨兵,quorum=2),保证故障判定的准确性;

  • 哨兵节点与主从节点之间、哨兵节点之间,需保证网络连通(避免哨兵无法监控节点)。

3. Redis Cluster(集群)的原理是什么?如何实现高可用和水平扩展?

核心知识点

Redis Cluster是Redis的分布式解决方案,核心是「分片存储+主从复制」,将数据分片存储在多个节点上,实现水平扩展(支持海量数据),同时每个分片节点有主从备份,实现高可用;集群至少需要3个主节点,每个主节点至少1个从节点(共6个节点)。

原理详解

(1)核心设计:分片存储(一致性哈希)

  1. 哈希槽(Hash Slot):Redis Cluster将整个键空间分为16384个哈希槽(0-16383),每个主节点负责一部分哈希槽(如3个主节点,分别负责0-5460、5461-10922、10923-16383);

  2. 键的分配规则:对键进行CRC16哈希计算,得到哈希值后,对16384取模,得到对应的哈希槽,该键就存储在负责该哈希槽的主节点上;

  • 示例:key=user:123,CRC16(user:123) = 12345,12345 % 16384 = 12345,若该哈希槽由主节点A负责,则key存储在A节点;
  1. 优势:哈希槽固定,可灵活调整主节点负责的哈希槽(如新增主节点时,将其他主节点的部分哈希槽迁移到新节点),实现水平扩展。

(2)高可用实现(主从复制+自动故障切换)

  1. 节点结构:每个主节点(master)对应至少1个从节点(slave),主节点负责哈希槽的读写操作,从节点负责备份主节点的数据;

  2. 故障检测:集群中所有节点互相监控(通过Gossip协议交换节点状态),当某个主节点故障时,其从节点会检测到,并发起故障切换;

  3. 故障切换流程:

  • 主节点故障后,其从节点中会通过选举(类似哨兵的Raft算法),选出一个作为新的主节点;

  • 新主节点接管原主节点的所有哈希槽,集群中其他节点更新哈希槽与主节点的对应关系;

  • 原主节点恢复后,成为新主节点的从节点。

(3)客户端访问原理

  1. 客户端首次连接集群时,会获取集群的「哈希槽分配信息」(所有主节点及其负责的哈希槽);

  2. 客户端对键进行哈希计算,确定对应的哈希槽和主节点,直接向该主节点发送请求;

  3. 若客户端发送请求的节点,不负责该键的哈希槽,节点会返回「MOVED」命令,告知客户端该键对应的正确主节点地址,客户端收到后,重新向正确节点发送请求(一次重定向);

  4. 若哈希槽正在迁移(如扩容、缩容),节点会返回「ASK」命令,告知客户端临时的主节点地址,客户端临时向该节点发送请求。

(4)集群的优势与局限

优势:

  • 水平扩展:支持新增主节点,分担哈希槽,突破单节点内存限制;

  • 高可用:每个主节点有从节点备份,故障自动切换,不影响服务;

  • 分布式部署:节点可分布在不同服务器,提升并发处理能力。

局限:

  • 不支持跨节点事务(如MULTI/EXEC命令,只能在单个节点内执行);

  • 不支持跨节点的聚合操作(如sinter、zunion,只能在单个节点内执行);

  • 配置和维护比主从、哨兵更复杂。