Redis 底层为什么这么快?核心数据结构如何支撑?
Redis 底层之所以快,主要靠三件事:数据大多在内存里,命令执行路径足够短,核心数据结构针对常见操作做了大量取舍。它不是简单的“单线程所以快”,而是 SDS、dict、skiplist、quicklist、intset、事件循环、I/O 多路复用和内存分配共同配合的结果。面试里回答这个问题,最好从数据结构讲到网络模型,再落到性能边界。
字符串和哈希表是基础
Redis 的 String 底层不是直接使用 C 字符串,而是 SDS。SDS 记录了长度和容量,获取长度是 O(1),扩容时有预分配策略,还能安全存储二进制数据。它的取舍是多占一点元数据空间,换来更安全的修改和更少的内存重分配。
Hash、数据库 key 空间、过期字典大量依赖 dict。dict 使用哈希表加链地址法解决冲突,并通过两个哈希表做渐进式 rehash。这样不会在扩容时一次性搬完整张表,避免服务长时间卡住。踩坑点是 rehash 期间查询、写入都要同时照顾两张表,理解这一点才能解释为什么 Redis 能边服务边扩容。
ctypedef struct dict { dictht ht[2]; long rehashidx; } dict;
List、Set、ZSet 会按规模切换编码
Redis 不会一上来就用最通用但最重的数据结构。小集合会用 intset 或紧凑编码节省内存,元素变多或类型变化后再升级。List 在新版本主要基于 quicklist,它把多个紧凑节点串成双向链表,兼顾顺序访问和内存占用。ZSet 常见实现是 dict + skiplist:dict 保证按 member 快速查分数,skiplist 保证按 score 范围查询和排名。
跳表不是唯一能做有序结构的方案,平衡树也可以。Redis 选择跳表,是因为实现相对简单,范围查询自然,插入删除平均 O(log n),调试和维护成本低。代价是多层索引会额外占内存,score 相同还要按 member 做字典序比较。
单线程命令执行不等于只有一个线程
Redis 的核心命令执行长期保持单线程,这能避免复杂锁竞争,也让数据结构操作更可控。但网络 I/O、后台删除、AOF 刷盘、持久化重写等任务可能由其他线程或子进程参与。Redis 6 之后支持 I/O 多线程,主要优化网络读写,命令执行仍以单线程为主。
bashredis-cli INFO memory redis-cli SLOWLOG GET 10 redis-cli --bigkeys -h 127.0.0.1 -p 6379
事件循环和过期删除决定稳定性
Redis 用事件循环处理文件事件和时间事件。文件事件负责连接读写,时间事件负责定时任务,比如过期 key 抽样删除、统计维护、复制心跳。过期删除不是每个 key 到点立刻删除,而是惰性删除加定期抽样删除。这个设计节省 CPU,但也意味着大量过期 key 可能短时间占住内存,需要合理设置 TTL 分布。
追问
Redis 为什么用 SDS,不直接用 C 字符串?
C 字符串没有长度字段,求长度需要遍历,拼接时还容易写越界。SDS 把长度、剩余容量和字节数组放在一起,常见修改能先检查容量,不够再扩容。它的边界是会增加少量结构体开销,但 Redis 更在意高频操作的稳定延迟。存二进制内容时,SDS 也不会因为中间出现 \0 就截断。
渐进式 rehash 解决了什么问题?
如果哈希表扩容时一次性迁移所有 key,Redis 会在这一瞬间明显阻塞。渐进式 rehash 把迁移分摊到后续命令和定时任务里,每次搬一小部分。代价是 rehash 期间要查两张表,代码复杂一些,内存也会短暂增加。这个取舍很典型:用一点空间和实现复杂度,换线上延迟更平滑。
ZSet 为什么通常用 dict 加 skiplist?
dict 让 ZSCORE 这类按成员查分数的操作接近 O(1),skiplist 让按分数范围查询、排名查询保持 O(log n)。如果只用 dict,没法高效排序;如果只用 skiplist,按 member 查找又不够快。踩坑点是 ZSet 的内存成本比普通 Set 高很多,不适合无限堆用户行为明细。排行榜要加时间维度和过期策略,否则迟早变成大 key。
Redis 单线程会不会被慢命令拖垮?
会,所以生产环境要避免 KEYS、超大集合 SMEMBERS、大 key 删除这类操作。单线程的好处是没有锁竞争,坏处是一个慢命令就可能挡住后面的请求。线上更推荐 SCAN 分批扫描,用 UNLINK 异步删除大 key,并打开慢日志观察异常命令。Redis 很快,但前提是每个命令都足够短。
怎么从底层角度排查 Redis 性能问题?
先看 INFO memory 判断内存、碎片率和淘汰情况,再看 SLOWLOG 找慢命令。怀疑大 key 时用 --bigkeys 或离线 RDB 分析,怀疑网络瓶颈时看连接数、客户端输出缓冲区和命令 QPS。不要一上来就调线程数,很多问题其实是 key 设计或命令复杂度错了。底层原理的价值就在这里:能把“Redis 慢了”拆成结构、网络、内存和持久化几个方向。