Valkey 深度拆解:从 Redis 分叉到十亿级 RPS 集群,Linux 基金会键值存储的三年三级跳
2024 年 3 月,Redis Labs 一纸许可证变更公告,把开源社区炸出了一个新项目。三年过去,这个叫 Valkey 的分叉不但没有像多数 fork 一样悄悄死掉,反而在 8.0 重写了 I/O 线程模型、在 8.1 换掉了用了十几年的哈希表、在 9.0 干出了原子槽位迁移和集群多数据库,官方口径下集群规模拉到 2000 节点、每秒超 10 亿次请求。
这篇文章把 Valkey 从出生到 9.x 的关键技术决策一条条拆开:它到底改了什么、为什么这么改、跟 Redis 现在的差异在哪、生产上该不该换。不站队,只看代码和数据。
一、背景:2024 年那场许可证地震
先把时间线捋清楚,因为后面所有技术决策都和这段历史有关。
- 2024 年 3 月,Redis Labs(现 Redis Ltd.)宣布从 Redis 7.4 开始,许可证由宽松的 BSD-3-Clause 变更为 RSALv2 + SSPLv1 双许可。这两个许可证的核心限制是:云厂商不能再拿 Redis 直接卖托管服务。
- 几天之内,Linux 基金会宣布接管社区分叉,项目命名为 Valkey,基于 Redis 7.2.4(最后一个 BSD 版本)分叉,继续使用 BSD-3-Clause。
- 背后站台的名单很说明问题:AWS、Google Cloud、Oracle、Ericsson、Snap 等。多位 Redis 核心维护者(包括长期贡献者 Madelyn Olson、Zhao Zhao 等)转入 Valkey 阵营。
- 2025 年 5 月,Redis 8.0 又把许可证改回了三选一(其中包含 AGPLv3),算是往回收了半步——但信任这东西,碎了就很难拼回去。
这场分叉和当年 MySQL → MariaDB、OpenOffice → LibreOffice 的剧本很像,但有一个关键区别:Valkey 手里有云厂商的工程师资源。AWS ElastiCache、Google Cloud Memorystore 这些团队常年在内部维护 Redis 补丁,分叉之后这些补丁有了正大光明的去处。这直接决定了 Valkey 的演进速度——它不是「社区用爱发电」,而是「大厂雇佣兵集团军作战」。
理解了这一点,你就能理解为什么 Valkey 敢在 8.0 就动 I/O 线程模型、在 8.1 就换哈希表——这些都是在云厂商内部验证过的方向。
二、Valkey 8.0:把 I/O 线程模型推倒重来
2.1 Redis 的单线程神话与瓶颈
Redis 的经典架构是「单线程事件循环」:一个主线程用 epoll/kqueue 做多路复用,命令的读取、解析、执行、回写全在这一个线程里。这个设计的好处是没有锁、没有竞争、逻辑简单;坏处是在现代多核机器上,一颗核心跑满,其余核心围观。
Redis 6.0 引入了 io-threads,但那是个「半吊子」方案:
- I/O 线程只负责 read/write 系统调用和协议解析的一部分;
- 主线程在每个事件循环里同步等待 I/O 线程完成——本质是 fan-out/fan-in 的批处理模式;
- 主线程分发任务、等待、收集,自己也干一份活,等待期间就是干等。
实测里 Redis 6/7 开 io-threads 通常只能带来 30%~100% 的提升,而且线程数超过 6 之后收益急剧衰减。原因就是那个同步屏障:主线程每一轮都要等最慢的 I/O 线程。
2.2 Valkey 8.0 的异步 I/O 线程
Valkey 8.0(2024 年 9 月发布)把这套模型重写成了真正的异步流水线:
- 主线程不再等待 I/O 线程。I/O 线程通过无锁环形队列接收任务,完成后把结果放回队列,主线程在方便的时候收割。两边解耦,各跑各的。
- I/O 线程承担更多工作:不只是 read/write,还包括完整的协议解析、回复序列化,甚至部分内存释放(lazy free 卸载)。
- 主线程专注命令执行——这是唯一必须串行的部分(保证 Redis 语义),其余全部剥离出去。
- 批量预取(prefetching):主线程在执行一批命令前,先对涉及的 key 做内存预取(
__builtin_prefetch),把哈希表节点提前拉进 CPU 缓存,减少执行阶段的 cache miss。这个细节非常「云厂商风格」——典型的榨干硬件式优化。
效果如何?AWS 在发布时给出的基准数据:在 r7g(Graviton3)实例上,Valkey 8.0 单节点跑到了约 117 万 RPS,相比同配置的 Redis 7.2 提升了约 3 倍。即便按保守口径打个对折,这也是键值存储单节点性能的一次跨越。
这里有个值得玩味的点:这套异步 I/O 的思路,AWS 早在 ElastiCache 的「增强 I/O」里就商用了多年。Valkey 8.0 相当于把云厂商的私有优化开源反哺——这正是分叉带来的结构性变化。
2.3 配置实战
# valkey.conf —— 8 核机器的典型配置
io-threads 7 # 建议:物理核数 - 1,给主线程留一颗
# Valkey 8.0 起不再需要 io-threads-do-reads yes(默认全异步)
# 配合 lazy free,把大 key 删除也卸载出主线程
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-user-del yes
lazyfree-lazy-user-flush yes
用 valkey-benchmark 验证(注意要开 pipeline 和多连接,否则测的是网络往返不是引擎):
valkey-benchmark -h 127.0.0.1 -p 6379 \
-t get,set -n 10000000 -c 512 -P 16 --threads 8 -d 128
经验值:io-threads 从 1 加到 7,GET 吞吐大致是线性上扬;超过物理核数后必掉,因为 I/O 线程和主线程开始互相抢核。容器环境要特别注意:如果 Pod 的 CPU limit 是 4,io-threads 写 8 只会带来疯狂的上下文切换。
三、Valkey 8.1:换掉用了十五年的哈希表
3.1 老 dict 的原罪
Redis 的 dict 是链式哈希:桶数组里存指针,指向 dictEntry 链表,每个 entry 里再存 key 指针、value 指针、next 指针。这个结构的问题:
- 指针跳来跳去,缓存极不友好。一次查找 = 桶数组一次内存访问 + 链表节点一次 + key 字符串一次,至少三次随机内存访问,每次都可能 cache miss。
- 每个 key 的元数据开销大。dictEntry 三个指针 24 字节,加上分配器开销,一个小 key 的「管理费」就得 30~40 字节。存 1 亿个 key,光管理费就是几个 GB。
3.2 新哈希表:开放寻址 + 缓存行对齐
Valkey 8.1(2025 年 3 月)引入了全新的哈希表实现,思路和 Google 的 SwissTable 一脉相承:
- 开放寻址代替链表:数据紧凑排布在桶数组里,消灭链表指针。
- 桶按 64 字节缓存行设计:一个桶正好一个 cache line,装 7 个元素外加 1 字节的元数据(存哈希值的高位片段)。查找时先在元数据字节上做快速比对,绝大多数不匹配的槽位不需要访问实际 key——一次内存访问筛掉 7 个候选。
- 每 key 内存开销下降约 20~30 字节。官方给的数据是典型场景下整体内存节省 10% 左右,key 越小、数量越多,收益越明显。
对做缓存的人来说,这个改动的意义不是「省了 10% 内存」这么简单,而是:同样的机器规格,能多装 10% 的热数据,缓存命中率直接受益。缓存系统的成本模型里,内存就是钱。
8.1 还顺手带来了官方 Bloom 过滤器模块(valkey-bloom,Rust 编写),把以前要靠 RedisBloom 商业模块解决的问题纳入了开源版图——这同样是许可证战争的延续:Redis Ltd. 的商业模块护城河,Valkey 在一块块拆。
四、Valkey 9.0:三个「等了十年」的功能
2025 年 10 月,Valkey 9.0 发布,这是分叉以来最重的一个版本。三个核心特性,每一个都是社区喊了多年的痛点。
4.1 原子槽位迁移:终结 resharding 的玄学故障
Valkey/Redis 集群把 key 空间划分为 16384 个槽(slot),每个节点负责一部分。老的迁移方式是逐 key 搬家:
1. 目标节点标记槽为 IMPORTING,源节点标记为 MIGRATING
2. 逐个 key 执行 MIGRATE(序列化 → 传输 → 反序列化 → 删除源)
3. 全部搬完后,更新槽位归属并广播
这套流程的坑,做过 Redis Cluster 运维的都懂:
- 迁移过程中,槽处于「一半在这、一半在那」的中间态,客户端会收到 ASK 重定向,遇到多 key 命令(MGET、Lua 脚本)直接报 CROSSSLOT 或 TRYAGAIN;
- 迁移中断(节点重启、网络抖动)会留下半迁移状态,需要人工
CLUSTER SETSLOT ... STABLE修复; - 大 key 的 MIGRATE 是阻塞的,一个 500MB 的 hash 能把源节点卡出主从切换。
Valkey 9.0 的**原子槽位迁移(Atomic Slot Migration)**换了思路:不搬 key,搬整个槽。Valkey 项目负责人 Kyle Davis 的解释很直白:迁移不再逐 key 进行,而是把整个槽以 AOF 格式流式传输到目标节点,传输期间源节点照常服务;传输完成后做一次原子交接,槽的归属瞬间切换。
好处是结构性的:
- 迁移期间 key 路由完全一致——槽归属在交接前不变,客户端不会撞上中间态;
- 交接是原子的——要么整个槽过去了,要么没过去,不存在半迁移残骸;
- 失败可以干净回滚——传输失败就丢弃目标侧数据,源节点毫发无损。
对平台团队来说,这意味着在线扩缩容从「深夜灰度、盯盘两小时」变成了普通操作。这个特性单独就值一次大版本号。
4.2 哈希字段过期:数据建模的解放
以前 Valkey 的 TTL 只能作用于整个 key。想给 hash 里的单个 field 设置过期?两条路:要么把 hash 拆成一堆独立 String key(丧失聚合性,内存暴涨),要么自己用 ZSET 存时间戳再定时扫描(引入轮询和竞态)。
9.0 直接支持 field 级过期:
# 用户会话:整体 24 小时过期,但验证码字段 60 秒过期
HSET user:1001 name "eggplant" level "9" otp "384756"
EXPIRE user:1001 86400
HEXPIRE user:1001 60 FIELDS 1 otp
# 查看字段剩余 TTL
HTTL user:1001 FIELDS 1 otp
# 取消字段过期
HPERSIST user:1001 FIELDS 1 otp
几个真实场景立刻变得优雅:
- 风控计数器:
HINCRBY risk:uid:1001 login_fail 1+ 字段级 5 分钟过期,一个 hash 管理一个用户的所有滑动窗口计数; - 设备心跳表:一个 hash 存全部设备的最后心跳,字段 90 秒过期,过期即离线,不再需要扫描比对时间戳;
- 多级缓存:同一个实体的不同属性给不同 TTL(基础信息 1 小时,实时库存 5 秒)。
实现上,字段过期沿用了主动过期(定期采样)+ 惰性过期(访问时检查)的双轨机制,过期元数据紧凑存储,只有设置了 TTL 的 field 才付出额外内存。注意:Redis 7.4 也加入了同类命令(这是双方分叉后「平行进化」的典型案例),但 Valkey 9.0 的版本在集群语义和复制格式上是独立实现。
4.3 集群模式多数据库:微服务的资源合并
这是社区呼声最高的一个。单机 Valkey 一直有 16 个编号数据库(SELECT 0~15),但集群模式十几年来只支持 db0。后果是:每个微服务的缓存都得独占一套集群,或者所有服务挤在 db0 里靠 key 前缀隔离——前者浪费资源,后者互相踩踏(SCAN 扫全库、FLUSHDB 团灭)。
Valkey 9.0 在集群模式下支持了完整的编号数据库:
# 集群模式下,不同服务使用不同逻辑库
valkey-cli -c -h cluster.internal -p 6379 -n 3 # 订单服务 → db3
valkey-cli -c -h cluster.internal -p 6379 -n 7 # 推荐服务 → db7
# FLUSHDB 只清空当前逻辑库,不影响其他服务
配合本来就有的 ACL,可以做到「一个物理集群、N 个逻辑租户」:
ACL SETUSER order-svc on >pass1 ~* +@all
# 9.0 之后可以按逻辑库维度做隔离规划,微服务不再需要一服务一集群
对中小团队,这直接省掉了一堆 3 节点小集群的运维负担;对平台团队,这是资源池化的地基。
4.4 其他值得一提的
- MPTCP(Multipath TCP)支持:多网卡链路聚合与冗余,对跨机房复制和高可用链路有实际价值;
- 集群规模上限拉到 2000 节点:官方口径下超大集群可达每秒 10 亿次请求量级(当然,这是集群总吞吐的理论口径,别拿单节点去脑补);
- 统一的自动故障转移配置:单机哨兵语义与集群语义收敛,开发、测试、生产环境用同一套 HA 配置心智;
- 安全关机模式:SHUTDOWN 需要确认语义,防止生产环境手滑;
- 客户端命令过滤器:连接级别的命令拦截,安全加固多了一层。
顺带一提,2026 年 6 月发布的 Valkey 9.1 里,有一大批 bug 修复和跨版本回迁(backport)工作是由 AI 智能体自动完成的——维护者用 Agent 自动化处理 cherry-pick 冲突和回归验证。开源基础设施项目开始吃 AI 工程化红利,这本身就是个信号。
五、代码实战:从部署到 Go 客户端
5.1 快速起一个 9.x 集群
# Docker 单机起 6 节点测试集群(3 主 3 从)
for port in $(seq 7000 7005); do
docker run -d --name valkey-$port --net host valkey/valkey:9 \
valkey-server --port $port --cluster-enabled yes \
--cluster-config-file nodes-$port.conf \
--appendonly yes --io-threads 4
done
docker exec -it valkey-7000 valkey-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
5.2 Go 客户端:风控滑动窗口实战
Valkey 有官方 Go 客户端 valkey-go(从 rueidis 演化而来,支持 RESP3 与客户端缓存):
package main
import (
"context"
"fmt"
"github.com/valkey-io/valkey-go"
)
// 基于 hash field TTL 的登录失败计数:5 分钟窗口,5 次锁定
func checkLoginRisk(ctx context.Context, c valkey.Client, uid string) (bool, error) {
key := "risk:login:" + uid
// HINCRBY 累加失败次数
n, err := c.Do(ctx, c.B().Hincrby().Key(key).Field("fail").Increment(1).Build()).AsInt64()
if err != nil {
return false, err
}
// 首次失败时给字段设置 300 秒过期(NX:仅当无 TTL 时设置,保证窗口不被续期)
if n == 1 {
c.Do(ctx, c.B().Hexpire().Key(key).Seconds(300).Nx().
Fields().Numfields(1).Field("fail").Build())
}
return n >= 5, nil
}
func main() {
client, err := valkey.NewClient(valkey.ClientOption{
InitAddress: []string{"127.0.0.1:7000"}, // 集群任一节点,自动发现拓扑
SelectDB: 3, // 9.0+:集群模式也能选逻辑库了
})
if err != nil {
panic(err)
}
defer client.Close()
locked, _ := checkLoginRisk(context.Background(), client, "1001")
fmt.Println("locked:", locked)
}
注意两个点:一是 HEXPIRE ... NX 保证滑动窗口的起点不被后续失败刷新(这决定了它是固定窗口还是「永不过期」);二是集群模式选库是 9.0 之后才有的能力,客户端版本要跟上。
5.3 无缝迁移自 Redis
Valkey 兼容 RESP2/RESP3 协议和 RDB/AOF 格式(9.x 与 Redis 7.2 的数据文件互通),存量业务迁移的标准路径:
# 方案一:主从复制切换(停机时间 = 一次主从切换)
# 在 Valkey 节点上:
valkey-cli replicaof <redis-host> 6379
# 等待同步完成(master_link_status:up 且 lag 归零)后,
# 切流量 → replicaof no one
# 方案二:RDB 冷迁移
redis-cli --rdb /backup/dump.rdb
valkey-server --dbfilename dump.rdb --dir /backup
兼容性红线要记住:Valkey 分叉自 Redis 7.2,Redis 7.4+ 的新命令和新编码它不保证兼容(反之亦然)。如果你的代码用了 Redis 8.x 的新数据类型或模块 API,先做全量回归再谈迁移。客户端侧基本零改动——绝大多数 Redis SDK 直连 Valkey 没有任何问题,但要用上 9.0 的新命令(HEXPIRE、集群多库),得升级到明确声明支持 Valkey 9 的客户端版本。
5.4 客户端缓存:RESP3 时代被低估的大杀器
Valkey 完整继承并强化了 RESP3 的客户端缓存(client-side caching / tracking)能力,valkey-go 把它做成了一等公民。原理:客户端在本地内存缓存热 key,服务端通过 invalidation 消息在数据变更时主动推送失效通知——相当于免费得到一层「永远一致」的进程内缓存。
// valkey-go 的 DoCache:本地缓存 + 服务端失效推送
resp := client.DoCache(ctx,
client.B().Hgetall().Key("user:1001").Cache(),
10*time.Minute, // 本地缓存上限时长
)
fields, _ := resp.AsStrMap()
fmt.Println(fields["name"], "from cache:", resp.IsCacheHit())
这套机制的收益在读多写少的场景非常暴力:命中本地缓存的读操作延迟从数百微秒降到数百纳秒,而且 Valkey 8.0 之后 invalidation 消息的推送也走异步 I/O 线程,服务端开销进一步降低。压测里,90% 读命中本地缓存时,同样的业务 QPS 下 Valkey 服务端 CPU 占用能降一个数量级。
坑也要说:tracking 模式下服务端要为每个连接维护 key 关注表(BCAST 模式可以按前缀订阅来缓解),连接数很多的场景要评估这部分内存;另外本地缓存吃的是应用进程的堆,Go 服务记得压测 GC 表现。
六、横向对比:Valkey 9 vs Redis 8 vs 其他玩家
选型绕不开对比,把 2026 年年中的格局摆一摆(以各自当前稳定版为准):
核心引擎性能:Valkey 9 的异步 I/O 线程 + 新哈希表组合目前是开源阵营单节点吞吐的天花板;Redis 8 也做了大量性能工作(新的 I/O 线程改进、内部 keyspace 优化),双方差距在缩小,但 Valkey 的多线程路线更激进。
功能广度:Redis 8 把 Search、JSON、TimeSeries、Bloom 全部并入核心发行版,开箱即用;Valkey 走「核心精简 + 官方模块」路线,valkey-bloom 已就绪,valkey-json、valkey-search 在推进。今天要向量检索一把梭,Redis 8 更省事。
集群运维:Valkey 9 的原子槽位迁移、集群多数据库、2000 节点上限是明显差异化;Redis 集群在这几个点上仍是老模式。这块 Valkey 领先身位不小。
治理与许可证:Valkey = BSD-3 + Linux 基金会中立治理;Redis = AGPLv3(或商业许可)+ 单一公司主导。给云厂商、发行版打包方、SaaS 供应商的答案几乎是唯一的;纯内部使用则两者皆可。
其他替代品:Dragonfly(多线程 shared-nothing 架构,单节点性能凶悍,但 BSL 许可证 + 集群生态弱)、KeyDB(多线程先驱,Snap 收购后进入维护模式,社区活跃度下滑明显)、Garnet(微软 .NET 系,协议兼容但生态尚早)。综合许可证、社区、生产验证三要素,Valkey 是目前「Redis 平替」里综合分最高的一个。
一句话总结:要全家桶选 Redis 8,要引擎性能与集群运维确定性选 Valkey 9,要中立许可证只能选 Valkey。
七、性能调优清单
生产上把 Valkey 9.x 榨到位,按优先级过一遍:
- io-threads = 物理核数 - 1,容器里以 CPU limit 为准;主线程和 I/O 线程避免和其他高负载进程混部。
- 打开全套 lazy free(见 2.3 节配置),大 key 删除、过期、淘汰全部异步化。
- maxmemory 留出 buffer:新哈希表虽然省内存,但 fork 做 RDB/AOF 重写时的 COW 开销仍在,maxmemory 建议不超过实例内存的 65%~70%。
- 优先用 hash field TTL 替代「拆 key + 独立 TTL」的老模式:百万级会话场景下,聚合成 hash 通常能省 30% 以上内存(省掉了大量 key 的全局字典条目和 TTL 条目)。
- 集群 resharding 用原子槽位迁移:升级到 9.0 后,检查你的运维脚本,把老的逐 key 迁移逻辑替换掉。
- 基准测试要对齐口径:开 pipeline(-P 16 起步)、多连接、值大小贴近生产,单条 GET 的裸延迟对比没有意义。
- 监控项更新:9.x 的 INFO 输出多了槽位迁移状态、逻辑库级别的 keyspace 统计,接入 Prometheus exporter 时注意版本适配。
八、冷静的边界分析
吹完技术,泼点冷水。以下几点决定了你该不该现在就切:
1. Redis 没有躺平。 Redis 8.x 把向量搜索、JSON、时序等原商业模块并入开源发行版(AGPLv3),单论功能广度,Redis 目前反而更「全家桶」。Valkey 的策略是核心引擎性能 + 通过模块(Bloom、JSON、Search 在陆续补齐)追赶。如果你重度依赖 RediSearch/RedisJSON 的成熟度,Valkey 生态还需要时间。
2. AGPLv3 对多数自用企业其实够用。 许可证焦虑主要属于「把 Redis 作为服务卖给别人」的厂商。内部使用 Redis 8.x 的 AGPL 版本,合规风险可控。选 Valkey 更多是选它的性能路线和治理模型(Linux 基金会中立治理 vs 单一商业公司),而不是被许可证逼的。
3. 平行进化带来的兼容性漂移是长期成本。 哈希字段过期两边都有、但实现细节不同;集群协议、复制格式会随时间越走越远。今天「基本兼容」,三年后可能就是两个物种。技术选型时要按「不可逆决策」来评估,别抱着「不行再切回去」的幻想。
4. 超大集群数字要祛魅。 2000 节点、10 亿 RPS 是能力上限的展示,99% 的团队一辈子用不到。对多数场景,9.0 真正值钱的是原子槽位迁移的运维确定性和多逻辑库的资源池化——这些是每天都在发生的收益。
九、总结与展望
回头看 Valkey 这三年的路线图,能看出一条非常清晰的主线:把云厂商内部验证过的工程优化,逐个开源化。8.0 的异步 I/O 线程、8.1 的缓存友好哈希表、9.0 的原子槽位迁移,没有一个是「炫技式创新」,全都是在超大规模生产环境里被逼出来的实用主义改进。
这也是我认为 Valkey 最值得关注的地方——它验证了一种开源基础设施的新范式:基金会中立治理 + 多云厂商供养工程师 + 激进但务实的性能路线。相比之下,单一商业公司控制的开源项目,永远绕不开「社区利益与股东利益冲突」的结构性问题。
给个直接的建议:
- 新项目:如果只用核心键值/缓存能力,直接上 Valkey 9.x,性能、内存、运维体验都是目前开源阵营的第一梯队;
- 存量 Redis ≤7.2:迁移成本接近零,可以在从库先挂 Valkey 灰度观察;
- 重度依赖 Redis Stack 模块:再等等,盯着 valkey-search、valkey-json 的成熟度,一年后再评估;
- 所有人:把 HEXPIRE 和原子槽位迁移加进你的工具箱,这两个特性会改变一批系统的数据建模和运维脚本。
键值存储这个赛道沉寂了太多年,一场许可证战争反而把它重新打活了。竞争才是开源世界最好的 GC——回收的是惰性,释放的是性能。