你好,我是风一样的树懒,一个工作十多年的后端专家,曾就职京东、阿里等多家互联网头部企业。公众号“吴计可师”,已经更新了近百篇高质量的面试相关文章,喜欢的朋友欢迎关注点赞
上一文《大厂二面面试题居然跟简单的String相关,你能答上来么?》 是讲了使用应用上的区别,这样的回答只能说是及格分了,那么怎样才能高分呢?
下面我们来看看Redis 的 String 类型底层设计。
Redis 的 String 类型底层设计基于 简单动态字符串(Simple Dynamic String, SDS),这是一种专门为高效操作和内存管理优化的数据结构。相较于传统 C 字符串,SDS 在性能、安全性和功能性上有显著提升。以下是其核心实现原理和设计细节:
一、SDS 结构定义
Redis 3.2 之后的版本根据字符串长度优化了 SDS 类型,共有 5 种结构体,以最小化内存占用:
// Redis 5.0+ 的 SDS 结构示例(sdshdr8)
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; // 已用长度(最大 2^8-1)
uint8_t alloc; // 总分配容量(排除头和空终止符)
unsigned char flags;// 类型标识(低3位表示类型,高5位未用)
char buf[]; // 实际数据存储(兼容C字符串)
};
SDS 类型分类
| 类型 | 长度范围 | 适用场景 |
|---|---|---|
sdshdr5 | 0~31 字节 | 极短字符串(已弃用) |
sdshdr8 | 0~255 字节 | 短字符串(如缓存键) |
sdshdr16 | 0~65535 字节 | 中等长度字符串 |
sdshdr32 | 0~4294967295 字节 | 长字符串(如JSON数据) |
sdshdr64 | 超大字符串 | 特殊场景(较少使用) |
二、SDS 的核心优化设计
1. O(1) 时间复杂度获取长度
- 传统 C 字符串:需遍历直到
\0,时间复杂度 O(n)。 - SDS:直接读取
len字段,时间复杂度 O(1)。
2. 杜绝缓冲区溢出
- C 字符串问题:
strcat可能覆盖相邻内存。 - SDS 安全机制:修改前检查
alloc,不足时自动扩容。
3. 二进制安全
- C 字符串限制:依赖
\0结尾,无法存储含\0的数据(如图片、ProtoBuf)。 - SDS 突破:通过
len记录真实长度,可存储任意二进制数据。
4. 内存预分配策略
-
扩容规则:
- 新长度 < 1MB:分配
2 * len的空间。 - 新长度 ≥ 1MB:额外分配 1MB。
- 示例:从 10KB 扩容到 12KB,实际分配 24KB(
alloc = 24K,free = 12K)。
- 新长度 < 1MB:分配
-
惰性空间释放:
- 缩短字符串时不立即回收内存,更新
len并保留free。 - 显式释放 API
sdsRemoveFreeSpace可供调用。
- 缩短字符串时不立即回收内存,更新
三、编码优化策略
Redis 根据 String 的值类型自动选择最优编码,最大化性能和内存效率:
1. int 编码
- 触发条件:字符串表示 64 位有符号整数。
- 内存布局:直接存储在指针位置(无需 SDS 结构)。
void *ptr = (void*)(long)12345; // 直接存储整数 - 优势:节省内存(无需结构体)且支持原子操作(如
INCR)。
2. embstr 编码
- 触发条件:长度 ≤ 44 字节的字符串。
- 内存布局:SDS 结构体与数据连续存储在单块内存。
[ sdshdr8 | data ... | \0 ] - 优势:
- 内存局部性好(减少 CPU 缓存未命中)。
- 减少内存分配次数(一次分配完成)。
3. raw 编码
- 触发条件:长度 > 44 字节的字符串。
- 内存布局:SDS 结构体与数据分两次分配。
[ sdshdrX ] --> [ data ... | \0 ] - 优势:灵活处理大字符串,避免连续内存压力。
四、操作原子性与高性能
1. 原子性操作
- 单命令原子性:如
APPEND、INCR、GETSET。 - 线程安全:Redis 单线程模型确保无竞态条件。
2. 零拷贝优化
- 批量操作:如
SETRANGE直接修改内存,无需创建新字符串。 - 网络传输:通过
writev系统调用合并发送 SDS 数据。
五、实战场景与编码选择
| 场景 | 推荐编码 | 理由 |
|---|---|---|
| 分布式计数器(库存) | int | 节省内存,支持原子增减 |
| 短文本缓存(Token) | embstr | 内存连续,快速访问 |
| 大JSON数据存储 | raw | 避免内存碎片,灵活扩容 |
| 二进制数据(图片) | raw | 二进制安全,支持任意数据格式 |
六、SDS 的演进与性能对比
- Redis 3.2 前:单一 SDS 结构,存在内存浪费。
- Redis 3.2+:多类型 SDS,内存节省 30%~50%。
// sdshdr8(1字节 len + 1字节 alloc + 1字节 flags) // 存储 "hello"(5字节)总占用:3头字节 + 6字节(含\0)= 9字节
最后
现在基于底层Redis 的 String 类型通过 SDS 数据结构 和 智能编码策略,来回答Java的String与Redis的String的区别
相信基于这样的对比,肯定会收到面试官更多的肯定吧?
今天文章就分享到这儿,喜欢的朋友可以关注我的公众号,回复“进群”,可进免费技术交流群。博主不定时回复大家的问题。 公众号:吴计可师