当前位置: 首页 > news >正文

Redis面试精讲 Day 10:Redis数据结构底层实现原理

【Redis面试精讲 Day 10】Redis数据结构底层实现原理

文章标签

Redis,数据结构,底层原理,面试技巧,源码分析,内存优化

文章简述

本文是"Redis面试精讲"系列第10天,深入解析Redis核心数据结构的底层实现机制。文章详细讲解SDS、字典、跳跃表等关键数据结构的设计原理,分析不同数据类型在不同场景下的编码转换策略。提供Redis源码级分析及内存优化实践,包含5个高频面试题深度解析和微博热搜榜案例实现。通过对比不同版本的实现差异,帮助读者透彻理解Redis高效存储背后的秘密,掌握面试中数据结构相关问题的回答技巧。


开篇引言

Redis的卓越性能很大程度上源于其精心设计的数据结构。今天我们将深入Redis内核,探索String、Hash、List等数据类型背后的实现原理,这是考察Redis底层机制的核心面试内容。

一、概念解析:Redis对象系统

1.1 Redis对象结构

所有Redis键值都存储为redisObject结构:

字段类型描述
type4bits数据类型(String/Hash/List等)
encoding4bits编码方式(raw/int/ht等)
lru24bits最近访问时间/LFU计数
refcount32bits引用计数
ptrvoid*指向实际数据的指针
// redisObject源码定义(redis.h)
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS;
int refcount;
void *ptr;
} robj;

1.2 数据类型与编码映射

数据类型可能编码适用条件
Stringint/embstr/raw数值/短字符串/长字符串
Listziplist/quicklist元素少/元素多
Hashziplist/hashtable字段少/字段多
Setintset/hashtable纯数字/混合类型
ZSetziplist/skiplist元素少/元素多

二、原理剖析:核心数据结构

2.1 SDS(简单动态字符串)

struct sdshdr {
int len;    // 已用长度
int free;   // 剩余空间
char buf[]; // 字节数组
};

特性优势:

  1. O(1)时间复杂度获取长度
  2. 杜绝缓冲区溢出
  3. 减少内存重分配次数
  4. 二进制安全

2.2 字典(hashtable)

Redis使用渐进式rehash的双哈希表实现:

typedef struct dict {
dictType *type;
dictht ht[2];    // 两个哈希表
long rehashidx;  // rehash进度
} dict;

rehash过程:

  1. 准备ht[1]空间
  2. 维持索引计数器rehashidx
  3. 每次操作迁移一个桶
  4. 完成时切换主表

2.3 跳跃表(skiplist)

ZSet的底层实现之一,平均O(logN)复杂度:

typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode;

三、代码实现:编码转换实战

3.1 观察编码转换

# 字符串编码演示
127.0.0.1:6379> SET num 123
OK
127.0.0.1:6379> OBJECT ENCODING num
"int"127.0.0.1:6379> SET short "hello"
OK
127.0.0.1:6379> OBJECT ENCODING short
"embstr"127.0.0.1:6379> SET long "a very long string..."
OK
127.0.0.1:6379> OBJECT ENCODING long
"raw"# Hash编码转换
127.0.0.1:6379> HMSET small f1 v1 f2 v2
OK
127.0.0.1:6379> OBJECT ENCODING small
"ziplist"127.0.0.1:6379> HMSET big f1 v1 ... f65 v65
OK
127.0.0.1:6379> OBJECT ENCODING big
"hashtable"

3.2 配置编码阈值

# redis.conf配置示例
hash-max-ziplist-entries 512  # Hash字段数阈值
hash-max-ziplist-value 64     # 字段值长度阈值
list-max-ziplist-size -2      # Quicklist节点大小
set-max-intset-entries 512    # intset元素数阈值

四、面试题解析

4.1 Redis为什么自研SDS而不直接用C字符串?

面试官意图:考察对Redis设计哲学的理解

参考答案

  1. 安全性:
  • 避免缓冲区溢出
  • 二进制安全(含\0的数据)
  1. 性能:
  • O(1)获取长度
  • 空间预分配(减少alloc)
  1. 兼容性:
  • 兼容部分C字符串函数
  • 自动追加\0

4.2 渐进式rehash如何保证操作不被阻塞?

考察点:高可用设计思想

结构化回答

  1. 双表机制:
  • 同时维护ht[0]和ht[1]
  • 查询操作访问双表
  1. 分步迁移:
  • 每次操作迁移1个桶
  • 定时任务辅助迁移
  1. 最终一致性:
  • 迁移完成前新旧数据共存
  • 保证服务不中断

