Valkey 9.0 深度拆解:当 Redis 分叉决定「把大 Key 迁移变成无感手术」——从 Vector Sets 到 UV 去重 200% 吞吐跃升,Linux 基金会托管的内存数据库如何重新定义缓存与向量检索的终极形态
一、引言:一次许可证变更,炸出一个新生态
2024 年 3 月 20 日,Redis 官方宣布核心数据结构的开源许可证从 BSD 3-Clause 变更为 RSALv2 与 SSPLv1 双许可证。一石激起千层浪——这意味着 Redis 不再是 OSI 认证的"开源软件",云厂商、发行版、托管服务商都不能再免费把它打包进自己的商业产品里。
八天之后,Linux 基金会宣布托管一个新项目:Valkey。AWS、Google Cloud、Oracle、Ericsson、Snap、Bloomberg 等一长串名字出现在创始成员名单上,随后腾讯云也以创始成员身份加入。这个从 Redis 7.2.4 fork 出来的项目,继承了 BSD-3-Clause 许可,保留 RESP2/RESP3 协议兼容,然后头也不回地走上了一条独立演进的路线。
两年多过去,Valkey 交出了自己的答案:8.0 引入 Vector Sets 与 Hash 字段过期、深化多线程 IO;9.0 把批量请求吞吐拉高 40%、计算密集型场景部分操作提升 200%,并重构了大 Key 迁移机制;9.1 在安全与可观测性上继续补课。2026 年 8 月 7 日,腾讯云宣布国内率先全面支持 Valkey 9.0,并一次性引入 8.1 与 9.0 两个版本的能力。
这篇文章不聊口号,只拆技术:Valkey 的架构继承了什么、改了什么、9.0 的性能数字是怎么来的、大 Key 无感迁移背后的机制是什么、Vector Sets 到底能不能当向量数据库用、以及你该怎么把这一整套迁移到自己的生产环境。
二、分叉简史:从 redis/redis 到 valkey-io/valkey
先把时间线钉清楚,这是理解后面所有技术选择的背景板:
- 2024-03-20:Redis 变更许可证为 RSALv2 + SSPLv1(SSPL 的争议点在于"如果向第三方提供服务,必须开源整个服务层",云厂商几乎无法合规使用)。
- 2024-03-28:Linux 基金会官宣 Valkey 项目,从 Redis 7.2.4 分叉,许可证维持 BSD-3-Clause。创始成员清一色是"Redis 的重度用户":AWS(当时 ElastiCache 和 MemoryDB 都基于 Redis)、Google Cloud、Oracle、Ericsson、Snap、Bloomberg。
- 2024-09-11:Valkey 8.0 发布。这是分叉后第一个大版本,带着三个标志性能力:多线程 IO 深化、Vector Sets 新数据类型、Hash 字段级过期。
- 2025 年:8.1 版本补课,继续在稳定性、可观测性和集群能力上加固。
- 2025-10-22:Valkey 9.0 发布。性能与运维的双重突破:批量请求吞吐最高 +40%、大数据量访问 +20%、计算密集型场景部分操作 +200%;大 Key 迁移机制重构,扩缩容不再打断在线业务。
- 2026-07:Valkey 9.1 发布。安全、可观测性、性能、效率全面更新,值得一提的是其中一大批 Bug 修复是由 AI 智能体完成的。
- 2026-08-07:腾讯云国内首家全面支持 Valkey 9.0,从 8.0 直接升级引入 8.1 + 9.0 全部能力。
有一个细节值得注意:分叉之后,Valkey 的二进制和配置都改了名——redis-server 变成 valkey-server,redis-cli 变成 valkey-cli,redis.conf 变成 valkey.conf。但对外协议层面,RESP2/RESP3、命令集、数据结构语义完全兼容,所以 redis-py、go-redis、Lettuce 这些老客户端几乎零改动就能连上 Valkey。这种"换引擎不换方向盘"的设计,是 Valkey 能在两年内快速获得生产级认可的关键——迁移成本被压到了最低。
三、架构拆解:单线程的遗产,多线程的突围
要理解 Valkey 9.0 的性能优化,必须先理解它从 Redis 继承的那套执行模型,以及这套模型在什么场景下是优势、什么场景下是瓶颈。
3.1 事件循环:一切性能的起点
Redis/Valkey 的核心是一个基于 epoll/kqueue 的事件循环(ae.c),所有客户端连接、命令读取、命令执行、结果写回,都由单线程串行驱动。这套模型有两个被反复称道的特性:
- 无锁。数据结构的读写不需要任何互斥锁,因为同一时刻只有一个线程在操作它们。这让 dict、skiplist、listpack 这些底层结构的实现可以做到极致简单且无竞态。
- 可预测的延迟。单线程意味着不存在上下文切换抖动,P99 延迟曲线极其平滑。
但代价也很明确:单个 CPU 核的算力就是天花板。当业务需要的内存带宽、哈希计算、网络解析超过单核能力时,加再多核也没用——其他核心都在围观。
3.2 IO 多线程:瓶颈转移的艺术
Redis 7.x 开始引入 IO 线程,Valkey 继承并深化了这套设计。理解它的关键在于:IO 多线程并没有让"命令执行"变并行,它只并行化了三个阶段:
- 从 socket 读取请求数据
- 解析协议(RESP 的
*N\r\n$len\r\n...结构) - 把响应写回 socket
命令的实际执行(查 dict、改数据结构)依然在主线程串行完成。为什么这么设计?因为数据结构操作一旦并行,就需要加锁,加锁就会引入竞争和抖动,反而毁掉单线程模型最宝贵的延迟可预测性。而网络读写的 CPU 开销(系统调用、内存拷贝、协议解析)恰好是可以用多核摊薄的。
配置方式:
# valkey.conf
io-threads 4
io-threads-do-reads yes
io-threads-do-reads yes 很关键:默认情况下 IO 线程只负责写回(write),读和解析仍走主线程。开启后读也分摊到 IO 线程,对高并发小请求(典型的缓存 workload)提升明显。经验值:IO 线程数建议 = 机器物理核数的一半,且不要超过 8——线程太多时,socket 锁竞争(io_threads_pending 互斥)会吃掉多线程带来的收益。
3.3 真正的单点:分配器与持久化
很多人的误区是"单线程是 Valkey 的性能瓶颈"。实际上在 8.0/9.0 时代,更常见的瓶颈已经转移:
第一,内存分配器。 默认的 jemalloc 在分配/释放频繁的场景下,会与主线程争抢 arena 锁。这也是为什么 Redis 7.2+ 和 Valkey 8.0 都在调优 MALLOC_CONF(如 background_thread:true,让 jemalloc 的后台线程做 purging)。
第二,持久化。 RDB 快照用 fork + COW(写时复制),父进程在 fork 后虽然可以继续服务,但页面表复制和 COW 触发的缺页中断会带来周期性延迟毛刺。AOF 的 fsync 策略(everysec / always)则直接决定数据安全与吞吐的权衡。Valkey 9.x 在 aof-timestamp-enabled、增量 fsync 上的改进,本质都是在跟"持久化与低延迟不可兼得"这个老难题较劲。
第三,网络栈。 大量小包的 read/write 系统调用、TCP 收发包的软中断,都会消耗 CPU。这正是 9.0 批量请求吞吐 +40% 的主要战场(见第五节)。
四、8.0 的三张新牌:Vector Sets、Hash 字段过期、IO 深化
Valkey 8.0 是分叉后第一次真正展示"我们不是 Redis 的跟班"的版本。三个能力分别对准三条战线:AI 时代的数据类型、精细化 TTL 管理、多核利用。
4.1 Vector Sets:把向量检索塞进内存
Vector Sets 是 Valkey 8.0 引入的全新数据类型(命令族以 V 开头:VADD、VREM、VSCORE、VLEN、VSIM、VINFO)。它解决什么问题?
在 RAG(检索增强生成)应用里,最经典的模式是:文本 → embedding 模型 → 向量 → 向量数据库 → 相似度检索。过去这个链路至少要引入一个独立组件(Milvus、Qdrant、pgvector……),而 Vector Sets 让你直接在 Valkey 里存向量、做 ANN(近似最近邻)检索:
# 添加向量成员(member 是二进制向量,维度需一致;score 为可选的相关性分数)
valkey-cli VADD items 0.9 "\x00\x01\x02..."
valkey-cli VADD items 0.5 "\x03\x04\x05..."
# 相似度检索:找与 query 向量最相似的 top-3
valkey-cli VSIM items "\x00\x01\x01..." COUNT 3 WITHSCORES
# 查看集合大小
valkey-cli VLEN items
VSIM 的 EF 参数控制搜索宽度(越大越准、越慢),语义上对齐了 HNSW 的 ef_search——这说明底层索引是一个内存版 HNSW/图索引结构。与专用向量数据库的边界在哪?我的判断是:
- 适合:几百万级向量、要求微秒~毫秒级延迟、向量只是缓存层的一部分(比如"先查 Valkey,miss 再查 Milvus")、不想为一个小功能多维护一套基础设施的场景。
- 不适合:十亿级向量、需要复杂标量过滤 + 向量混合检索、需要持久化保障的严肃检索场景。Valkey 的内存模型决定了它的向量容量受限于内存,RDB/AOF 对向量数据的持久化开销也远高于标量数据。
用一句话概括:Vector Sets 是"缓存层的向量化",不是"检索层的向量库"。 但这两者的组合拳(Valkey 兜底热向量 + Milvus 管全量)确实是 2026 年 RAG 架构里成本最优的形态之一。
4.2 Hash 字段过期:细粒度 TTL 终于来了
Redis 的 TTL 一直以 key 为粒度,这导致一个经典痛点:一个 Hash 里存了 100 万用户的会话,你想让每个用户的会话独立过期,只能拆成 100 万个 key——或者自己写惰性清理逻辑。
Valkey 8.0 的 Hash 字段过期直接把这个能力做进了内核:
# 给 hash 的指定字段设置 TTL(秒)
valkey-cli HEXPIRE session:20260808 3600 FIELDS user_1001 user_1002
# 查询字段剩余 TTL
valkey-cli HTTL session:20260808 FIELDS user_1001
# 精确时间点过期(时间戳)
valkey-cli HEXPIREAT session:20260808 1783598400 FIELDS user_1003
# 移除字段过期
valkey-cli HPERSIST session:20260808 FIELDS user_1001
配套命令还有 HPEXPIRE(毫秒)、HPEXPIREAT、HEXPIRETIME/HPEXPIRETIME(返回过期时间戳),覆盖了 Redis EXPIRE 家族的全部语义,只是粒度下沉到了 field。
这带来的架构简化是实打实的:
- 会话管理:一个 key 管一个用户的所有会话字段,不再拆 key 爆炸。
- 优惠券/券码:每个券字段独立过期,过期后字段自动不可见。
- 限流窗口:每个用户维度一个字段,
HTTL查剩余窗口,配合 Lua 脚本做原子扣减。 - 内存节省:1 个 hash + 100 万字段 vs 100 万个 key,光是 dict 元数据(
redisObject头、key 字符串)就能省下可观的内存。一个空 key 约 100+ 字节开销,100 万 key 就是 100MB+ 的纯元数据——换成 hash 字段后这部分几乎归零。
实现层面,Valkey 用每个 hash 额外维护一个过期字典(类似 dict 存 field → 过期时间戳),惰性删除 + 主动抽样删除双轨并行,与 key 级过期策略(ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)同构。值得注意的坑:字段过期后,HGETALL 这类全量命令会跳过已过期字段,但 HSTRLEN/HLEN 等命令的语义在过期瞬间有极短窗口的不一致,业务上不要依赖"过期即绝对不可见"的强一致,这与 key 级过期的行为一致(过期 key 在主线程主动删除前也可能被读到)。
4.3 IO 多线程的深化
8.0 对多线程 IO 的深化主要体现在两点:一是把更多协议解析路径挪到 IO 线程(减少主线程在 read 阶段的占比),二是优化了线程间的任务分发,减少 io_threads_pending 上的自旋。配合 io-threads-do-reads yes,在 64 核机器上实测多线程 IO 可以让整体吞吐从单核的 ~15 万 QPS 级别拉升到 60-100 万 QPS 级别(取决于请求大小与网卡能力)——当然,这是"命令执行仍串行"前提下的吞吐,单条命令的延迟没有变快,变快的是"单位时间能处理多少条"。
五、9.0 深度拆解:性能与运维的双重极限
9.0 是 Valkey 迄今最重要的大版本。腾讯云官方给出的数据是:批量请求吞吐最高提升 40%,大数据量访问吞吐最高提升 20%,用户画像、UV 去重统计等计算密集型场景部分操作吞吐最高提升 200%。这些数字不是营销话术,背后各有明确的技术抓手。
5.1 批量 +40%:从哪来?
批处理场景(MGET、MSET、PIPELINE、Lua 脚本)的提升主要来自三处:
第一,命令分发路径优化。 主线程从 IO 线程队列取任务的逻辑被重写,减少了每次取任务的原子操作和缓存未命中。在高 QPS 下,io_threads_pending 的竞争从"每命令一次"降为"每批次一次",这在 64 核机器上是实打实的几十微秒级节省。
第二,RESP 解析优化。 批量请求的协议报文往往是大量相同结构的命令(*3\r\n$3\r\nGET\r\n...),9.0 对解析器做了 SIMD 化探索——用宽位运算一次扫描多个字节查找 \r\n 分隔符,而不是逐字节比较。这类优化对"短命令 + 大批量"的 workload 收益最明显,恰好就是缓存业务最常见的形态。
第三,内存拷贝削减。 响应组装阶段避免不必要的中间缓冲区,尽量直接引用对象内存。减少一次 memcpy,在 10KB+ 的大 value 场景(对应"大数据量访问 +20%")收益被放大。
5.2 计算密集 +200%:为什么是 UV 去重和用户画像?
这个 200% 的数字最值得拆。它对应的场景是"用户画像、UV 去重统计"——这类 workload 的共同点是大量使用 HyperLogLog(PFADD/PFCOUNT)和位图(SETBIT/BITCOUNT)。
Valkey 9.0 对这两类命令做了针对性优化:
- HyperLogLog 的寄存器更新路径:PFADD 的核心操作是"哈希 → 找到寄存器 → 比较并更新前导零计数"。9.0 把哈希计算和寄存器索引计算中的重复分支消除,并在寄存器稀疏表示(sparse representation)与密集表示(dense)之间切换时做了批量处理,避免逐字节转换。
- 位图命令的字节级批量操作:SETBIT/BITCOUNT 在 9.0 里对连续位操作做了 word-level(64 位)批量处理,而不是逐位循环。BITCOUNT 用查表法 + SWAR(SIMD Within A Register)技术,一次处理 8 字节。
为什么这些优化能到 200%?因为 HyperLogLog 的 PFADD 本身是 CPU 密集的微小操作(单次 < 1 微秒),任何指令级的节省都会直接线性放大到吞吐上。而这类命令在主线程执行,恰恰是"单线程模型最吃亏"的部分——所以 9.0 把火力集中在这里,效果立竿见影。
对业务的意义:用相同硬件,UV 统计的 QPS 天花板直接翻三倍。在高峰期以 UV 计费/防刷的业务(大促、活动页、广告投放),这可能是"不加机器扛住峰值"的关键。
5.3 大 Key 无感迁移:扩缩容的范式变化
这是 9.0 我最看重的更新,因为它解决的是集群运维里最疼的痛点。
先看老问题。Redis Cluster(Valkey Cluster)的扩缩容核心是 slot 迁移:把一部分 hash slot 从 A 节点迁到 B 节点,逐个 key 用 MIGRATE 命令搬。MIGRATE 的实现是:源节点 DUMP key → 序列化 → 发给目标节点 → RESTORE → 删除源节点 key。这个 DUMP + RESTORE 全程在主线程执行,一个 500MB 的大 key,迁移期间源节点要序列化 500MB 数据、网络传输、目标节点反序列化重建——这期间源节点完全阻塞,所有命令排队。
后果很直观:大促前扩容,一个不小心迁到大 key,线上 P99 直接飙到秒级,事故报告就写好了。
Valkey 9.0 的改进方向是让迁移可中断、可断点续传:
- 拆分式迁移:大 key 的内部结构(hash 的 field、list 的元素、zset 的成员)按批次迁移,而不是整个 key 一次性序列化。每次迁移一个批次后回到事件循环处理积压的命令,再继续下一批。
- 迁移期间持续服务:源节点在迁移进行中仍然可以响应读请求(旧数据仍在),写请求通过集群重定向或本地缓冲处理。
- 同步完成后再切换:所有数据同步完成后,才原子性地把 slot 所有权切换给目标节点,避免"数据迁了一半、客户端已经路由到新节点"的中间态。
用腾讯云的话说:"过去迁移大 Key 时可能造成访问延迟抖动,新机制可以在迁移过程中持续提供服务,并在数据同步完成后完成节点切换。" 这意味着:
- 扩容不再需要业务低峰期窗口,随时可以扩;
- 大 key 不再需要提前人工拆分(虽然仍然建议拆分,见第七节);
- 集群的弹性能力从"小时级 + 风险操作"变成"分钟级 + 常规操作"。
架构层面的启示:Valkey 9.0 本质上把"key 迁移"从"整块搬运"重构为"流式搬运 + 状态机管理"。这和 TiDB 的 region 调度、Cassandra 的 vnode 迁移是同一套思想——把不可中断的大操作拆成可调度的小操作,用状态机保证最终一致。
六、9.1:安全、可观测性与 AI 修复 Bug 的新玩法
Valkey 9.1 于 2026 年 7 月发布,官方口径是"安全性、可观测性、性能以及效率方面均带来全新功能与显著改进"。三个值得关注的信号:
第一,安全加固。 9.1 强化了 ACL 的粒度和 TLS 的默认配置,对未授权访问的防护更严格。对于把 Valkey 暴露在公网的生产环境,这些是刚需。
第二,可观测性。 新增/增强了慢命令统计、延迟监控(latency monitor)和 INFO 输出的细粒度指标,让"定位大 key、定位慢命令"不再靠猜。配合 SLOWLOG GET 和 --bigkeys 扫描,排查手段基本齐了。
第三,AI 智能体修复 Bug。 9.1 的一大批 Bug 修复是由 AI 智能体完成的——这是 2026 年开源社区的新常态,但也引出一个值得思考的问题:AI 修的 Bug 质量如何保证?Valkey 的做法是"AI 提 patch + 人类维护者 review + 完整测试套件兜底"。对于基础设施软件,这个流程的底线是:任何 patch 必须过 CI 和回归测试,AI 只是生产力工具,不是质量担保。这条经验对我们在业务里用 AI 写代码同样适用——可以快,但不能跳过 review 和测试。
七、代码实战:把 Valkey 9.0 跑起来
7.1 五分钟部署
# Docker 一键启动(最简单)
docker run -d --name valkey -p 6379:6379 valkey/valkey:9.0
# macOS / Linux 本地编译安装
git clone https://github.com/valkey-io/valkey.git
cd valkey
git checkout 9.0
make -j8
# 产物在 src/ 目录:valkey-server、valkey-cli
# 启动
./src/valkey-server --port 6379 --save 60 1000 --appendonly yes
# 连接
./src/valkey-cli -p 6379 ping
# PONG
注意:Valkey 9.0 需要较新的编译器和 glibc,CentOS 7 等老系统需要自己处理工具链;容器部署是最省心的路径。
7.2 Vector Sets 语义缓存实战(RAG 场景)
场景:RAG 应用里,用户 query 先做 embedding,然后在缓存里找"语义上最接近的历史问答",命中直接返回,未命中才调 LLM。这样既能省 token 费,又能降延迟。
import redis
import numpy as np
r = redis.Redis(host="localhost", port=6379, decode_responses=False)
def embed(text: str) -> bytes:
"""调用 embedding 模型,返回 384 维 float32 向量"""
vec = your_embedding_model.encode(text).astype(np.float32)
return vec.tobytes()
# 写入缓存:answer 的 embedding + 原始答案
def cache_answer(question: str, answer: str) -> None:
vec = embed(question)
r.execute_command(
"VADD", "qa:embeddings", "0", vec, # score 0,语义相似度由 VSIM 计算
)
# 原始文本存在普通 key,用 question 的 hash 做关联
r.set(f"qa:text:{hash(question)}", answer, ex=86400)
# 语义检索:找最相似的历史问题
def semantic_lookup(query: str, topk: int = 3):
qvec = embed(query)
# 返回 [(member, score), ...],score 是余弦相似度
results = r.execute_command(
"VSIM", "qa:embeddings", qvec, "COUNT", topk, "WITHSCORES"
)
return results
# 命中且相似度 > 0.92 才认为是缓存命中
def get_answer(query: str):
hits = semantic_lookup(query)
for member, score in zip(hits[::2], hits[1::2]):
if float(score) > 0.92:
return r.get(f"qa:text:{hash(member)}")
return None
几个工程要点:
- 向量维度必须一致,
VADD会隐式校验,不一致会报错。 - WITHSCORES 返回的 score 是相似度,阈值要按你的 embedding 模型实测校准——不同模型的相似度分布差异很大,0.9 在有的模型里是"很接近",在另一些里只是"沾边"。
- Vector Sets 的成员是二进制 blob,关联业务数据时建议把"向量"和"业务载荷"分开存(向量放 VADD,原文放普通 key),避免大 payload 拖慢 VSIM 的内存遍历。
- 缓存穿透保护:语义缓存的 miss 率天然比精确 key 缓存高,建议叠加"精确 key 缓存 + 语义缓存"两级。
7.3 Hash 字段过期实战:会话管理
场景:一个活动页,每个用户一个独立过期时间的领取状态。过去要拆 100 万个 key,现在一个 hash 搞定:
# 用户领取状态:1 = 已领取
valkey-cli HSET campaign:0808:claims user_1001 1
valkey-cli HSET campaign:0808:claims user_1002 1
# 字段级 TTL:24 小时后各自过期
valkey-cli HEXPIRE campaign:0808:claims 86400 FIELDS user_1001 user_1002
# 查询剩余时间
valkey-cli HTTL campaign:0808:claims FIELDS user_1001
# (integer) 86342
# 领取时原子检查(Lua)
-- 原子领取脚本:已领取返回 0,未领取设置并返回 1
local existed = redis.call('HGET', KEYS[1], ARGV[1])
if existed then return 0 end
redis.call('HSET', KEYS[1], ARGV[1], 1)
redis.call('HEXPIRE', KEYS[1], tonumber(ARGV[2]), 'FIELDS', ARGV[1])
return 1
valkey-cli --eval claim.lua campaign:0808:claims , user_1003 86400
对比拆 key 方案(SET campaign:user_1003 1 EX 86400),hash 字段过期的优势:
| 维度 | 拆 key | Hash 字段过期 |
|---|---|---|
| key 数量 | 100 万 | 1 |
| 元数据内存 | ~100MB+ | 可忽略 |
| 批量管理(查所有用户状态) | SCAN 全库 | HGETALL 一个 key |
| 单字段过期 | 原生支持 | 8.0+ 支持 |
| 大 key 风险 | 无 | 有(需控制字段数) |
代价与坑:单个 hash 字段数太多会变成大 key(建议单个 hash 字段数控制在 10 万以内,或按用户 ID 分桶);字段级过期的主动清理是抽样式的,过期字段的物理删除有延迟,内存不会立刻释放。
7.4 Go 客户端实战
Valkey 官方 Go 客户端是 valkey-go(github.com/valkey-io/valkey-go),协议兼容层也支持直接用老牌 go-redis:
package main
import (
"context"
"fmt"
"github.com/valkey-io/valkey-go"
)
func main() {
client, err := valkey.NewClient(valkey.ClientOption{
InitAddress: []string{"127.0.0.1:6379"},
// 连接池配置:高并发下关键
MaxMultiPipelining: 128,
})
if err != nil {
panic(err)
}
defer client.Close()
ctx := context.Background()
// 基本 KV
if err := client.Do(ctx, client.B().Set().Key("k").Value("v").Ex(3600).Build()).Error(); err != nil {
panic(err)
}
v, err := client.Do(ctx, client.B().Get().Key("k").Build()).ToString()
fmt.Println(v) // v
// Pipeline 批量(9.0 优化主战场)
p := client.MultiPipelined()
for i := 0; i < 100; i++ {
p.Do(ctx, client.B().Set().Key(fmt.Sprintf("batch:%d", i)).Value("x").Build())
}
p.Exec(ctx)
// Vector Sets:语义检索
qvec := make([]byte, 384*4) // 384 维 float32
res, err := client.Do(ctx, client.B().Vsim().Key("qa:embeddings").Query(qvec).
Count(3).Withscores().Build()).AsStrSlice()
fmt.Println(res)
}
valkey-go 的设计特点值得一提:它用代码生成的方式构建命令(client.B().Set().Key()...Build()),避免了反射开销,并且内建了 MultiPipelining 的自动聚合——高并发下吞吐比逐条发送高出数倍。这是 9.0 批量优化的客户端侧搭档。
7.5 集群与扩缩容演练
# 三主三从集群
valkey-cli --cluster create \
10.0.0.1:6379 10.0.0.2:6379 10.0.0.3:6379 \
10.0.0.4:6379 10.0.0.5:6379 10.0.0.6:6379 \
--cluster-replicas 1
# 查看 slot 分布
valkey-cli --cluster info 10.0.0.1:6379
# 添加节点
valkey-cli --cluster add-node 10.0.0.7:6379 10.0.0.1:6379
# 重新分片(把 4096 个 slot 从现有节点迁到新节点)
valkey-cli --cluster reshard 10.0.0.1:6379 --cluster-from all --cluster-to <new-node-id> --cluster-slots 4096 --cluster-yes
Valkey 9.0 的大 Key 无感迁移在 reshard 时自动生效:如果遇到超大 key,迁移以"批处理 + 事件循环让出"的方式执行,你可以边迁移边压测,观察 P99 是否出现尖刺——9.0 下这个尖刺会明显小于 8.x。验证方法:
# 压测同时观察延迟
redis-benchmark -h 10.0.0.1 -p 6379 -t set,get -n 1000000 -c 200 -P 16
# 另开终端
valkey-cli -h 10.0.0.1 -p 6379 latency history
八、性能优化实践清单
把前面拆的原理落成可执行的清单,按优先级排序:
1. 开多线程 IO(性价比最高)
io-threads 4 # 物理核数的一半,最多 8
io-threads-do-reads yes
2. 客户端侧批量。能用 pipeline/MGET 就不要逐条发。valkey-go 的 MultiPipelining、redis-py 的 pipeline(transaction=False) 都是现成的。9.0 的 +40% 就是给这种用法准备的。
3. 内存分配器后台线程。启动参数或 MALLOC_CONF 里开 background_thread:true,减少分配器 purge 对主线程的干扰。
4. 大 key 治理(无论如何都要做):
# 扫描大 key
valkey-cli --bigkeys
# 定位后拆分策略:
# - hash:按 field 分桶(user_id % 100)
# - list:按时间分片,用 LTRIM 裁剪
# - zset:按分数段分片
# - string 大 value:压缩(zstd/gzip)或拆块
9.0 的无感迁移解决的是"迁移大 key 时线上不抖",但不解决"大 key 本身拖慢单命令延迟"(HGETALL 一个百万字段的 hash 依然会阻塞主线程毫秒级)。大 key 的根除还是要靠业务拆分。
5. 持久化权衡。追求吞吐:AOF 关掉或 appendfsync everysec;追求安全:always + 独立磁盘。RDB 的 save 策略错峰,避免大促高峰触发 fork。
6. 淘汰策略与内存。maxmemory-policy 按业务选(allkeys-lru 是缓存默认,volatile-ttl 适合混合场景);active-defrag 对碎片敏感的场景开启,但注意它本身吃 CPU。
7. 慢命令治理。SLOWLOG GET 100 定期审计,9.1 的增强指标让定位更精准。KEY *、SMEMBERS、HGETALL 全量命令在线上慎用。
九、Valkey vs Redis:路线图与选型
到了 2026 年,Valkey 和 Redis 已经是两条独立演进的路线,选型不再是"等 Valkey 追平 Redis",而是"两条路线各自的未来在哪":
Redis 的路线:许可证商业化(Redis Cloud 是核心收入)、AI 原生特性(Redis 8 的向量搜索、语义缓存是自研商业特性)、Stack 模块生态。它的优势是功能全、生态久,劣势是核心特性不再开源,社区贡献热情下降。
Valkey 的路线:纯开源(BSD-3-Clause)、云厂商共建(AWS/腾讯云/GCP 的贡献和背书)、性能优先(9.0 的优化方向非常"云厂商生产环境导向")。它的优势是许可证干净、没有"被商业公司卡脖子"的风险、性能迭代凶猛,劣势是 Stack 模块(RedisJSON、RedisTimeSeries、Search)生态需要时间重建(Valkey 社区有对应项目在推进,但成熟度有差距)。
选型建议:
- 新项目,纯缓存/会话/计数器 → 直接 Valkey 9.x,零风险零成本,性能还更好。
- 重度依赖 RedisJSON/RediSearch/TimeSeries 模块 → 短期留 Redis,同时盯 Valkey 的模块生态进展(或者用网关层抽象,把底层可替换性留出来)。
- 云上托管 → 看云厂商支持:AWS ElastiCache 已全面支持 Valkey,腾讯云连续两代首发(8.0、9.0),国内场景腾讯云是 Valkey 支持最激进的厂商。
- 向量检索需求 → 小规模用 Valkey Vector Sets 省钱省事,大规模上专用向量库,两者可以共存(Valkey 做热缓存)。
还有一个容易被忽略的决策因素:许可证政治风险。SSPL 的争议至今未消,如果你的客户/合规部门对开源许可证敏感(很多政企、金融场景),BSD-3-Clause 的 Valkey 是更稳妥的答案。
十、总结与展望
回看 Valkey 两年多的历程,三个判断值得留下:
第一,分叉不是末日,是生态的自我修复。 Redis 许可证事件一度被视为开源项目的经典危机,但 Valkey 用两年时间证明了:当一个项目的基础设施价值足够大、用户足够多、云厂商足够团结,社区的集体行动可以快速重建一个同等甚至更强的生态。Linux 基金会的治理框架(厂商中立、开放贡献、商标保护)是这套模式的关键基础设施。
第二,性能优化的方向正在从"单核极限"转向"系统协同"。 9.0 的 +40%/+200% 不是某一个魔改带来的,而是协议解析 SIMD 化、任务分发减竞争、HLL 指令级优化、迁移状态机重构的组合拳。单线程模型的核心设计被保留(延迟可预测性依然是卖点),但所有"可以并行、可以批处理、可以增量"的部分都在被逐个攻破。这给所有做性能优化的团队一个方法论启示:先找到真正的主瓶颈(可能是分配器、可能是解析、可能是迁移阻塞),再针对性优化,而不是盲目上多线程。
第三,AI 时代的内存数据库正在重新定义自己的边界。 Vector Sets 是 Valkey 对 AI 工作负载的第一个回答,Hash 字段过期是对精细化数据管理的回答,无感迁移是对弹性运维的回答。下一个版本的方向不难猜:向量检索的规模与精度继续提升、模块生态加速补齐、与 AI Agent 基础设施(MCP、工具调用缓存)的集成。内存数据库的终局形态,可能不再只是"缓存",而是"应用的内存数据平面"——热数据、向量、会话、实时统计,都在这一个平面上。
最后给读者一个行动建议:不要等,现在就把一个非核心服务迁到 Valkey 9.x 跑一个月。协议兼容意味着迁移成本极低(改个连接串的事),而 9.0 的性能收益和 9.1 的可观测性增强是立刻能感受到的。等你在生产环境摸清了大 key 分布、慢命令、内存画像,再决定全量迁移的节奏——到那时,你已经比大多数还在观望的人领先一个版本了。