【Redis面试高频】- Redis数据结构的底层设计是什么呢?了解过吗?

本文深入解析Redis中字符串、列表、哈希、集合和有序集合五种对象类型的底层数据结构及编码方式,包括简单动态字符串(SDS)、链表、字典、跳跃表、整数集合和压缩列表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【Redis面试高频】- Redis数据结构

Redis是一个基于内存中的数据结构存储系统,可以用作数据库、缓存和消息中间件。

Redis支持五种常见对象类型:字符串(String)、列表(List)、集合(Set)、有序集合(ZSet)和哈希(Hash).

1 对象类型和编码

Redis使用对象来存储键和值的,在Redis中,每个对象都由redisObject结构表示redisObject结构主要包含三个属性:typeencodingptr

typedef struct redisObject {
    // 类型
    unsigned type:4;
    // 编码
    unsigned encoding:4;
    // 底层数据结构的指针
    void *ptr;
} robj;

其中type属性记录了对象的类型,对于Redis来说,键对象总是字符串类型,值对象可以是任意支持的类型。因此,当我们说Redis键采用哪种对象类型的时候,指的是对应的值采用哪种对象类型。

类型常量对象类型名称
REDIS_STRING字符串对象
REDIS_LIST列表对象
REDIS_HASH哈希对象
REDIS_SET集合对象
REDIS_ZSET有序集合对象

\*ptr属性指向了对象的底层数据结构,而这些数据结构由encoding属性决定

编码常量编码对应的底层数据结构
REDIS_ENCODING_INTlong类型的整数
REDIS_ENCODING_EMBSTRemstr编码的简单动态字符串
REDIS_ENCODING_RAW简单动态字符串
REDIS_ENCODING_HT字典
REDIS_ENCODING_LINKEDLIST双端链表
REDIS_ENCODING_ZIPLIST压缩列表
REDIS_ENCODING_INTSET整数集合
REDIS_ENCODING_SKIPLIST跳跃表和字典

之所以由encoding属性来决定对象的底层数据结构,是为了实现同一对象类型,支持不同的底层实现。这样就能在不同场景下,使用不同的底层数据结构,进而极大提升Redis的灵活性和效率。

2 String的底层结构

如果一个字符串对象保存的是不超过long类型的整数值,此时编码类型即为int,其底层数据结构直接就是long类型。例如执行set number 10086,就会创建int编码的字符串对象作为number键的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gK0GrK45-1620883752533)(imgs\217.png)]

如果字符串对象保存的是一个长度大于39字节的字符串,此时编码类型即为raw,其底层数据结构是简单动态字符串(SDS);如果长度小于等于39个字节,编码类型则为embstr,底层数据结构就是embstr编码SDS。下面,我们详细理解下什么是简单动态字符串。

简单动态字符串

SDS定义

Redis中,使用sdshdr数据结构表示SDS

struct sdshdr {
    // 字符串长度
    int len;
    // buf数组中未使用的字节数
    int free;
    // 字节数组,用于保存字符串
    char buf[];
};

SDS遵循了C字符串以空字符结尾的惯例,保存空字符的1字节不会计算在len属性里面。例如,Redis这个字符串在SDS里面的数据可能是如下形式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9H4IQwfP-1620883752546)(imgs\218.png)]

SDS与C字符串的区别

C语言使用长度为N+1的字符数组来表示长度为N的字符串,并且字符串的最后一个元素是空字符\0Redis采用SDS相对于C字符串有如下几个优势:

  1. 常数复杂度获取字符串长度
  2. 杜绝缓冲区溢出
  3. 减少修改字符串时带来的内存重分配次数
  4. 二进制安全

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4UYfuZ8e-1620883752548)(imgs\219.png)]

3 list列表的底层数据结构

列表对象的编码可以是linkedlist或者ziplist,对应的底层数据结构是链表和压缩列表

默认情况下,当列表对象保存的所有字符串元素的长度都小于64字节,且元素个数小于512个时,列表对象采用的是ziplist编码,否则使用linkedlist编码。

3.1 linkedlist链表

链表是一种非常常见的数据结构,提供了高效的节点重排能力以及顺序性的节点访问方式。在Redis中,每个链表节点使用listNode结构表示:

typedef struct listNode {
    // 前置节点
    struct listNode *prev;
    // 后置节点
    struct listNode *next;
    // 节点值
    void *value;
} listNode

