1项目概览
1.1 核心功能
Valkey 是一个高性能数据结构服务器(data structure server),对外以 RESP 协议提供键/值工作负载。它并非简单的 K-V 缓存,而是"数据结构即服务"系统——客户端存储的是具有语义的结构(字符串、列表、哈希、集合、有序集合、流、位图、HyperLogLog、地理信息等),服务器侧完成结构化操作。
引证:"Valkey is a high-performance data structure server that primarily serves key/value workloads." —— README.md:9-10

1.2 解决的主要问题
ae.c | ||
t_zset.c / t_stream.c | ||
cluster_legacy.c | ||
aof.c / rdb.c |
1.3 典型使用场景
分布式缓存
TTL、LRU/LFU 淘汰、客户端缓存追踪会话/计数/限流
原子 INCR、滑动窗口、分布式锁排行榜/统计
ZSET 排行榜、HyperLogLog UV 去重消息队列
List 阻塞队列、Stream 消费组地理位置
GEO 命令集(ZSET+GeoHash)发布订阅
Pub/Sub + Sharded Pub/Sub1.4 主要技术栈
-std=gnu11) | Makefile:58-62 | |
| jemalloc | Makefile:74-77 | |
ae_epoll.c | ||
| libvalkey | deps/libvalkey | |
deps/lua | ||
socket/tls/rdma/unix.c | ||
networking.c |
Valkey 9.0 关键演进(综合 00-RELEASENOTES):新增 hashtable.c(Swiss-table+SIMD)、Hash 字段级 TTL、原子槽位迁移 ASM、集群多 DB、双通道复制、IO 线程重写、全局重命名(redisObject→serverObject、redisDb→serverDb,server.h:95,875)。
2整体架构
2.1 架构设计总览
Valkey 采用单线程反应器(Reactor)+ 多路复用 + 受控多线程架构。核心思想:命令执行串行化(避免锁),慢速 I/O 与阻塞操作卸载到辅助线程/子进程。

2.2 分层结构
networking.cresp_parser.c | ||
ae.cae_epoll.cio_threads.c | ||
server.ccommands.c | ||
db.cobject.ckvstore.chashtable.c | ||
t_*.clistpack/quicklist/intset/rax | ||
rdb.caof.crio.c | ||
replication.ccluster*.csentinel.c |
2.3 模块依赖关系
核心规律: server.c是唯一全局枢纽,子系统通过server全局结构体(server.h:1668)交互而非直接互调。降低耦合,但使server成为"上帝对象"。
networking.c ──┐ │ │ ae.c(事件循环)│ │ │ ┌───▼────────▼───┐ │ server.c │ ← 全局枢纽 (main/call/processCommand/serverCron) └──┬─────┬────┬──┘ ▼ ▼ ▼ db.c config.c replication.c → rdb.c/aof.c │ │ ▼ ▼ kvstore→hashtable rio.c │ ▼ t_*.c → module.c → eval.c/functions.c2.4 数据流视角

