——從 1.0 到 7.2,一窺數據結構、網絡模型、持久化、復制、高可用與生態協同的底層脈絡
(一)序章:為什么是 Redis
1999 年,Salvatore Sanfilippo 在開發一個實時訪客分析系統時,發現傳統磁盤型數據庫無法在毫秒級響應 100 萬并發連接。于是,他寫了一個將全量數據放在內存、用 C 語言實現、單線程處理命令、支持簡單文本協議的服務——這就是 Redis 的原型。二十年后,Redis 已不僅僅是“緩存”的代名詞,而是橫跨緩存、消息總線、流處理、機器學習特征存儲、實時排行榜、地理位置服務等眾多場景的“瑞士軍刀”。
本文不貼源碼,也不給調參清單,而是回到設計哲學:為什么 Redis 選擇單線程?為什么后來又要引入多線程 I/O?為什么跳表而不是 B+ 樹?AOF 與 RDB 能否合二為一?主從復制與哨兵、集群、Raft 之間的演進邏輯是什么?帶著這些問題,我們穿越版本號,拆解 Redis 的骨骼與血脈。
(二)數據結構:不止是 key-value
2.1 字符串(SDS)
Redis 沒有復用 C 字符串,而是自建簡單動態字符串 SDS。預分配、惰性釋放、二進制安全、O(1) 長度獲取,這些看似微小的優化,奠定了所有復合類型的基石。
2.2 字典
哈希表 + 漸進式 rehash。負載因子 1 時開始擴容,0.1 時縮容;rehash 期間采用“分槽遷移”策略,避免一次性拷貝導致延遲抖動。
2.3 跳表
實現有序集合 ZSET 的核心。多層索引、概率平衡、范圍查詢 O(logN)。跳表比 B+ 樹省內存,且實現簡單,但范圍查詢的 CPU cache 局部性稍遜。Redis 在 7.0 引入 listpack + 跳表混合編碼,將元素 < 64 字節且個數 < 128 的小 ZSET 壓縮為連續內存塊,從而減少 30% 內存。
2.4 壓縮列表 → quicklist → listpack
早期 list 用 ziplist 保存,連鎖更新問題嚴重;3.2 引入 quicklist(雙向鏈表 + ziplist 節點);5.0 以后用 listpack 取代 ziplist,徹底消除連鎖更新。
2.5 HyperLogLog、Bitmap、Geo、BloomFilter、TDigest、CuckooFilter
這些模塊化數據結構展示了 Redis “數據結構服務器”的定位:把學術界算法工程化,暴露成幾條命令即可落地業務。
(三)網絡模型:單線程、I/O 多路復用、最近的多線程
3.1 單線程事件循環
Reactor 模式 + epoll/kqueue。命令執行階段在單線程完成,天然避免鎖。瓶頸不在 CPU,而在系統調用和內存帶寬。
3.2 連接風暴與 I/O 多線程
6.0 引入 I/O 多線程:主線程仍負責命令執行,網絡 read/write 可由線程池并行化。通過“無鎖隊列 + 原子操作”傳遞 client 對象,避免上下文切換開銷。實測在 128 并發連接、value 1 KB 場景下,QPS 從 12 萬提升到 45 萬。
3.3 TLS、Unix Socket、RESP3
TLS 握手開銷大,Redis 通過“延遲啟動 TLS”優化:先接受普通連接,AUTH 成功后再升級。RESP3 引入屬性、推送、大數類型,為客戶端提供類型安全與流式通知。
(四)持久化:RDB、AOF 與混合范式
4.1 RDB 快照
fork + copy-on-write。父進程繼續服務,子進程遍歷哈希表寫磁盤。大實例 fork 延遲可達百毫秒,Redis 7 引入 “lazy free” 后臺線程異步釋放頁表,減少 80% 阻塞時間。
4.2 AOF 日志
4.0 以前 AOF 是命令重放;4.0 支持 RDB-AOF 混合:前半段是 RDB,后半段是增量命令。重寫時用 “copy-on-write + pipe” 邊生成邊傳輸,避免雙倍內存。
4.3 fsync 策略
always、everysec、no。云盤場景下 everysec 也可能阻塞毫秒級,Redis 6.2 支持 “aof-fsync-in-thread” 將 fsync 移到后臺線程。
4.4 重啟恢復流程
先加載 RDB 構建基線,再重放 AOF 增量。Redis 7.2 引入 “AOF manifest” 記錄多個 AOF 文件順序,實現并行加載,提速 40%。
(五)主從復制:全量、部分、PSYNC2
5.1 SYNC 與 PSYNC
2.8 以前全量復制;PSYNC 引入 backlog 環形緩沖區,支持斷線續傳。backlog 大小 = 平均寫速率 * 容忍宕機時間。
5.2 PSYNC2
4.0 解決級聯復制場景下 “主從切換后新主仍能與舊主部分重同步” 的問題。通過 replid + offset 雙標識,實現“多主歷史”追蹤。
5.3 無盤復制
5.0 支持 “diskless replication”,子進程直接把 RDB 通過 socket 流式發送到 replica,省去磁盤臨時文件。
(六)高可用:哨兵、集群、Raft、聯邦
6.1 Sentinel
哨兵本質是分布式仲裁系統:監控、通知、自動故障轉移、配置提供者。raft-style leader 選舉,但不需要持久化日志。
6.2 Cluster
16384 槽位、CRC16 算法映射、客戶端重定向、ASK/MOVED 語義。節點間采用 Gossip + 心跳 + 故障報告機制。擴容/縮容通過 “槽遷移” 實現:遷移過程中 key 臨時存在于源、目標兩節點,客戶端通過 ASKING 命令解決雙重應答。
6.3 RedisRaft
Redis 官方實驗項目,將 Raft 日志嵌入 Redis 內部命令流,實現強一致。犧牲性能換 CP,適用于金融場景。
6.4 聯邦模式
在云廠商實踐中,常見“代理層分片”:twemproxy、codis、predixy。優勢是對客戶端零侵入;劣勢是代理層成為瓶頸,且無法支持多 key 原子事務。
(七)內存管理:jemalloc、內存碎片、逐出策略
7.1 jemalloc vs tcmalloc
jemalloc 的 arena 機制減少多線程競爭,但 4.x 版本存在 huge page 浪費;Redis 6 引入 “activedefrag” 后臺線程,周期性搬遷對象,降低碎片率。
7.2 逐出算法
LRU、LFU、TTL、random。LFU 用 Morris counter 近似 8 位計數器,支持衰減因子。
7.3 內存壓縮
listpack、hash-ziplist、set-intset、HyperLogLog-dense/sparse 編碼,根據元素特征自適應切換。
(八)事務與腳本:從 MULTI/EXEC 到 Lua 再到 Functions
8.1 MULTI/EXEC 與 WATCH
樂觀鎖機制,WATCH 監控 key 版本號,EXEC 時若版本變化則回滾。
8.2 Lua
5.1 解釋器嵌入,腳本以原子方式執行。EVALSHA 緩存字節碼,減少帶寬。
8.3 Functions
Redis 7 推出 “Functions”:腳本持久化到 AOF/RDB,重啟可恢復;支持集群模式自動廣播。解決 Lua 腳本在集群擴容后丟失的問題。
(九)發布訂閱與 Stream:消息總線的兩種范式
9.1 Pub/Sub
無持久化、無 ACK、無回溯,純推模式。適用于高吞吐低可靠場景,例如彈幕、實時股價推送。
9.2 Stream
Kafka-like 日志抽象:消息 ID(毫秒時間戳+序號)、消費組、pending list、ack、claim。支持按 ID 重放,實現 CQRS 與事件溯源。
(十)模塊系統:從 RedisModule 到 RedisML
模塊 API 允許在子線程注冊新命令、新數據結構、新事件。RedisTimeSeries、RedisSearch、RedisJSON、RedisBloom、RedisAI 等模塊讓 Redis 變身時序數據庫、搜索引擎、向量數據庫。
(十一)云原生與可觀測性
11.1 Kubernetes Operator
通過 CRD 定義 RedisCluster、RedisFailover,利用 sidecar 實現自動故障轉移、滾動升級、密碼輪換。
11.2 監控指標
? 內存:used_memory、mem_fragmentation_ratio
? 延遲:latency_percentiles_usec、slowlog
? 復制:master_repl_offset、master_link_down_time_seconds
? 集群:cluster_state、cluster_slots_fail
11.3 追蹤
Redis 7 支持 “keyspace hits/misses” 細粒度指標,結合 eBPF 可定位 hotkey。
(十二)尾聲:下一跳
Redis 8 Roadmap:
? Threaded Lua:腳本跑在獨立線程池,避免長腳本阻塞主循環。
? Diskless Cluster:Gossip 消息也走 RDMA,實現全內存復制。
? SQL-like 查詢:基于 RediSQL 模塊,支持類關系型語法。
? Tiered Storage:SSD 作為二級緩存,LRU 算法跨 DRAM/SSD 兩層。
Redis 的演進史,是一部在 “性能、功能、一致性” 三角張力中反復權衡的歷史。每一次看似激進的重構,背后都有業務場景的真實痛苦。理解這些權衡,遠比背參數、背命令更有生命力。