多个listNode通过prevnext指针组成双端链表,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IU76dpxt-1620883752554)(imgs\220.png)]

为了操作起来比较方便,Redis使用了list结构持有链表。

typedef struct list {
    // 表头节点
    listNode *head;
    // 表尾节点
    listNode *tail;
    // 链表包含的节点数量
    unsigned long len;
    // 节点复制函数
    void *(*dup)(void *ptr);
    // 节点释放函数
    void (*free)(void *ptr);
    // 节点对比函数
    int (*match)(void *ptr, void *key);
} list;

list结构为链表提供了表头指针head、表尾指针tail,以及链表长度计数器len,而dupfreematch成员则是实现多态链表所需类型的特定函数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-96M2TTuo-1620883752557)(imgs\221.png)]

Redis链表实现的特征总结如下:

  1. 双端:链表节点带有prevnext指针,获取某个节点的前置节点和后置节点的复杂度都是O(n)
  2. 无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。
  3. 带表头指针和表尾指针:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)
  4. 带链表长度计数器:程序使用list结构的len属性来对list持有的节点进行计数,程序获取链表中节点数量的复杂度为O(1)
  5. 多态:链表节点使用void*指针来保存节点值,可以保存各种不同类型的值。

3.2 ziplist压缩列表

压缩列表(ziplist)是列表键和哈希键的底层实现之一。压缩列表主要目的是为了节约内存,是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p72wih95-1620883752559)(imgs\222.png)]

如上图所示,压缩列表记录了各组成部分的类型、长度以及用途。

属性类型长度用途
zlbytesuint_32_t4字节记录整个压缩列表占用的内存字节数
zltailuint_32_t4字节记录压缩列表表尾节点距离起始地址有多少字节,通过这个偏移量,程序无需遍历整个压缩列表就能确定表尾节点地址
zlenuint_16_t2字节记录压缩列表包含的节点数量
entryX列表节点不定压缩列表的各个节点,节点长度由保存的内容决定
zlenduint_8_t1字节特殊值(0xFFF),用于标记压缩列表末端

4 hash对象

哈希对象的编码可以是ziplist或者hashtable

4.1 hash-ziplist(压缩列表)

ziplist底层使用的是压缩列表实现,上文已经详细介绍了压缩列表的实现原理。每当有新的键值对要加入哈希对象时,先把保存了键的节点推入压缩列表表尾,然后再将保存了值的节点推入压缩列表表尾。比如,我们执行如下三条HSET命令:

HSET profile name "tom"
HSET profile age 25
HSET profile career "Programmer"

如果此时使用ziplist编码,那么该Hash对象在内存中的结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-maD2rBqA-1620883752562)(imgs\223.png)]

4.2 hash-hashtable(字典)

hashtable编码的哈希对象使用字典作为底层实现。字典是一种用于保存键值对的数据结构,Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,每个哈希表节点保存的就是一个键值对。

a. 哈希表

Redis使用的哈希表由dictht结构定义:

typedef struct dictht{
    // 哈希表数组
    dictEntry **table;

    // 哈希表大小
    unsigned long size;

    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size-1
    unsigned long sizemask;

    // 该哈希表已有节点数量
    unsigned long used;
} dictht

table属性是一个数组,数组中的每个元素都是一个指向dictEntry结构的指针,每个dictEntry结构保存着一个键值对。size属性记录了哈希表的大小,即table数组的大小。used属性记录了哈希表目前已有节点数量。sizemask总是等于size-1,这个值主要用于数组索引。比如下图展示了一个大小为4的空哈希表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zgIVMQnG-1620883752563)(imgs\224.png)]

b.哈希表结点

哈希表节点使用dictEntry结构表示,每个dictEntry结构都保存着一个键值对:

typedef struct dictEntry {
    // 键
    void *key;

    // 值
    union {
        void *val;
        unit64_t u64;
        nit64_t s64;
    } v;

    // 指向下一个哈希表节点,形成链表
    struct dictEntry *next;
} dictEntry;

key属性保存着键值对中的键,而v属性则保存了键值对中的值。值可以是一个指针,一个uint64_t整数或者是int64_t整数。next属性指向了另一个dictEntry节点,在数组桶位相同的情况下,将多个dictEntry节点串联成一个链表,以此来解决键冲突问题。(链地址法)

c.字典

