Redis底层设计那些事儿,源码细节也想扒一扒看看
- 问答
- 2026-01-25 04:36:42
- 3
Redis底层设计那些事儿,源码细节也想扒一扒看看
要扒Redis的底,得从它最基础的内存分配说起,Redis自己封装了一个叫zmalloc的东西来管内存,这不是为了炫技,主要是为了能精确统计用了多少内存,方便超过上限时触发淘汰策略,它在glibc的malloc外面包了一层,自己维护了一个内存使用量的计数器。(这个细节在zmalloc.c的源码注释里有提)
说到数据结构,大家常说的String(字符串)底层可不止是char数组,Redis搞了个叫“简单动态字符串”(SDS)的东西,它有个神奇之处:字符串长度是直接存起来的,取长度时不用像C语言那样遍历,复杂度是O(1),而且它预留了多余空间,追加字符串时如果空间够就直接用,减少了频繁分配内存的开销,这个设计在《Redis设计与实现》这本书里讲得很细。
List(列表)呢,早期版本是用双向链表实现的,后来为了省内存,在小数据量时改用了一种叫“压缩列表”(ziplist)的紧凑结构,它就是一块连续的内存,一个挨着一个存数据,没有链表指针那些开销,但当元素多了或者单个元素太大时,又会转成真正的双向链表,这个切换的阈值可以在配置文件里改。

最核心的Hash(字典),实现上和很多语言里的HashMap类似,但有自己的门道,它也是数组加链表解决冲突,但扩容时很讲究,它不是一次性把全部数据搬过去,而是“渐进式rehash”,它会同时维护两个哈希表,慢慢地把旧表的数据一点点搬到新表,期间如果有新的读写操作,都会参与到这个搬运过程中,这样避免了单次扩容导致的长时间卡顿,这个精妙的设计在源码的dict.c文件里,看rehash相关函数就能扒到。
持久化机制RDB,俗称快照,它生成文件的时候,Redis会fork出一个子进程来干这个体力活,父进程继续服务客户端,这里有个容易误解的点:fork出来的子进程,并不会复制父进程的全部物理内存,它利用操作系统“写时复制”的机制,只有在父进程修改某块数据时,这块数据才会被真正复制一份出来,所以通常它比想象中更省资源。(这个机制在《Redis持久化》官方文档中有解释)

AOF(追加日志)文件会越来越大,所以需要重写,重写也是fork子进程,但重写的过程并不是简单读旧的AOF文件,而是子进程直接读取当前数据库的内存状态,逆向生成最短的Redis命令序列,这样得到的新的AOF文件最精简。
再来扒一个网络相关的细节,Redis是单线程处理命令的,这个大家都知道,但它为什么还能这么快?它的网络I/O模型用了像epoll这样的多路复用机制,可以同时监听成千上万个连接,当某个连接有命令到来时,它会被放到一个队列里,由那个单线程按顺序处理,这个事件循环的源码在ae.c文件中,是Redis高性能的基石之一。
还有过期键的删除,并不是键一到时间就立刻删除,它采用惰性删除加定期删除的策略:当你尝试访问一个键时,Redis才检查它是否过期,如果过期就当场删除,Redis会每隔一段时间,随机抽查一些设置了过期时间的键,清理掉其中已过期的,这种策略是在性能和内存使用之间取得平衡。
看看事务,Redis的事务(MULTI/EXEC)和数据库的事务完全不同,它只是把一组命令打包,确保它们被顺序执行,且不会被其他客户端的命令打断,但它不提供回滚机制,如果事务中某条命令出错,后面的命令依然会执行,源码里,事务命令是被放入一个队列,直到EXEC命令到来,才一次性全部执行。
本文由雪和泽于2026-01-25发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/85513.html
