服务端阅读 05月31日 02:05
Redis 底层为什么这么快?核心数据结构如何支撑?
Redis 底层之所以快,主要靠三件事:数据大多在内存里,命令执行路径足够短,核心数据结构针对常见操作做了大量取舍。它不是简单的“单线程所以快”,而是 SDS、dict、skiplist、quicklist、intset、事件循环、I/O 多路复用和内存分配共同配合的结果。面试里回答这个问题,最好从数据结构讲到网络模型,再落到性能边界。字符串和哈希表是基础Redis 的 String 底层不是直接使用 C 字符串,而是 SDS。SDS 记录了长度和容量,获取长度是 O(1),扩容时有预分配策略,还能安全存储二进制数据。它的取舍是多占一点元数据空间,换来更安全的修改和更少的内存重分配。Hash、数据库 key 空间、过期字典大量依赖 dict。dict 使用哈希表加链地址法解决冲突,并通过两个哈希表做渐进式 rehash。这样不会在扩容时一次性搬完整张表,避免服务长时间卡住。踩坑点是 rehash 期间查询、写入都要同时照顾两张表,理解这一点才能解释为什么 Redis 能边服务边扩容。typedef 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 多线程,主要优化网络读写,命令执行仍以单线程为主。redis-cli INFO memoryredis-cli SLOWLOG GET 10redis-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 慢了”拆成结构、网络、内存和持久化几个方向。