Redis高频面试题——String对象
Redis 中的 String
是最常用的数据类型之一,它在内部有多种编码方式,用于提升性能和节省内存。下面我们详细介绍一下 👇
🎯 Redis String 对象的编码类型
Redis 中的 String 对象底层支持 三种编码方式(Encoding),3.2版本之前是39,3.2版本之后才是44:
编码方式 | 说明 | 使用场景 |
---|---|---|
int | 整数 | 可以表示为 long long 的整数 |
embstr | 短字符串(长度 ≤ 44 字节) | 小字符串、高效分配 |
raw | 普通字符串(长度 > 44 字节) | 大字符串 |
🔍 逐个介绍
1. int
编码
- 存储内容是一个整数,比如:
SET age 25
- Redis 会将该 String 对象编码为
int
- 优点:节省内存,操作效率高(无需分配字符串结构)
✅ 使用条件:
- 值能被解析为整数(
long long
范围内)
2. embstr
编码(Embedded String)
- 用于存储长度小于等于 44 字节的字符串
- Redis 会将
redisObject
和实际字符串SDS
分配在一起(一次 malloc),提高分配效率和缓存局部性
优点:
- 创建快,释放快,内存连续
- 常用于小字符串,如:
SET name "Bob"
3. raw
编码
- 超过 44 字节的长字符串使用
raw
编码 redisObject
和字符串内容是分开分配的(两次 malloc)- 灵活但性能略低于
embstr
🛠 如何查看实际编码?
你可以使用 OBJECT ENCODING key
命令查看 Redis 内部编码:
SET mykey 42
OBJECT ENCODING mykey # 返回 intSET mykey "hello"
OBJECT ENCODING mykey # 返回 embstrSET mykey "a very very long string over 44 bytes........"
OBJECT ENCODING mykey # 返回 raw
💡 小结
编码方式 | 特点 | 内存结构 | 触发条件 |
---|---|---|---|
int | 高效存储整数 | 非字符串 | 字符串能转为整数 |
embstr | 快速创建、释放的小字符串 | 连续内存 | 字符串长度 ≤ 44 字节 |
raw | 普通字符串,大字符串 | 分散内存 | 字符串长度 > 44 字节 |
如果你对 SDS(Simple Dynamic String)机制也感兴趣,我可以继续讲讲它是如何扩容、预分配、避免内存碎片的 😉
当然可以!在 Redis 的 raw
编码中,字符串是通过一种特殊的结构 —— SDS(Simple Dynamic String) 来管理的。SDS 的底层结构是 sdshdr
,它比 C 的 char*
更加安全、灵活。下面我们详细看看它的结构和特点👇
🔍 什么是 sdshdr
sds
是 Redis 自定义的一种动态字符串类型,它的底层是一个结构体叫 sdshdr
。这个结构不仅存储了字符串的内容,还保存了字符串的长度和分配空间等元信息。
💡 为什么不用 char*
?
C 语言的 char*
有这些问题:
- 需要用
strlen()
才能知道字符串长度,时间复杂度 O(n)。 - 容易出现 缓冲区溢出。
- 扩容和拼接效率低、内存管理混乱。
而 SDS 就解决了这些问题。
🧱 sdshdr 的常见结构
Redis 中的 SDS 实现其实是一个结构体族(根据字符串长度使用不同的结构),我们以常用的 sdshdr8
举例说明:
struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; // 当前字符串长度(不含 \0)uint8_t alloc; // 已分配的空间长度(不含 header 和 \0)unsigned char flags; // 低 3 位表示是哪种类型的 sdshdrchar buf[]; // 字符串内容(以 \0 结尾)
};
字段含义解释:
字段 | 含义 |
---|---|
len | 当前字符串实际长度(不包含\0 ) |
alloc | 分配的缓冲区大小(也不含\0 ) |
flags | 记录类型,比如 sdshdr5/8/16/32/64 |
buf[] | 存储实际字符串,最后是\0 结尾 |
🧠 优点分析
✅ 获取长度是 O(1)
size_t sdslen(const sds s);
不需要遍历字符串,效率远超 strlen()
。
✅ 自动扩容
如果你执行 sds cat
或追加,SDS 会自动判断是否需要扩容。
✅ 减少内存碎片
预分配机制:比如你追加 “abc” 后,可能会分配更多的内存空间,下次再追加就不用 realloc 了。
📦 多种 sdshdr 类型
为了节省内存,Redis 使用了不同类型的 sdshdr
:
类型 | 最大长度 | 结构体大小 |
---|---|---|
sdshdr5 | 31 字节 | 最小,仅 flags |
sdshdr8 | 255 字节 | 常用小字符串 |
sdshdr16 | 65,535 字节 | 中等字符串 |
sdshdr32 | 4GB | 长字符串 |
sdshdr64 | 超大字符串(稀有) | 特殊场景 |
Redis 会根据字符串的实际长度自动选择最优的 sdshdr 类型。
🧪 一个例子(伪代码):
sds s = sdsnew("hello redis");
printf("长度: %d\n", sdslen(s));
printf("容量: %d\n", sdsavail(s));
输出示例:
长度: 11
容量: 33
说明 alloc
分配了 44 字节,但当前只用了 11。
🧵 小结
特性 | 优点 |
---|---|
O(1) 获取长度 | 高效替代 strlen() |
自动扩容 | 更好支持拼接和增长 |
内存安全 | 避免 C 的 buffer overflow |
多结构支持 | 节省内存,适应不同长度的字符串 |
与 C 兼容 | buf[] 末尾是 \0 ,可以与 C 字符串兼容 |
面试中关于 Redis 的 String 类型的提问非常高频,通常围绕数据结构、编码、性能、应用场景等方面。下面我给你整理了一些面试中高频问题 + 答案解析,帮你系统性准备 👇
🎯 高频面试题 + 答案(关于 Redis 的 String)
1️⃣ Redis 的 String 是什么?底层是怎么实现的?
答:
Redis 的 String 是最基本的数据类型,最大可以存储 512MB 的二进制安全数据。
底层使用的是 SDS(Simple Dynamic String),不是 C 的 char*
,它具备以下优点:
- 记录字符串长度(O(1) 获取)
- 自动扩容、预分配空间
- 安全:防止缓冲区溢出
- 多种类型(sdshdr5、sdshdr8、16、32、64)节省内存
2️⃣ String 的常见应用场景有哪些?
答:
- 缓存对象(JSON、序列化后的对象)
- 计数器(用
INCR
/DECR
实现) - 分布式锁(
SET key value NX EX 10
) - 限流器(结合 Lua 脚本 + INCR)
- 存储配置信息、Token、验证码等
3️⃣ Redis SETNX 和 SET NX 有什么区别?
答:
SETNX key value
:只在 key 不存在时设置,只能设置值。SET key value NX
:功能一样,但SET
命令还支持额外参数,比如EX
设定过期时间,更推荐使用。
👉 面试建议答:推荐使用 SET key value NX EX 10
原子性地实现分布式锁。
4️⃣ String 的编码方式有哪些?什么时候会用?
答:
Redis 会根据 value 的内容和大小自动选择编码方式:
编码类型 | 说明 |
---|---|
int | 8 字节整数,节省内存 |
embstr | 小于等于 44 字节的字符串,连续内存块,创建快 |
raw | 长字符串,SDS 实现,支持扩容 |
触发条件:
- 字符串是整数 ➜
int
- 字符串 ≤ 44 字节 ➜
embstr
- 字符串 > 44 字节 ➜
raw
5️⃣ embstr 和 raw 区别?
特性 | embstr | raw |
---|---|---|
内存分配 | 一次性分配对象和数据 | 分别分配 |
适用数据 | 短字符串(≤ 44 字节) | 长字符串(> 44 字节) |
性能 | 创建、释放更快 | 支持动态扩容 |
6️⃣ 你了解 SDS 的预分配机制吗?
答:
是的。SDS 在字符串增长时会自动扩容,避免频繁的内存分配。
扩容策略如下:
newlen < 1MB
:多分配相同大小的空间newlen >= 1MB
:多分配 1MB
这样可以提升性能,减少 realloc 次数。
7️⃣ String 类型能存二进制数据吗?
答:
能,Redis 的 String 是二进制安全的,可以存储任意内容(图片、音频、序列化对象等),不限制字符编码。
8️⃣ 如何实现一个全局唯一 ID 生成器?
答:
利用 Redis 的 INCR
原子性即可:
INCR user:id
还可以结合业务前缀与时间戳生成订单号等,如:
SET order:20250425 1000
INCR order:20250425
9️⃣ String 类型最大能存多少数据?
答:
最大为 512MB,超出 Redis 会报错。
1️⃣0️⃣ 如何实现分布式锁?
答:
核心命令是:
SET lock:job123 "UUID" NX EX 10
加锁成功返回 OK,失败代表锁已被其他客户端占用。
释放锁时使用 Lua 脚本确保删除的是自己加的锁,避免误删:
if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])
elsereturn 0
end
11.浮点型在String是用什么表示?
分析:基础知识考查,只有三种编码模式INT只针对于整型,所以浮点型必然是字符串存储。
回答:要将一个浮点数放入字符串对象里面,需要先将这个浮点数转换成字符串值,然后再保存转换所得的字符串值,比如浮点数3.14,对应就变成了"3.14"这个字符串了。所以浮点数在字符串对象里面是用字符串值表示的。
12.Redis字符串底层是String对象,String对象有三种编码方式:INT型、EMBSTR型、RAW型。如果是存在一个整型,可以用long表示的整数就以INT编码存储;如果存字符串,当字符串长度小宇等于一个阈值,使用EMBSTR编码;字符串大于阈值,则用RAW编码。在我用的5.0.5版本中阈值是44。
13.SDS有什么用?
分析:Redis是用C语言写的,SDS可以说是对C字符串的封装,一般对比普通C字符串。可以从计算长度、扩容、缩容、二进制存储这几个场景来描述。
回答:
(1)SDS包含已使用容量字段,O(1)时间快速返回有字符串长度,相比之下,C原生字符串需要O(n)。
(2)有预留空间,在扩容时如果预留空间足够,就不用再重新分配内存,节约性能,缩容时也可以将减少的空间先保留下来,后续可以再使用。
(3)不再以’\0’作为字符串结束判断标注呢,二进制安全,可以很方便地存储一些二进制数据。
如果你需要我总结成思维导图或 PDF 方便复习,请点赞加关注私信我!