首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
开篇词 | 阅读Redis源码能给你带来什么?
01 | 带你快速攻略Redis源码的整体架构
02 | 键值对中字符串的实现,用char*还是结构体?
03 | 如何实现一个性能优异的Hash表?
04 | 内存友好的数据结构该如何细化设计?
05 | 有序集合为何能同时支持点查询和范围查询?
06 | 从ziplist到quicklist,再到listpack的启发
07 | 为什么Stream使用了Radix Tree?
08 | Redis server启动后会做哪些操作?
09 | Redis事件驱动框架(上):何时使用select、poll、epoll?
10 | Redis事件驱动框架(中):Redis实现了Reactor模型吗?
11 | Redis事件驱动框架(下):Redis有哪些事件?
12 | Redis真的是单线程吗?
13 | Redis 6.0多IO线程的效率提高了吗?
14 | 从代码实现看分布式锁的原子性保证
15 | 为什么LRU算法原理和代码实现不一样?
16 | LFU算法和其他算法相比有优势吗?
17 | Lazy Free会影响缓存替换吗?
18 | 如何生成和解读RDB文件?
19 | AOF重写(上):触发时机与重写的影响
20 | AOF重写(下):重写时的新写操作记录在哪里?
21 | 主从复制:基于状态机的设计与实现
22 | 哨兵也和Redis实例一样初始化吗?
23 | 从哨兵Leader选举学习Raft协议实现(上)
24 | 从哨兵Leader选举学习Raft协议实现(下)
25 | Pub/Sub在主从故障切换时是如何发挥作用的?
26 | 从Ping-Pong消息学习Gossip协议的实现
27 | 从MOVED、ASK看集群节点如何处理命令?
28 | Redis Cluster数据迁移会阻塞吗?
29 | 如何正确实现循环缓冲区?
30 | 如何在系统中实现延迟监控?
31 | 从Module的实现学习动态扩展功能
32 | 如何在一个系统中实现单元测试?
当前位置:
首页>>
技术小册>>
Redis源码剖析与实战
小册名称:Redis源码剖析与实战
### 02 | 键值对中字符串的实现:用`char*`还是结构体? 在Redis这样的高性能键值存储系统中,字符串是最基础且使用最频繁的数据类型之一。它不仅用于存储简单的文本数据,还广泛应用于计数器、标志位等多种场景。因此,字符串的存储效率和访问速度直接影响到Redis的整体性能。在设计Redis内部对字符串的表示时,一个核心问题就是:是使用裸的`char*`指针直接指向字符串数据,还是通过一种更复杂的结构体来封装字符串?这个选择背后涉及到内存管理、灵活性、性能优化等多个方面的考量。 #### 一、`char*`的实现方式 使用`char*`指针直接存储字符串是最直观的方法。它允许Redis直接操作C语言标准库中的字符串函数,如`strlen`、`strcpy`、`strcat`等,简化了很多基本操作。然而,这种方式也存在一些显著的缺点: 1. **内存分配与释放**:使用`char*`意味着Redis需要手动管理字符串的内存分配和释放。这通常涉及使用`malloc`、`realloc`和`free`等函数,而这些操作可能因内存碎片、内存分配失败等问题影响系统稳定性。 2. **长度信息缺失**:`char*`只指向字符串的起始位置,不直接提供字符串长度的信息。为了获取长度,Redis需要调用`strlen`函数遍历整个字符串,这在处理长字符串时效率低下。 3. **二进制数据支持不足**:标准C字符串以`\0`作为终结符,这限制了其对二进制数据的直接支持。虽然可以通过特殊处理(如记录长度并在末尾手动添加`\0`)来存储二进制数据,但这种方式既不方便也不高效。 4. **内存复用和共享**:在Redis这样的高性能存储系统中,经常需要复用或共享内存中的字符串数据以减少内存消耗。使用`char*`直接指向字符串的方式,难以高效地实现这些高级功能。 #### 二、结构体的实现方式 鉴于`char*`方式的种种限制,Redis采用了结构体的方式来封装字符串。这种方式通过定义一个专门的字符串对象(如Redis中的`SDS`,即Simple Dynamic Strings),将字符串的长度、内容等信息封装在一起,从而克服了上述问题。 ##### 1. SDS结构体的定义 Redis中的SDS结构体通常包含以下几个关键字段: - `len`:已使用字符串的长度(不包括终结符`\0`)。 - `free`:未使用空间的大小,用于动态扩容。 - `buf[]`:字符数组,用于存储实际的字符串数据,且总是以`\0`结尾,尽管`len`字段已经包含了实际长度信息。 这样的设计带来了以下几个好处: ##### 2. 优点 1. **获取长度的时间复杂度为O(1)**:由于`len`字段直接存储了字符串的长度,Redis可以在常数时间内获取字符串的长度,而无需遍历整个字符串。 2. **预防缓冲区溢出**:使用SDS时,所有修改字符串的操作(如追加)都会先检查剩余空间是否足够,不足时会自动扩容,从而避免了缓冲区溢出的风险。 3. **减少内存重分配次数**:SDS通过`free`字段预留了额外的空间,这使得在大多数情况下,字符串的扩容不需要立即执行内存重分配操作,提高了内存分配的效率。 4. **二进制安全**:SDS不以`\0`作为字符串终结的标志,因此它可以安全地存储任何二进制数据,包括含有`\0`的字符串。 5. **支持更灵活的操作**:SDS的设计使得Redis能够更容易地实现字符串的截断、拼接等高级操作,同时也便于实现字符串的共享和复用。 ##### 3. 实现细节与性能考量 虽然SDS为Redis带来了诸多好处,但其实现也需要注意性能方面的考量: - **内存占用**:SDS由于包含了额外的`len`和`free`字段,因此在存储短字符串时可能会比裸`char*`指针占用更多的内存。然而,对于大多数Redis应用场景而言,这种内存开销是可以接受的,因为Redis处理的主要是长字符串和二进制数据。 - **扩容策略**:SDS的扩容策略是其在性能和内存使用之间做出平衡的关键。Redis通过一套复杂的算法来确定扩容时所需的新空间大小,既保证了扩容的效率,又避免了不必要的内存浪费。 - **兼容性与迁移**:虽然SDS是Redis内部特有的字符串表示方式,但在需要与其他系统或库交互时,Redis也会将SDS转换为标准的C字符串,以保持兼容性。 #### 三、总结 综上所述,Redis在实现键值对中的字符串时选择了结构体而非`char*`,这是基于内存管理、性能优化、二进制安全等多方面的综合考虑。SDS作为Redis内部字符串的核心表示方式,不仅提高了Redis处理字符串的效率,还为其提供了更多灵活性和可扩展性。这种设计选择充分体现了Redis作为一款高性能键值存储系统的设计哲学和技术追求。 通过深入分析SDS的设计原理和优势,我们可以更好地理解Redis在处理字符串时的高效性和灵活性,从而为进一步优化Redis性能或开发类似系统提供有益的参考。
上一篇:
01 | 带你快速攻略Redis源码的整体架构
下一篇:
03 | 如何实现一个性能优异的Hash表?
该分类下的相关小册推荐:
Redis的Lua脚本编程
Redis零基础到实战
Redis面试指南
Redis核心技术与实战