Redis字典由dict结构表示:

typedef struct dict {

    // 类型特定函数
    dictType *type;

    // 私有数据
    void *privdata;

    // 哈希表
    dictht ht[2];

    //rehash索引
    // 当rehash不在进行时,值为-1
    int rehashidx;
}

ht是大小为2,且每个元素都指向dictht哈希表。一般情况下,字典只会使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。rehashidx记录了rehash的进度,如果目前没有进行rehash,值为-1。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g1XpVMLQ-1620883752568)(imgs\225.png)]

d.rehash

为了使hash表的负载因子(ht[0]).used/ht[0]).size)维持在一个合理范围,当哈希表保存的元素过多或者过少时,程序需要对hash表进行相应的扩展和收缩。rehash(重新散列)操作就是用来完成hash表的扩展和收缩的。rehash的步骤如下:

  1. ht[1]哈希表分配空间
    1. 如果是扩展操作,那么ht[1]的大小为第一个大于ht[0].used*2的2n。比如ht[0].used=5,那么此时ht[1]的大小就为16。(大于10的第一个2n的值是16)
    2. 如果是收缩操作,那么ht[1]的大小为第一个大于ht[0].used的2n。比如ht[0].used=5,那么此时ht[1]的大小就为8。(大于5的第一个2n的值是8)
  2. 将保存在ht[0]中的所有键值对rehash到ht[1]中。
  3. 迁移完成之后,释放掉ht[0],并将现在的ht[1]设置为ht[0],在ht[1]新创建一个空白哈希表,为下一次rehash做准备。

哈希表的扩展和收缩时机

  1. 当服务器没有执行BGSAVE或者BGREWRITEAOF命令时,负载因子大于等于1触发哈希表的扩展操作。
  2. 当服务器在执行BGSAVE或者BGREWRITEAOF命令,负载因子大于等于5触发哈希表的扩展操作。
  3. 当哈希表负载因子小于0.1,触发哈希表的收缩操作。
e.渐进式rehash

前面讲过,扩展或者收缩需要将ht[0]里面的元素全部rehash到ht[1]中,如果ht[0]元素很多,显然一次性rehash成本会很大,从影响到Redis性能。为了解决上述问题,Redis使用了渐进式rehash技术,具体来说就是分多次,渐进式地将ht[0]里面的元素慢慢地rehash到ht[1]。下面是渐进式rehash的详细步骤:

  1. ht[1]分配空间。
  2. 在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash正式开始。
  3. 在rehash进行期间,每次对字典执行添加、删除、查找或者更新时,除了会执行相应的操作之外,还会顺带将ht[0]rehashidx索引位上的所有键值对rehash到ht[1]中,rehash完成之后,rehashidx值加1。
  4. 随着字典操作的不断进行,最终会在啊某个时刻迁移完成,此时将rehashidx值置为-1,表示rehash结束。

渐进式rehash一次迁移一个桶上所有的数据,设计上采用分而治之的思想,将原本集中式的操作分散到每个添加、删除、查找和更新操作上,从而避免集中式rehash带来的庞大计算。

因为在渐进式rehash时,字典会同时使用ht[0]ht[1]两张表,所以此时对字典的删除、查找和更新操作都可能会在两个哈希表进行。比如,如果要查找某个键时,先在ht[0]中查找,如果没找到,则继续到ht[1]中查找。

hash对象中的hashtable

HSET profile name "tom"
HSET profile age 25
HSET profile career "Programmer"

还是上述三条命令,保存数据到Redis的哈希对象中,如果采用hashtable编码保存的话,那么该Hash对象在内存中的结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B026Z2E0-1620883752569)(imgs\226.png)]

当哈希对象保存的所有键值对的键和值的字符串长度都小于64个字节,并且数量小于512个时,使用ziplist编码,否则使用hashtable编码。

5 set集合对象

集合对象的编码可以是intset或者hashtable。当集合对象保存的元素都是整数,并且个数不超过512个时,使用intset编码,否则使用hashtable编码。

5.1 set-intset

intset编码的集合对象底层使用整数集合实现。

整数集合(intset)是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_tint32_t或者int64_t的整数值,并且保证集合中的数据不会重复。Redis使用intset结构表示一个整数集合。

typedef struct intset {
    // 编码方式
    uint32_t encoding;
    // 集合包含的元素数量
    uint32_t length;
    // 保存元素的数组
    int8_t contents[];
} intset;