3模块结构
3.1 主要模块清单
服务器二进制由约 130 个目标文件链接(src/Makefile:426的 ENGINE_SERVER_OBJ)。
server.c server.h | ||
ae.c ae_epoll.c networking.c connection.c socket.c tls.c unix.c rdma.c io_threads.c | ||
dict.c hashtable.c kvstore.c listpack.c ziplist.c intset.c quicklist.c rax.c | ||
object.c db.c expire.c evict.c lazyfree.c defrag.c | ||
t_string.c t_list.c t_set.c t_zset.c t_hash.c t_stream.c hyperloglog.c geo.c bitops.c sort.c | ||
rdb.c aof.c rio.c bio.c childinfo.c | ||
replication.c syncio.c | ||
cluster.c cluster_legacy.c cluster_migrateslots.c cluster_slot_stats.c crc16.c | ||
sentinel.c | ||
eval.c functions.c scripting_engine.c script.c lua/* | ||
module.c valkeymodule.h | ||
acl.c multi.c tracking.c pubsub.c blocked.c | ||
config.c commands.c commandlog.c latency.c debug.c notify.c timeout.c | ||
valkey-cli.c valkey-benchmark.c valkey-check-aof.c valkey-check-rdb.c | ||
trace/* |
3.2 核心模块职责
server.c — 全站枢纽
main() // server.c:7139 程序入口(weak 符号)initServerConfig() // server.c:2229 仅设默认值initServer() // server.c:2835 运行时资源初始化InitServerLast() // server.c:3138 bioInit + initIOThreadsprocessCommand() // server.c:4186 命令检查链(ACL/集群/maxmemory)call() // server.c:3795 执行 c->cmd->proc(c)serverCron() // server.c:1483 周期后台任务beforeSleep()/afterSleep()// server.c:1794/1980 循环钩子
数据层 kvstore.c + hashtable.c + dict.c
kvstore:按 cluster slot 分片(集群 16384 分片,非集群 1 分片)hashtable.c:Swiss-table 风格,64 字节 cache-line 对齐桶 + SIMD 过滤dict.c:经典拉链哈希表,渐进式 rehash,用于非 DB 字典
3.3 模块调用关系(HSET 示例)

3.4 核心模块识别
以下为"牵一发而动全身"的核心模块,理解它们即理解 Valkey 骨架:

4启动流程
4.1 程序入口
入口为 main(),位于 src/server.c:7139,标记为弱符号以支持测试覆盖:
__attribute__((weak)) intmain(int argc, char **argv) { // server.c:71394.2 初始化流程(6 阶段)

4.3 配置加载流程
配置优先级(server.c:7240-7241):配置文件、stdin、命令行 --option——最后出现的生效。
三步合一的 loadServerConfig()(config.c:649-712)将三种来源拼成一个 sds,交给 loadServerConfigFromString():
voidloadServerConfig(char *filename, char config_from_stdin, char *options) { sds config = sdsempty();if (filename) { /* 读文件,支持 glob 通配符 * ? [ */ }if (config_from_stdin) { /* 追加 stdin */ }if (options) { config = sdscat(config, "\n"); config = sdscat(config, options); } loadServerConfigFromString(config); // config.c:710}注意:不存在独立的 loadServerConfigFromStdin函数——stdin 处理内联在loadServerConfig(config.c:699-703)。
配置项注册——standardConfig 表(config.c:292-300)
structstandardConfig {constchar *name; /* 用户可见名 */constchar *alias; /* 别名(slave-*→replica-*) */unsignedint flags; /* IMMUTABLE/MODIFIABLE/MULTI_ARG/... */ typeInterface interface; /* init/set/get/rewrite/apply 回调 */ typeData data; /* yesno/string/sds/enumd/numeric */ configType type; /* BOOL/STRING/SDS/ENUM/NUMERIC */void *privdata; /* 模块配置 */};静态表 static_configs[](config.c:3190)+ 运行时字典 static dict *configs(config.c:302)。文件解析与 CONFIG SET/GET/REWRITE 复用同一套回调,设计高度统一。
4.4 核心组件初始化(initServer, server.c:2835-3051)
signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN); // 2836setupSignalHandlers(); // 6713: SIGTERM/SIGINT → sigShutdownHandlerserver.el = aeCreateEventLoop(server.maxclients + CONFIG_FDSET_INCR); // 2904server.dbnum = server.cluster_enabled ? server.config_databases_cluster : server.config_databases; // 2910server.db = zcalloc(sizeof(serverDb*) * server.dbnum);createDatabaseIfNeeded(0); evictionPoolAlloc(); // 2914aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL); // 2986aeCreateTimeEvent(server.el, 1, clientsTimeProc, NULL, NULL); // 2992aeSetBeforeSleepProc(server.el, beforeSleep); // 3005aeSetAfterSleepProc(server.el, afterSleep); // 3006scriptingEngineManagerInit → luaEngineInitEngine → functionsInit → evalInit; // 3019-3039InitServerLast()(server.c:3138)延迟到最后(模块加载后),规避 ld.so与 dlopen的 TLS 竞争 bug:bioInit() + initIOThreads()。
initListeners()(server.c:3053)端口绑定被移出 initServer,单独在配置加载后调用。
4.5 加载数据(loadDataFromDisk, server.c:6922)
if (server.aof_state == AOF_ON) { loadAppendOnlyFiles(server.aof_manifest); // AOF 优先} else { rdbLoad(server.rdb_filename, &rsi, rdb_flags); // 否则 RDB}5核心执行流程
5.1 一条命令的完整生命周期(SET foo bar)



5.2 数据对象的编码流转
serverObject结构(server.h:796-806):
structserverObject {unsigned type : 4; // 逻辑类型 (OBJ_STRING..OBJ_STREAM)unsigned encoding : 4; // 底层编码unsigned lru : LRU_BITS; // 24位, LRU时间或LFU计数复用unsigned hasexpire : 1; // Valkey新增: 内嵌过期标记unsigned hasembkey : 1; // Valkey新增: 内嵌keyunsigned refcount : OBJ_REFCOUNT_BITS; // 30位void *ptr;};OBJ_ENCODING_EMBSTR_SIZE_LIMIT=44object.c:221 | |||
list-max-listpack-size -2 | |||
hash-max-listpack-entries 512 / value 64 | |||
set-max-intset-entries 512 | |||
zset-max-listpack-entries 128 | |||
stream-node-max-bytes 4096 / entries 100 |
设计精髓:小数据用紧凑编码(listpack/intset)省内存,大数据用高效结构(hashtable/skiplist)保性能,转换在写入路径自动触发,对客户端透明。
5.3 命令执行前的检查链(processCommand, server.c:4186)


5.4 call() 的执行与后处理
constlonglong call_timer = ustime();enterExecutionUnit(1, call_timer); // 3832 进入执行单元(嵌套统计)c->flag.executing_command = 1;monotime monotonic_start = 0;if (monotonicGetType() == MONOTONIC_CLOCK_HW) monotonic_start = getMonotonicUs();c->cmd->proc(c); // 3846 ← 真正执行命令exitExecutionUnit();duration = getMonotonicUs() - monotonic_start; // 仅硬件时钟时用,避免3次系统调用latencyAddSampleIfNeeded(latency_event, duration); // 3893commandlogPushCurrentCommand(c, real_cmd); // 3904 慢日志// 命令统计、错误统计、传播...性能细节: monotonic_start仅在硬件时钟(TSC)可用时获取,否则复用call_timer,避免每次命令 3 次gettimeofday系统调用(server.c:3844,3857)。
5.5 关键对象的流转
client (客户端对象, networking.c) │ querybuf (输入缓冲, sds) / cmd_queue (解析后命令队列, 9.0批量) │ argv/argc / buf(16KB)+reply(动态链表) 输出缓冲 / db ▼serverObject / robj (object.c) │ type + encoding + lru + refcount + ptr ▼serverDb (db, server.h:875) │ keys (kvstore) / expires (kvstore) / keys_with_volatile_items (kvstore) ▼kvstore (分片层, kvstore.c) → hashtables[16384] 按slot索引 ▼hashtable / dict (存储引擎) → bucket(64B cache-line) / dictEntry ▼listpack / intset / skiplist / quicklist / rax (底层编码)6并发模型
6.1 线程模型总览
Valkey 是"单线程命令执行 + 受控多线程辅助"模型,这是其低延迟的根基。

为什么命令执行串行化?单线程无锁访问数据集,彻底消除锁竞争、死锁、缓存行与伪共享。这是 Valkey 用"多核靠多实例分片"换"单核极致低延迟"的哲学。
6.2 是否使用线程池
(1) IO 线程池(io_threads.c,9.0 全新重写)
Valkey 9.0 用无锁环形缓冲队列取代旧版共享 list + 自旋。每个 IO 线程拥有独立 SPSC(单生产者单消费者)队列:
typedefstructIOJobQueue { iojob *ring_buffer;size_t size;_Atomicsize_t head __attribute__((aligned(CACHE_LINE_SIZE))); /* 主线程写 */_Atomicsize_t tail __attribute__((aligned(CACHE_LINE_SIZE))); /* IO线程写 */} IOJobQueue;- 生产者(主线程)
IOJobQueue_push: memory_order_release发布 head(io_threads.c:72) - 消费者(IO 线程)
IOJobQueue_availableJobs: memory_order_acquire读 head(io_threads.c:82) - 缓存行对齐
: head/tail分别aligned(CACHE_LINE_SIZE),避免伪共享 - 忙等+挂起混合
:先自旋 1,000,000 次( io_threads.c:232),无任务则pthread_mutex_lock挂起(:239)
IO 线程可承担:读 socket、写 socket、解析 RESP,甚至 aeApiPoll本身(IOThreadPoll,io_threads.c:204)。动态伸缩:adjustIOThreadsByEventLoad()(:170)按负载增减活跃线程;9.0 支持运行时 CONFIG SET io-threads N(updateIOThreads,:309)。
(2) BIO 后台线程池(bio.c)
BIO_CLOSE_FILE | ||
BIO_AOF_FSYNCBIO_CLOSE_AOF | ||
BIO_LAZY_FREE | ||
BIO_RDB_SAVE |
fsync 状态通过原子变量 aof_bio_fsync_status回传,无需加锁。
6.3 锁优化与无锁结构
Valkey 几乎不在数据路径加锁(命令执行单线程)。但仍有多处精心设计的并发原语:
无锁 SPSC 环形缓冲_Atomic+ memory_order) | io_threads.c:20-25 | |
| 原子标志位+自旋 | io_threads.c:150-166 | |
| 原子变量 | bio.c:285 | |
| GIL | server.c:1991,1971 | |
| refcount 哨兵值 | object.c:123-127 | |
ae.h:116 | ||
lazyfree.c:183 |
关键约束——GIL 边界: beforeSleep()末尾moduleReleaseGIL()(server.c:1971),afterSleep()开头moduleAcquireGIL()(server.c:1991)。两处强警告注释:GIL 释放后/获取前不得添加任何代码。这构成模块后台线程与主线程数据访问的同步边界。注意:存储引擎(hashtable.c/dict.c)既非无锁并发结构也非分段锁——全文无 mutex/atomic/lock 字段,线程安全完全依赖单线程事件循环保证。
7性能优化
7.1 缓存设计
(1) 多级紧凑编码("结构即缓存")
listpack
变长整数+字符串连续字节数组,替代旧 ziplist,无 prevlen 回写问题
intset
纯整数集合,按值类型(16/32/64位)紧凑排列,二分查找
自动转换
小 Hash 用 listpack 省元数据,大时转 hashtable 保性能
(2) 共享对象池
预创建 0~9999 共 10000 个共享整数对象(OBJ_SHARED_INTEGERS,server.h:135,server.c:2194),refcount 设为 OBJ_SHARED_REFCOUNT(永不释放),无锁访问。
(3) 客户端查询缓冲复用 + 回复缓冲(固定+动态)
_addReplyToBufferOrList(networking.c:683):优先写入 16KB 固定缓冲 c->buf,溢出才用动态链表 c->reply,减少小回复的 malloc。
7.2 批处理
networking.c:3835io_threads.c:331 | ||
server.c:1917 | ||
aof.c:1321 |
7.3 异步处理
LAZYFREE_THRESHOLD) | lazyfree.c:171,180 | |
bio.c:280 | ||
bio.c:270 | ||
rdb.c:1648 | ||
aof.c:2462 | ||
bio.c:305 | ||
db.c:2107expire.c:198 | ||
dict.c:114hashtable.c:1270 |
7.4 IO 优化
ae_epoll.c:83-84 | ||
io_threads.c | ||
hashtable.c:700-754 | ||
memory_prefetch.c | ||
evict.c:73 |
8容错与稳定性
8.1 重试机制
aof.c:1171 | ||
syncio.c | ||
cluster_legacy.c:5882 | ||
replication.c:3971 | ||
aof.c:1598 |
8.2 降级策略
server.c:4391 | ||
evict.c:546 | ||
cluster_legacy.c:6360 | ||
config.c | ||
cluster_legacy.c:6296 | ||
server.c:4282 | ||
server.c:4187 |
8.3 限流
- 客户端输出缓冲区限制
: client-output-buffer-limit对普通/副本/pubsub 客户端设硬/软限制,超限强制断开,防慢客户端拖垮服务器。 - 客户端内存驱逐
: evictClients()(server.c:4366)每条命令前检查,超maxmemory-clients驱逐占用最多者。 - 集群连接限速
:9.0 限制每周期新建 cluster link 数(#2009)。 - 慢日志与延迟监控
: commandlog记录超阈值命令;latency.c按事件类型记录时间序列(LATENCY_TS_LEN=160,latency.h:39)。
8.4 异常处理
(1) 断言与 panic 体系
_serverAssert(debug.c:1063)/ _serverPanic(debug.c:1170):触发崩溃报告(含栈、内存、客户端信息)。serverPanic用于"不可恢复状态"(如编码损坏,aof.c:1785)。
(2) 看门狗(Watchdog)
applyWatchdogPeriod(debug.c:2357)用 setitimer(ITIMER_REAL)设定 SIGALRM,主线程阻塞超时则中断打印栈。最小周期 2*(1000/hz)ms(:2365)。
(3) 优雅停机
sigShutdownHandler(server.c:6713)捕获 SIGTERM/SIGINT,finishShutdown依次:等副本追平→杀子进程→RDB 保存→关闭 AOF。9.0 新增 SHUTDOWN SAFE拒绝不安全停机(#2195)。
(4) 持久化容错
- RDB CRC64 校验
:加载时校验,损坏则拒绝( rdb.c:1486) - AOF manifest 多文件
:BASE+INCR 链,单文件损坏不影响其他; valkey-check-aof --fix修复 - RDB 版本兼容
: rdbIsVersionAccepted放宽对外来格式校验(9.0 #2543)
(5) 集群故障自愈

(6) 过期双保险
惰性删除(访问时检查,db.c:2107)+ 定期删除(cron 抽样,expire.c:198),FAST/SLOW 双周期交替,KEYS/FIELDS 任务防饿死(expire.c:486)。
(7) 内存碎片整理
defrag.c+ jemalloc je_get_defrag_hint()主动整理碎片,避免内存虚高。
9设计优点
9.1 架构优点
✅ 极致低延迟
单线程无锁 + 事件循环 + epoll,单核十万 QPS
✅ 内存高效
listpack/intset 紧凑编码 + 自动转换 + jemalloc
✅ 接口统一
standardConfig 函数指针表,文件解析与 CONFIG SET/GET/REWRITE 复用
✅ 连接抽象
connection.c 多态,TCP/TLS/Unix/RDMA 无缝切换
✅ 渐进演进
dict→hashtable、IO 线程重写,保持向后兼容
✅ 可扩展
模块 API + 脚本引擎抽象层支持多语言
✅ 高可用完备
Cluster+Sentinel 双方案,Raft-like 选举+Gossip 自愈
✅ 可观测性
latency/commandlog/hdr_histogram/lttng/INFO
学习路径
建议按"由外到内、由简到繁、由数据到控制"的顺序阅读:

阶段一:建立全局观
README.mdvalkey.conf | ||
src/Makefile:426 | ||
src/version.h00-RELEASENOTES |
阶段二:启动与事件循环
server.c:7139 main | ||
server.c:2835 initServer | ||
ae.c/ae.hae_epoll.cserver.c:1794/1980 |
阶段三:命令执行主链
networking.c:4179 readQueryFromClient:3835 parseInputBuffer:3774 processCommandAndResetClient | ||
server.c:4186 processCommand:3795 call | ||
networking.c:743 addReply:2946 writeToClient |
阶段四:数据结构与存储引擎
server.h:796 serverObjectobject.c:55db.c:88 lookupKey | ||
kvstore.chashtable.cdict.c:317 dictRehash | ||
listpack/intset/quicklist/raxt_string→t_zset | ||
expire.c:198 activeExpireCycleJob |
阶段五:并发与性能
io_threads.cbio.clazyfree.cevict.c:527 performEvictionsdefrag.c |
阶段六:持久化与复制
rdb.c:1648 rdbSaveBackgroundaof.c:1321/1053aof.c:2462 rewrite | ||
replication.c:3971 syncWithPrimary:827 PSYNC:424 feedReplicationBuffer |
阶段七:集群与高可用
cluster.c:57 keyHashSlotcluster_legacy.c:4588 clusterSendPing:2432 markNodeAsFailingIfNeeded | ||
cluster_legacy.c:5325 clusterHandleReplicaFailovercluster.c:1047 getNodeByQuerycluster_migrateslots.csentinel.c:4407 |
阶段八:扩展与安全
config.c:3190multi.cblocked.ceval.c/functions.c/scripting_engine.cmodule.cacl.c |
夜雨聆风