4.3 ZSet为什么同时使用跳跃表和字典?

解决方案

  1. 跳跃表:
  • 范围操作高效(ZRANGE)
  • 有序性维护
  1. 字典:
  • O(1)单点查询(ZSCORE)
  • 成员唯一性保证
  1. 数据共享:
  • 元素对象被两者引用
  • 通过refcount管理

五、实践案例:微博热搜榜

5.1 基于ZSet的实现

// 添加热搜事件
public void addHotSearch(String event, double score) {
jedis.zadd("hotsearch", score, event);
}// 获取TOP10热搜
public List<String> getTop10() {
return jedis.zrevrange("hotsearch", 0, 9);
}// 模拟热度计算
public void calculateHot() {
Set<Tuple> events = jedis.zrangeWithScores("hotsearch", 0, -1);
for (Tuple event : events) {
double newScore = event.getScore() * 0.9 + random.nextDouble();
jedis.zadd("hotsearch", newScore, event.getElement());
}
}

5.2 内存优化技巧

  1. 缩短元素key长度(如用id代替长文本)
  2. 控制ZSet元素数量(定期清理低分项)
  3. 适当调整zset-max-ziplist-entries
  4. 使用32位redis降低指针开销

六、技术对比:不同版本优化

特性Redis 3.0Redis 5.0Redis 7.0
Listziplist+linkedlistquicklist优化quicklist节点
Hashziplist+hashtable相同优化hashtable扩容
Stream新增优化内存回收

七、面试答题模板

当被问到数据结构实现时

  1. 说明redisObject的核心作用
  2. 描述具体数据结构实现(如SDS)
  3. 分析编码转换机制
  4. 结合业务场景举例

示例回答
“Redis所有数据都封装在redisObject中,以String为例,当存储数值时会采用int编码节省空间。我们微博系统就用ZSet实现热搜榜,它底层采用跳跃表+字典的组合,既支持快速排序又保证高效查询…”

八、总结与预告

今日核心知识点

  1. redisObject的结构与作用
  2. SDS、字典、跳跃表等核心结构
  3. 编码转换规则与配置
  4. 生产环境的内存优化

面试官喜欢的回答要点

  1. 清楚不同编码的区别
  2. 理解数据结构的应用场景
  3. 能分析版本演进改进
  4. 掌握实际优化经验

明日预告:Day 11将讲解Redis主从复制原理与实践,包括全量/增量同步等核心机制。

进阶学习资源

  1. Redis源码仓库
  2. 《Redis设计与实现》
  3. Redis官方内存优化指南
http://www.dtcms.com/a/313017.html

相关文章:

  • RK3568 AB分区+OTA升级(Linux)
  • 在微信小程序中使用本地存储的方法
  • 《volatile 与 synchronized 底层实现与性能比较》
  • ubuntu syslog中appindicator报错解决
  • 深入理解C++缺省参数:从基础用法到最佳实践
  • 8-verilog-串口接收与发送模块
  • Python切片命名技术详解:提升代码可读性与维护性的专业实践
  • linux下jvm之jstack的使用
  • 洛谷——P1048 [NOIP 2005 普及组] 采药
  • 【openlayers框架学习】九:openlayers中的交互类(select和draw)
  • GaussDB SQL执行计划详解
  • Rust: 获取 MAC 地址方法大全
  • Zama的使命
  • 【读论文】KAG-Thinker:升级版RAG 框架
  • 推荐系统学习笔记(九)曝光过滤 Bloom Filter
  • 【DL学习笔记】感受野(Receptive Field)
  • 映射网络驱动器后,重启映射就没了
  • 王之凝视 免安中文 离线运行版
  • 【Bluetooth】【Transport层篇】第四章 基于基础UART的蓝牙硬件发送协议 UART H4 Transport详解
  • wordpress登陆前登陆后显示不同的顶部菜单
  • 前后端交流
  • Go语言声明变量
  • mybatis实现固定三层树形结构的嵌套查询
  • 怎么修改论文格式呢?提供一份论文格式模板
  • 【ProtoBuf】初识ProtoBuf
  • 【UE5医学影像可视化】读取本地Dicom生成VolumeTexture,实现2D显示和自动翻页
  • 关于记录一下“bug”,在做图片上传的时候出现的小问题
  • B3953 [GESP202403 一级] 找因数
  • 大模型智能体(Agent)技术全景:架构演进、协作范式与应用前沿
  • Python Dash 全面讲解