contents数组是整数集合的底层实现:整数集合的每个元素都是contents数组的一个数组项,各个项在数组中按值大小从小到大有序排列,并且数组中不包含重复项。虽然contents属性声明为int8_t类型的数组,但实际上,contents数组不保存任何int8_t类型的值,数组中真正保存的值类型取决于encoding。如果encoding属性值为INTSET_ENC_INT16,那么contents数组就是int16_t类型的数组,以此类推。

当新插入元素的类型比整数集合现有类型元素的类型大时,整数集合必须先升级,然后才能将新元素添加进来。这个过程分以下三步进行。

  1. 根据新元素类型,扩展整数集合底层数组空间大小。
  2. 将底层数组现有所有元素都转换为与新元素相同的类型,并且维持底层数组的有序性。
  3. 将新元素添加到底层数组里面。

还有一点需要注意的是,整数集合不支持降级,一旦对数组进行了升级,编码就会一直保持升级后的状态。

举个栗子,当我们执行SADD numbers 1 3 5向集合对象插入数据时,该集合对象在内存的结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FUocotN8-1620883752571)(imgs\227.png)]

5.2 set-hashtable(字典)

hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象对应一个集合元素,字典的值都是NULL。当我们执行SADD fruits "apple" "banana" "cherry"向集合对象插入数据时,该集合对象在内存的结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuzyQExK-1620883752572)(imgs\228.png)]

6 zset有序集合对象

有序集合的编码可以是ziplist或者skiplist。当有序集合保存的元素个数小于128个,且所有元素成员长度都小于64字节时,使用ziplist编码,否则,使用skiplist编码。

6.1 zset-ziplist

ziplist编码的有序集合使用压缩列表作为底层实现,每个集合元素使用两个紧挨着一起的两个压缩列表节点表示,第一个节点保存元素的成员(member),第二个节点保存元素的分值(score)

压缩列表内的集合元素按照分值从小到大排列。如果我们执行ZADD price 8.5 apple 5.0 banana 6.0 cherry命令,向有序集合插入元素,该有序集合在内存中的结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-avdNUrla-1620883752574)(E:\笔记\面试高频\imgs\229.png)]

6.2 zset-skiplist

skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表。

typedef struct zset {
    zskiplist *zs1;
    dict *dict;
}

继续介绍之前,我们先了解一下什么是跳跃表。

a.跳跃表

跳跃表(skiplist)是一种有序的数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。Redis的跳跃表由zskiplistNodezskiplist两个结构定义,zskiplistNode结构表示跳跃表节点,zskiplist保存跳跃表节点相关信息,比如节点的数量,以及指向表头和表尾节点的指针等。

跳跃表节点 zskiplistNode

跳跃表节点zskiplistNode结构定义如下:

typedef struct zskiplistNode {
    // 后退指针
    struct zskiplistNode *backward;
    // 分值
    double score;
    // 成员对象
    robj *obj;
    // 层
    struct zskiplistLevel {
        // 前进指针
        struct zskiplistNode *forward;
        // 跨度
        unsigned int span;
    } level[];
} zskiplistNode;

下图是一个层高为5,包含4个跳跃表节点(1个表头节点和3个数据节点)组成的跳跃表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rMPabzmI-1620883752575)(imgs\230.png)]

每次创建一个新的跳跃表节点的时候,会根据幂次定律(越大的数出现的概率越低)随机生成一个1-32之间的值作为当前节点的"层高"。每层元素都包含2个数据,前进指针和跨度。

  1. 前进指针
    每层都有一个指向表尾方向的前进指针,用于从表头向表尾方向访问节点。
  2. 跨度
    层的跨度用于记录两个节点之间的距离。
  3. 后退指针(BW)
    节点的后退指针用于从表尾向表头方向访问节点,每个节点只有一个后退指针,所以每次只能后退一个节点。
  4. 分值和成员
    节点的分值(score)是一个double类型的浮点数,跳跃表中所有节点都按分值从小到大排列。节点的成员(obj)是一个指针,指向一个字符串对象。在跳跃表中,各个节点保存的成员对象必须是唯一的,但是多个节点的分值确实可以相同。

需要注意的是,表头节点不存储真实数据,并且层高固定为32,从表头节点第一个不为NULL最高层开始,就能实现快速查找

图是一个完整的跳跃表结构示例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h042sv0W-1620883752579)(imgs\231.png)]

总的来说,Redis底层数据结构主要包括简单动态字符串(SDS)、链表、字典、跳跃表、整数集合和压缩列表六种类型,并且基于这些基础数据结构实现了字符串对象、列表对象、哈希对象、集合对象以及有序集合对象五种常见的对象类型。每一种对象类型都至少采用了2种数据编码,不同的编码使用的底层数据结构也不同。

b.有序集合对象的skiplist实现

前面讲过,skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表

typedef struct zset {
    zskiplist *zs1;
    dict *dict;
}

zset结构中的zs1跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素。通过跳跃表,可以对有序集合进行基于score的快速范围查找。zset结构中的dict字典为有序集合创建了从成员到分值的映射,字典的键保存了成员,字典的值保存了分值。通过字典,可以用O(1)复杂度查找给定成员的分值。

假如还是执行ZADD price 8.5 apple 5.0 banana 6.0 cherry命令向zset保存数据,如果采用skiplist编码方式的话,该有序集合在内存中的结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rkhdtq7d-1620883752581)(imgs\232.png)]

<think>好的,我现在需要整理一下关于Redis的常见高频面试题,帮助用户准备面试。首先,我得回忆一下自己学过的Redis知识,以及之前看过的面试题。可能的问题包括基本概念、数据结构、持久化、高可用、应用场景等等。 首先,Redis的基本概念是什么?它是一个内存数据库,支持多种数据结构,比如字符串、哈希、列表、集合、有序集合。还有,Redis是单线程的,但为什么性能还这么高呢?可能是因为它基于内存操作,并且采用了非阻塞I/O多路复用机制。 接下来是数据结构部分。常见的数据结构需要详细说明,比如String、Hash、List、Set、Zset,以及它们的使用场景。比如String适合缓存,Hash适合存储对象,List可以做队列,Zset可以做排行榜。 然后是持久化机制,RDB和AOF的区别。RDB是快照,适合备份,恢复快,但可能丢失数据。AOF记录每次写操作,更安全,但文件大,恢复慢。混合持久化可能是在Redis 4.0之后引入的,结合了两者的优点。 高可用方面,主从复制、哨兵、集群这三种模式需要解释清楚。主从复制实现数据同步,哨兵负责监控和故障转移,集群则提供数据分片和高可用。 缓存相关的问题,比如缓存穿透、击穿、雪崩的区别和解决方案。穿透是指查询不存在的数据,可以用布隆过滤器或者缓存空值。击穿是热点key过期导致大量请求到数据库,可以用互斥锁或者永不过期。雪崩是大量key同时过期,可以随机设置过期时间或者集群部署。 事务和管道。Redis的事务是通过MULTI/EXEC命令实现的,但不支持回滚。管道则是将多个命令打包发送,减少网络开销,提升性能。 内存淘汰策略,比如LRU、LFU、随机淘汰等,需要列举不同的策略及其应用场景。 还有可能问到Redis的线程模型,单线程如何处理并发连接,为什么单线程效率高。此外,Redis6.0引入了多线程IO,但执行命令还是单线程的,这点需要说明。 应用场景方面,除了缓存,还有分布式锁、消息队列(使用List或者Stream)、会话存储、计数器、排行榜等。 其他可能的问题包括Redis的过期策略(惰性删除和定期删除)、与Memcached的区别、如何实现分布式锁、Redis的集群数据分片方式(哈希槽)等。 需要确保每个问题都有简明扼要的回答,并且涵盖高频考点。可能还需要注意一些细节,比如持久化的配置参数,哨兵选举过程,集群的节点通信等。不过用户可能需要的是常见问题,所以不需要太深入,但重点要突出。 可能还需要检查是否有遗漏的重要问题,比如Redis的事务ACID属性,是否支持原子性,或者Redis的并发竞争问题如何解决。此外,大key和热key的问题,如何处理,也是常见的生产环境问题。 总结下来,应该分几个大类来组织问题,每个大类下列举几个具体的问题和简要答案。这样用户看起来清晰,容易复习。</think>以下是 Redis 常见高频面试题及简要解析,助你快速掌握核心知识点: --- ### **一、基础概念** 1. **Redis 是什么?** Redis 是基于内存的键值数据库,支持多种数据结构(String、Hash、List、Set、ZSet等),常用于缓存、消息队列、分布式锁等场景。 2. **Redis 为什么快?** - 内存操作,读写无磁盘 I/O 瓶颈。 - 单线程模型避免上下文切换,配合 I/O 多路复用处理高并发。 - 高效数据结构(如跳表、哈希表)。 --- ### **二、数据结构与应用** 1. **Redis 支持哪些数据结构?应用场景?** - **String**:缓存、计数器(如 `INCR`)。 - **Hash**:存储对象(如用户信息)。 - **List**:消息队列、栈(`LPUSH`/`RPOP`)。 - **Set**:去重、交集(共同好友)。 - **ZSet**:排行榜(按分数排序)。 - **BitMap**:位统计(日活用户)。 - **HyperLogLog**:基数统计(UV)。 - **Stream**:消息流(类似 Kafka)。 2. **ZSet 底层实现?** 使用**跳表(SkipList)** + **哈希表**,跳表支持范围查询,哈希表保证 O(1) 复杂度单值查询。 --- ### **三、持久化** 1. **RDB 和 AOF 的区别?** - **RDB**:定时生成内存快照,文件小、恢复快,但可能丢失最后一次快照后的数据。 - **AOF**:记录每次写命令,数据更安全,但文件大、恢复慢。 - **混合持久化(Redis 4.0+)**:结合两者,AOF 文件包含 RDB 头 + 增量 AOF 命令。 2. **AOF 重写的作用?** 压缩 AOF 文件体积,移除冗余命令(如多次修改同一 key),通过读取内存数据生成新 AOF。 --- ### **四、高可用与集群** 1. **主从复制原理?** 从节点连接主节点后发送 `SYNC` 命令,主节点执行 `BGSAVE` 生成 RDB 文件并发送给从节点,后续通过传播写命令保持同步。 2. **哨兵(Sentinel)机制的作用?** 监控主从节点状态,自动故障转移:当主节点宕机时,选举新主节点并通知客户端。 3. **Redis Cluster 如何分片?** 采用**哈希槽(16384 个槽)**,每个节点负责部分槽。数据按 `CRC16(key) % 16384` 分配到对应槽。 --- ### **五、缓存问题** 1. **缓存穿透、击穿、雪崩的区别及解决方案?** - **穿透**:查询不存在的数据。 → 布隆过滤器拦截非法 Key,缓存空值。 - **击穿**:热点 Key 过期后高并发请求压垮数据库。 → 互斥锁(如 `SETNX`)、永不过期 + 异步更新。 - **雪崩**:大量 Key 同时过期。 → 随机过期时间、集群部署、永不过期。 2. **如何保证缓存与数据库一致性?** - 旁路缓存模式(Cache-Aside):先更新库,再删缓存(可能短暂不一致)。 - 延时双删、设置缓存过期时间兜底。 - 最终一致性:通过消息队列异步同步。 --- ### **六、高级特性** 1. **Redis 事务(MULTI/EXEC)的原子性?** 事务中的命令按顺序执行,但非原子(单条命令失败不影响后续执行),不支持回滚。 2. **如何实现分布式锁?** - 使用 `SET key value NX EX` 设置锁(避免死锁)。 - Redlock 算法(多节点部署防止单点故障)。 - 推荐 Redisson 客户端(自动续期、可重入)。 3. **内存淘汰策略有哪些?** - `volatile-lru`/`allkeys-lru`:淘汰最近最少使用的 Key。 - `volatile-lfu`/`allkeys-lfu`:淘汰最不经常使用的 Key。 - `volatile-ttl`:淘汰即将过期的 Key。 - `noeviction`:默认策略,不淘汰,写操作返回错误。 --- ### **七、其他高频问题** 1. **Redis 单线程为什么高效?** 纯内存操作 + 非阻塞 I/O 多路复用(epoll)处理高并发,避免线程切换开销。 2. **Pipeline 的作用?** 批量发送命令,减少网络往返次数,提升批量操作性能(但非原子性)。 3. **大 Key 和热 Key 问题如何解决?** - **大 Key**:拆分(如 Hash 分多个 Key)、异步删除(`UNLINK`)。 - **热 Key**:多级缓存、本地缓存、Key 打散分布。 --- 掌握以上问题后,可进一步结合实际场景和源码深入理解,面试时结合项目经验回答更佳!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mind_programmonkey

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值