Valkey
一、是什么
1. Valkey 的准确定位
Valkey 是采用 BSD-3-Clause 许可的开源内存数据结构存储,可以作为数据库、缓存、消息代理和流处理引擎。它保存的是 Key 与数据结构对象,不是关系表;核心数据类型包括 String、Hash、List、Set、Sorted Set、Bitmap、HyperLogLog、Geo 和 Stream,并提供事务、Lua、过期、淘汰、复制、Sentinel 和 Cluster。
Valkey 源于 Redis OSS 7.2 开源代码线,但已经成为独立项目。它保留 RESP2、RESP3、Redis OSS 7.2 配置和命令的兼容基础,同时继续发展自己的版本、模块 API、Cluster 能力、可观测性和性能优化。项目主页是 valkey.io,源码和发布包位于 valkey-io/valkey,版本与下载入口位于 Valkey Download。
Valkey 不是“任何 Redis 版本都能直接替换”的镜像。官方迁移文档明确给出的兼容基础是 Redis OSS 7.2 及更早开源版本;Redis Community Edition 7.4 及以后生成的数据文件不能直接作为 Valkey 的 RDB 或 AOF 输入。客户端能够 PING、普通命令能够执行、数据文件能够恢复、故障切换能够完成,是四个不同的验收层次。
2. 版本和发布线
当前稳定发行线以 9.1.1 为基线,官方镜像可以固定为 valkey/valkey:9.1.1。官方同时维护 9.0、8.1、8.0 和 7.2 等分支的修复版本。生产环境不能使用 latest,也不能只写主版本;应固定补丁版本和镜像 digest,并在升级评审中核对安全修复、数据文件兼容、模块兼容和客户端行为。
查看服务端真实身份时不能只读 redis_version。Valkey 为兼容旧客户端可能继续返回 Redis 兼容版本字段,实际识别应同时查看 server_name 和 valkey_version:
valkey-cli INFO server | grep -E "server_name|valkey_version|redis_version"
valkey-server --version
valkey-cli --version3. Valkey 一次请求的执行链
客户端先从连接池取得连接,把命令编码为 RESP 请求并写入 Socket;服务端事件循环读取字节、解析命令和参数,检查 ACL、Key 权限、参数与数据类型,然后在命令执行路径访问 Key 字典和数据结构对象。命令完成后,响应进入客户端输出缓冲区,再由网络事件发送给客户端。写命令还可能触发过期字典、AOF、复制传播、客户端通知和后台释放。
应用看到的耗时不只来自命令执行。连接池等待、网络往返、请求排队、Value 序列化、输出缓冲区积压、AOF fsync、fork 和数据库回源都可能进入最终延迟。SLOWLOG 主要记录命令执行阶段,不能替代应用端完整链路追踪。
SLOWLOG GET 50
LATENCY LATEST
LATENCY DOCTOR
INFO commandstats
INFO clients4. 单线程、后台线程和可并发任务
Valkey 的核心命令执行仍以顺序执行模型为基础,使单条命令天然具有原子执行语义,并避免为每个 Key 操作引入复杂锁竞争。单线程不代表整个进程只有一个线程。网络 IO、文件关闭、异步释放、AOF fsync 等任务可以由后台线程承担;RDB 生成和 AOF 重写通常会 fork 子进程;部分新能力还会引入受控的并行执行路径。
顺序执行只保证某条命令执行期间没有另一条普通命令插入,不能保证由多条命令组成的业务流程原子。GET 后在应用中计算再 SET 会出现并发覆盖;需要使用单条原子命令、事务、Lua、版本号或事实库事务解决。
INCR counter:order
SET dev:your-project:lock:order:1001 <owner-token> NX EX 10
MULTI
SET tx:key value
INCR tx:counter
EXEC5. 内存对象、Key 字典和过期字典
每个逻辑数据库至少维护 Key 空间和过期信息。Key 字典负责从 Key 找到对象,过期字典保存带 TTL Key 的过期时间;Value 再根据类型和规模选择内部编码。小 Hash、List、Set 和 Sorted Set 可以使用更紧凑的编码,规模超过阈值后转换为更适合增删查改的数据结构。
这意味着容量不能只统计业务 Value。Key 字符串、对象头、字典桶、过期元数据、数据结构节点、客户端缓冲区、复制 backlog、内存碎片和 fork 写时复制都要进入预算。
INFO memory
MEMORY STATS
MEMORY USAGE dev:your-project:prod:user:1001
OBJECT ENCODING dev:your-project:prod:user:1001
INFO keyspaceTTL 到零不等于内存字节立即下降。读取已过期 Key 时会被动删除,后台过期周期会主动抽样清理;大对象释放还可能进入 LazyFree 队列。线上判断过期压力时要同时观察 expired_keys、evicted_keys、lazyfree_pending_objects、RSS 和业务命中率。
6. 数据结构与使用边界
String 适合整体缓存、计数器、Session 和位图;Hash 适合字段级读写对象;List 适合简单队列和有序序列;Set 适合去重和集合关系;Sorted Set 适合排行榜、优先级和延迟任务;Stream 适合消费组、确认和 Pending 管理。选择数据结构时,决定因素不是命令是否存在,而是访问模式、成员规模、返回数量、原子边界和失败恢复方式。
SET product:sku-1001 '{"id":"sku-1001","price":3999}' EX 1800
HSET user:1001 name zhangsan login_count 1
SADD product:sku-1001:buyers user:1001
ZINCRBY rank:daily:current 100 user:1001
XADD order:events * order_id 1001 status createdValkey 不适合复杂多表关联、任意条件过滤、大范围报表和长期历史分析。Stream 也不能自动替代专业消息系统;库存、订单、支付和权限等事实数据仍需要数据库、幂等、补偿和对账。
7. 持久化、复制、Sentinel 和 Cluster
RDB 保存某个时间点的内存快照,AOF 记录能够重建状态的写命令。RDB 适合备份和快速加载,但快照间隔内可能丢数据;AOF 能缩小写入丢失窗口,但需要处理 fsync、重写、磁盘空间和文件恢复。两者同时启用时,重启通常优先使用更完整的 AOF 恢复。
主从复制默认主要是异步传播。Primary 返回写成功时,Replica 不一定已经应用该写入;故障切换仍可能丢失尚未传播的数据。Sentinel 解决单分片主节点发现和自动切换,不解决容量分片。Cluster 把 Key 映射到槽位并分散到多个 Primary,同时通过 Replica 提供一定故障恢复能力。
Valkey Cluster 使用异步复制和全互联 Cluster bus。多 Key 命令只有在相关 Key 落入同一槽位时才能执行,Hash Tag 可以让一组 Key 使用相同哈希片段。从 9.0 开始 Cluster 可以通过 cluster-databases 启用多个数据库,但默认值仍为 1;非零数据库还涉及 ACL 数据库权限、客户端 SELECT 与重连行为,不能把逻辑数据库当成多租户隔离。
8. 官方资源和核心命令入口
Valkey 的安装、迁移、持久化和 Cluster 行为应以官方文档为准:安装、迁移、持久化、Cluster 教程 和 Cluster 规范。遇到版本差异时,应同时核对目标版本发布说明和命令文档,不能用 Redis 文章或旧配置代替 Valkey 事实。
INFO server
INFO memory
INFO persistence
INFO replication
COMMAND INFO GET SET EVAL
CONFIG GET *
ACL LIST二、为什么
1. 什么时候选择 Valkey
当系统需要基于 Key 的低延迟访问、原子计数、TTL、集合计算、排行榜、轻量事件流或共享 Session,并且团队希望采用开放许可、社区治理清晰、Redis OSS 7.2 兼容基础明确的实现时,Valkey 是合理选择。已有 Redis OSS 7.2 及更早版本的应用,也可以通过协议、配置、数据文件或复制方式迁移,但仍要进行客户端、脚本、模块、持久化和故障切换验证。
选择 Valkey 之前先回答数据能否丢失、是否能重建、容量能否放入内存、访问是否主要按 Key 和有限范围、故障时是否允许回源、需要单分片高可用还是水平分片。只因为“和 Redis 命令差不多”不足以完成选型。
INFO keyspace
INFO memory
INFO persistence
INFO replication
SLOWLOG GET 502. Valkey、Redis 和 Memcached 怎么选
| 判断维度 | Valkey | Redis | Memcached |
|---|---|---|---|
| 数据模型 | 多种数据结构、事务、Lua、Stream | 多种数据结构,社区版与商业能力需按版本核对 | 简单 Key-Value 缓存 |
| 持久化 | RDB、AOF、组合模式 | RDB、AOF,按具体发行版核对 | 通常不提供持久化 |
| 高可用 | 复制、Sentinel、Cluster | 复制、Sentinel、Cluster,按发行版核对 | 通常由客户端一致性哈希和多实例承担 |
| 迁移基础 | Redis OSS 7.2 及更早版本 | 自身版本升级路径 | 不兼容 Redis 数据结构和协议 |
| 适合重点 | 开放兼容线、缓存、数据结构和分布式部署 | Redis 生态或特定 Redis 能力 | 极简对象缓存和水平扩展 |
如果只需要简单对象缓存、没有持久化、Lua、集合和服务端分片需求,Memcached 的模型更简单。依赖 Redis Community Edition 新版本能力、Redis Stack 模块或商业服务时,不能仅凭命令名称判断 Valkey 可替代,应按真实命令、模块和数据迁移方式评估。
3. 数据角色决定架构
纯缓存可以关闭持久化,采用明确 TTL 和淘汰策略,故障时通过受控回源恢复;Session 和临时状态需要 TTL、安全边界和登录降级;库存、订单和权限状态必须有事实库、幂等和补偿;Stream 事件需要 ACK、Pending、重试和保留策略。相同 Valkey 命令放在不同数据角色里,RPO、RTO 和恢复方法完全不同。
单机适合学习、开发和可重建的小型缓存;主从提供读扩展和副本,但不能自动完成主节点发现;Sentinel 适合单分片生产高可用;Cluster 适合单节点容量或吞吐无法满足时的分片。Cluster 不是“更高级的默认方案”,它会增加槽位、客户端拓扑、多 Key 限制、迁移和故障处理成本。
4. 兼容不等于无风险迁移
迁移必须分别验证协议、命令、Lua、模块、配置、数据文件、客户端识别、监控字段和高可用拓扑。Redis OSS 7.2 及更早版本可以采用物理文件、复制或 Key 级迁移;Redis CE 7.4 及以后不能直接复制数据文件,需要应用回填、双写、逻辑导出或专用迁移链路。
INFO server
INFO keyspace
CONFIG GET dir dbfilename appendonly appenddirname
COMMAND INFO GET SET EVAL迁移成功的判断不是 Valkey 启动,而是 Key 数量、样本值、TTL、类型、脚本、业务自检、故障切换和回滚路径全部通过。
三、怎么做
示例使用 valkey/valkey:9.1.1,固定补丁版本可以避免 latest 漂移。Valkey 采用 BSD-3-Clause 许可;镜像及其基础层还可能包含其他许可,交付时应按组织的软件准入流程核对。
Valkey 的开发入口可以分四类:
| 方式 | 适合场景 | 注意点 |
|---|---|---|
| 本机包管理器 | 需要本机 valkey-cli、学习命令、调试单进程 | 不同发行版版本不同,要看 valkey-server --version |
| Docker 单容器 | 临时验证命令、客户端、迁移脚本 | 不适合作为团队长期模板 |
| Docker Compose | 项目级联调、新人一键启动、CI smoke | 推荐作为团队默认开发入口 |
| 共享开发实例 | 多服务联调、低配置开发机、稳定测试数据 | 必须有 owner、ACL、prefix、清理窗口和误连生产防护 |
本机安装
macOS:
brew install valkey
brew services start valkey
brew services info valkeyDebian / Ubuntu 系:
sudo apt update
sudo apt install valkey如果只是为了 CLI,不一定要启动本机服务。先验证:
valkey-cli --version
valkey-cli -h 127.0.0.1 -p 6379 PINGWindows 开发机建议走 WSL 或 Docker,不把 Windows 原生安装写成团队默认。
Docker 单容器
个人快速验证:
docker run --rm --name valkey-smoke -p 127.0.0.1:6379:6379 valkey/valkey:9.1.1另开终端验证:
docker exec -it valkey-smoke valkey-cli PING
docker exec -it valkey-smoke valkey-cli INFO server这只能说明进程可用。它没有 ACL、固定配置、volume、key prefix、清理脚本和团队验收,不要把它复制成项目模板。
主流部署方式
| 方式 | 架构组成 | 适用场景 | 核心风险 |
|---|---|---|---|
| 单机单实例 | 一个 Valkey 进程,一个数据目录 | 本地开发、CI、低价值缓存 | 单点故障、内存上限、持久化未演练 |
| 单机多实例 | 一台机器多个 Valkey 进程和端口 | 测试环境、多项目轻量隔离 | 共享硬件故障域,端口和目录容易混乱 |
| 主从复制 | 一个 primary 加多个 replica | 读扩展、热备、备份卸载 | 默认异步复制,副本可能读旧值 |
| Sentinel | 主从加多个 Sentinel | 非分片高可用、自动发现主库 | 不做分片,客户端必须支持 Sentinel |
| Cluster | 多个 primary 分 slot,配 replica | 水平扩容、数据量超过单机 | 多 key、脚本、事务、数据库数量、客户端路由和热点 slot 都会暴露设计问题 |
| 云托管 | 云厂商 Valkey / Redis 兼容服务 | 中小团队、少运维 | 命令禁用、版本、备份、TLS、费用和兼容性要逐项复核 |
做架构选择时可以沿责任拆分:单机提供低延迟,复制提供副本,Sentinel 提供非分片高可用,Cluster 提供分片扩容,云托管降低日常运维负担。进入生产后,还要为选定形态单独建立部署、监控、故障切换和容量扩容 runbook。
推荐项目目录:
your-project/
compose.yaml
.env.example
cache/
valkey/
valkey.conf
users.acl
verify/
verify.valkey
docs/
dependency-setup.md
scripts/
valkey-scan.sh
valkey-reset.sh.env.example
VALKEY_IMAGE=valkey/valkey:9.1.1
VALKEY_HOST_PORT=6379
VALKEY_KEY_PREFIX=dev:your-project:
VALKEY_DATABASE=0
VALKEY_MAXMEMORY=256mb
VALKEY_MAXMEMORY_POLICY=noeviction
VALKEY_APP_USER=app_dev
VALKEY_APP_PASSWORD=YOUR_STRONG_VALKEY_APP_PASSWORD
VALKEY_READONLY_USER=cache_readonly
VALKEY_READONLY_PASSWORD=YOUR_STRONG_VALKEY_READONLY_PASSWORD
VALKEY_OPS_USER=cache_ops
VALKEY_OPS_PASSWORD=YOUR_STRONG_VALKEY_OPS_PASSWORD真实 .env 不提交。VALKEY_KEY_PREFIX 至少包含环境和项目,例如 dev:your-project:,共享实例再加模块或团队标识。 Docker Compose 会自动读取 .env,宿主机 Shell 不会。执行后续宿主机命令前,Bash 先导出变量:
set -a
. ./.env
set +aPowerShell 对简单的 KEY=VALUE 文件可以这样加载到当前进程:
Get-Content .env | ForEach-Object {
if ($_ -match '^\s*([^#][^=]*)=(.*)$') {
[Environment]::SetEnvironmentVariable($matches[1].Trim(), $matches[2].Trim(), 'Process')
}
}加载后用 printenv VALKEY_APP_USER 或 $env:VALKEY_APP_USER 确认用户名存在,但不要把密码打印到日志。
compose.yaml
services:
valkey:
image: ${VALKEY_IMAGE:-valkey/valkey:9.1.1}
container_name: your-project-valkey
ports:
- "127.0.0.1:${VALKEY_HOST_PORT:-6379}:6379"
environment:
VALKEY_APP_USER: ${VALKEY_APP_USER:-app_dev}
VALKEY_APP_PASSWORD: ${VALKEY_APP_PASSWORD}
VALKEY_READONLY_USER: ${VALKEY_READONLY_USER:-cache_readonly}
VALKEY_READONLY_PASSWORD: ${VALKEY_READONLY_PASSWORD}
VALKEY_OPS_USER: ${VALKEY_OPS_USER:-cache_ops}
VALKEY_OPS_PASSWORD: ${VALKEY_OPS_PASSWORD}
VALKEY_KEY_PREFIX: ${VALKEY_KEY_PREFIX:-dev:your-project:}
VALKEY_MAXMEMORY: ${VALKEY_MAXMEMORY:-256mb}
VALKEY_MAXMEMORY_POLICY: ${VALKEY_MAXMEMORY_POLICY:-noeviction}
command:
- sh
- -lc
- |
set -eu
: "$${VALKEY_APP_PASSWORD:?VALKEY_APP_PASSWORD is required}"
: "$${VALKEY_READONLY_PASSWORD:?VALKEY_READONLY_PASSWORD is required}"
: "$${VALKEY_OPS_PASSWORD:?VALKEY_OPS_PASSWORD is required}"
: "$${VALKEY_MAXMEMORY:?VALKEY_MAXMEMORY is required}"
: "$${VALKEY_MAXMEMORY_POLICY:?VALKEY_MAXMEMORY_POLICY is required}"
cat > /tmp/users.acl <<EOF
user default off
user $${VALKEY_APP_USER} on >$${VALKEY_APP_PASSWORD} ~$${VALKEY_KEY_PREFIX}* +@read +@write +@connection +@scripting +ping -@dangerous +acl|whoami
user $${VALKEY_READONLY_USER} on >$${VALKEY_READONLY_PASSWORD} ~$${VALKEY_KEY_PREFIX}* +@read +@connection +ping -@dangerous +acl|whoami
user $${VALKEY_OPS_USER} on >$${VALKEY_OPS_PASSWORD} ~$${VALKEY_KEY_PREFIX}* +@read +@connection +@admin +@slow -@dangerous +bgsave +bgrewriteaof +lastsave +memory +latency +acl|whoami +acl|list +acl|getuser +acl|log +acl|dryrun +config|get +info +slowlog|get +client|list
EOF
exec valkey-server /usr/local/etc/valkey/valkey.conf \
--aclfile /tmp/users.acl \
--maxmemory "$${VALKEY_MAXMEMORY}" \
--maxmemory-policy "$${VALKEY_MAXMEMORY_POLICY}"
volumes:
- valkey-data:/data
- ./cache/valkey/valkey.conf:/usr/local/etc/valkey/valkey.conf:ro
- ./cache/valkey/verify:/verify:ro
healthcheck:
test: ["CMD-SHELL", "VALKEYCLI_AUTH=\"$${VALKEY_APP_PASSWORD}\" valkey-cli --user \"$${VALKEY_APP_USER}\" PING | grep PONG"]
interval: 10s
timeout: 5s
retries: 12
restart: unless-stopped
volumes:
valkey-data:
name: your-project-valkey-data要点:
宿主机只绑定 127.0.0.1。default 用户关闭。app、readonly、ops 三类账号分开。
日常账号禁用 @dangerous。.env 中的内存上限和淘汰策略通过启动参数覆盖配置文件,改值后重建容器才会生效。使用固定镜像 tag。
配置文件和验证脚本进仓库,真实密码不进仓库。
cache/valkey/valkey.conf
bind 0.0.0.0
protected-mode no
port 6379
dir /data
appendonly yes
appendfilename "appendonly.aof"
save 60 1
databases 16
maxmemory 256mb
maxmemory-policy noeviction
loglevel notice为什么容器内 protected-mode no?
Compose 网络里的应用容器需要访问 Valkey。宿主机端口只绑定到 127.0.0.1。ACL 已经关闭默认用户并要求认证。
共享开发实例不要直接复制这份本地配置。共享环境需要防火墙、内网范围、TLS、审计、清理窗口和 owner。
启动:
docker compose up -d valkey
docker compose ps valkey
docker compose logs --tail=80 valkey验证版本和身份:
docker compose exec valkey sh -lc '
export VALKEYCLI_AUTH="$VALKEY_APP_PASSWORD"
valkey-cli --user "$VALKEY_APP_USER" PING
valkey-cli --user "$VALKEY_APP_USER" ACL WHOAMI
VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD" valkey-cli --user "$VALKEY_OPS_USER" INFO server | sed -n "1,20p"
'最小写读清理:
docker compose exec valkey sh -lc '
export VALKEYCLI_AUTH="$VALKEY_APP_PASSWORD"
valkey-cli --user "$VALKEY_APP_USER" SET "${VALKEY_KEY_PREFIX}smoke" ok EX 60
valkey-cli --user "$VALKEY_APP_USER" GET "${VALKEY_KEY_PREFIX}smoke"
valkey-cli --user "$VALKEY_APP_USER" TTL "${VALKEY_KEY_PREFIX}smoke"
valkey-cli --user "$VALKEY_APP_USER" DEL "${VALKEY_KEY_PREFIX}smoke"
'反向验证危险命令被拒绝:
docker compose exec valkey sh -lc '
export VALKEYCLI_AUTH="$VALKEY_APP_PASSWORD"
valkey-cli --user "$VALKEY_APP_USER" FLUSHALL
'预期是 NOPERM。如果能执行,说明 ACL 失效,不能进入共享环境。
验证持久化入口:
docker compose exec valkey sh -lc '
export VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD"
valkey-cli --user "$VALKEY_OPS_USER" CONFIG GET dir
valkey-cli --user "$VALKEY_OPS_USER" CONFIG GET appendonly
valkey-cli --user "$VALKEY_OPS_USER" INFO persistence | sed -n "1,40p"
ls -lah /data
'验证 .env 中的容量配置确实进入了运行时,而不是只停留在模板里:
docker compose exec valkey sh -lc '
export VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD"
valkey-cli --user "$VALKEY_OPS_USER" CONFIG GET maxmemory maxmemory-policy
'maxmemory 返回字节数,256mb 对应 268435456;策略应返回 noeviction。修改 .env 后要执行 docker compose up -d --force-recreate valkey 再检查。若输出仍是旧值,应检查 Compose 展开结果和容器启动命令,而不是根据 .env 猜测进程配置。
验证 Redis 兼容识别:
docker compose exec valkey sh -lc '
export VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD"
valkey-cli --user "$VALKEY_OPS_USER" INFO server | grep -E "server_name|valkey_version|redis_version"
'Valkey 为兼容可能保留 redis_version 口径,实际识别应看 server_name 和 valkey_version。
从命令响应追到内存与磁盘
SET key value EX 60 成功后,value 进入内存 keyspace,过期时间作为独立元数据参与到期判断。读取碰到已过期 key 时会把它当成不存在;后台的主动过期周期也会持续抽样清理,因此“TTL 到零”和“内存字节立刻下降”不是同一个时刻。达到 maxmemory 后,maxmemory-policy 决定分支:当前配置为 noeviction,新写入会报错,而不是悄悄删除旧 key。
持久化走另一条链。RDB 保存某一时点的内存快照,AOF 记录能够重建状态的写操作;appendonly yes 并不等于每次响应前都已落到稳定介质,实际丢失窗口由 fsync 策略、操作系统和存储共同决定。复制同样默认是异步链路,primary 已返回成功不代表 replica 已经应用该写入。
用一个专用 key 验证当前 Compose 的重启恢复,不要只检查 /data 下“有文件”:
docker compose exec valkey sh -lc '
export VALKEYCLI_AUTH="$VALKEY_APP_PASSWORD"
valkey-cli --user "$VALKEY_APP_USER" SET "${VALKEY_KEY_PREFIX}restart-proof" before-restart
'
docker compose exec valkey sh -lc '
export VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD"
valkey-cli --user "$VALKEY_OPS_USER" INFO persistence | grep -E "aof_enabled|aof_pending_bio_fsync|aof_last_write_status"
'
docker compose restart valkey
docker compose exec valkey sh -lc '
export VALKEYCLI_AUTH="$VALKEY_APP_PASSWORD"
valkey-cli --user "$VALKEY_APP_USER" GET "${VALKEY_KEY_PREFIX}restart-proof"
valkey-cli --user "$VALKEY_APP_USER" DEL "${VALKEY_KEY_PREFIX}restart-proof"
'健康路径中,持久化状态显示 AOF 已启用且最近写入状态正常,重启后返回 before-restart。若文件存在但 key 消失,应先查挂载目录、启动参数、AOF 加载日志和最近写入状态;这类失败证明“开启 AOF”与“完成可恢复性验收”之间还有加载和校验两步。
Java / Spring Boot
大多数 Java 项目仍会通过 Redis 协议客户端连接 Valkey。示例:
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.username=app_dev
spring.data.redis.password=YOUR_STRONG_VALKEY_APP_PASSWORD
spring.data.redis.database=0
spring.data.redis.timeout=2s
spring.data.redis.client-type=lettuce
spring.data.redis.lettuce.pool.max-active=16
spring.data.redis.lettuce.pool.max-idle=8团队必须另外写清:
当前连接的是 Valkey,不是 Redis。客户端兼容基线按 Redis OSS 7.2 命令集验证。database 不是租户隔离方案。Valkey 9.0 起 Cluster 可通过 cluster-databases 启用多个 DB,但默认值仍是 1;使用非 0 DB 前要同时验证服务端配置、ACL 数据库权限和客户端重连后的 SELECT。
RedisTemplate 序列化格式、key prefix、TTL 默认值和错误回退策略。连接池、超时、重试和熔断,不让缓存慢故障拖垮主链路。
Spring Boot 可执行接入
Spring Boot 可以继续使用 Spring Data Redis 的 Lettuce 驱动连接 Valkey。依赖只引入 Starter,不要同时混用多个连接池实现。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>spring:
data:
redis:
host: 127.0.0.1
port: 6379
username: ${VALKEY_APP_USER}
password: ${VALKEY_APP_PASSWORD}
connect-timeout: 2s
timeout: 1s
lettuce:
pool:
max-active: 32
max-idle: 16
min-idle: 4
max-wait: 500ms连接池大小不是越大越好。实例吞吐没有增加时,扩大连接池只会增加文件描述符、客户端缓冲区和排队竞争。超时应短于业务总超时,重试只应用于幂等读或具有幂等键的写,不能对 INCR、队列消费和库存扣减盲目自动重试。
@Service
public class ProfileCache {
private final StringRedisTemplate redis;
public ProfileCache(StringRedisTemplate redis) {
this.redis = redis;
}
public void put(String userId, String json) {
redis.opsForValue().set(
"dev:your-project:prod:user:" + userId + ":profile:v1",
json,
Duration.ofMinutes(10)
);
}
public String get(String userId) {
return redis.opsForValue().get("dev:your-project:prod:user:" + userId + ":profile:v1");
}
}启动后不要只看应用无异常,应执行一次写入、读取和 TTL 验证,并在 Valkey 端确认连接身份:
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CLIENT LIST TYPE normal
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning ACL LOG 10
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning GET dev:your-project:prod:user:1001:profile:v1
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning TTL dev:your-project:prod:user:1001:profile:v1正常结果应能看到应用连接名和用户,ACL LOG 不应持续出现认证或权限拒绝,缓存 Key 应有正数 TTL。
Node / Python / Go
Node.js 使用兼容 RESP 的客户端时,应显式配置连接超时、命令超时、重连上限和连接名称。以下以 ioredis 为例:
npm install ioredisimport Redis from "ioredis";
const client = new Redis({
host: process.env.VALKEY_HOST ?? "127.0.0.1",
port: Number(process.env.VALKEY_PORT ?? 6379),
username: process.env.VALKEY_APP_USER,
password: process.env.VALKEY_APP_PASSWORD,
connectTimeout: 2000,
commandTimeout: 1000,
connectionName: "order-api-prod",
maxRetriesPerRequest: 1,
});
await client.set("dev:your-project:demo:node:ping", "ok", "EX", 60);
console.log(await client.get("dev:your-project:demo:node:ping"));
console.log(await client.ttl("dev:your-project:demo:node:ping"));
await client.quit();预期输出是 ok 和 1 到 60 之间的 TTL。进程退出时调用 quit,不要依赖强制断开;使用 Cluster 时应创建 Cluster 客户端并提供多个种子节点,而不是使用单机客户端手工处理 MOVED。
Python 可以使用 redis-py 的 RESP 兼容能力:
python -m pip install redisimport os
import redis
r = redis.Redis(
host=os.getenv("VALKEY_HOST", "127.0.0.1"),
port=int(os.getenv("VALKEY_PORT", "6379")),
username=os.getenv("VALKEY_APP_USER"),
password=os.getenv("VALKEY_APP_PASSWORD"),
socket_connect_timeout=2,
socket_timeout=1,
health_check_interval=30,
decode_responses=True,
)
r.set("dev:your-project:demo:python:ping", "ok", ex=60)
print(r.get("dev:your-project:demo:python:ping"))
print(r.ttl("dev:your-project:demo:python:ping"))
r.close()Go 可以使用 Valkey 官方客户端 valkey-go:
go get github.com/valkey-io/valkey-gopackage main
import (
"context"
"fmt"
"os"
"time"
valkey "github.com/valkey-io/valkey-go"
)
func main() {
client, err := valkey.NewClient(valkey.ClientOption{
InitAddress: []string{"127.0.0.1:6379"},
Username: os.Getenv("VALKEY_APP_USER"),
Password: os.Getenv("VALKEY_APP_PASSWORD"),
})
if err != nil {
panic(err)
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := client.Do(ctx, client.B().Set().Key("dev:your-project:demo:go:ping").Value("ok").ExSeconds(60).Build()).Error(); err != nil {
panic(err)
}
value, err := client.Do(ctx, client.B().Get().Key("dev:your-project:demo:go:ping").Build()).ToString()
if err != nil {
panic(err)
}
fmt.Println(value)
}所有语言都应统一验证超时、断线重连、DNS 变更、凭据轮换、主从切换和 Cluster 拓扑变化。客户端“能够连接”只是第一层,故障期间不会无限重试、不会放大请求、不会把非幂等写执行两次才是生产验收。
不要凭名称指定唯一客户端。接入评审看这些问题:
| 维度 | 检查点 |
|---|---|
| 协议 | 是否支持 RESP2 / RESP3,是否按 Redis OSS 7.2 兼容基线测试 |
| Cluster | 是否处理 MOVED / ASK 和拓扑刷新 |
| Sentinel | 是否支持 Sentinel master name,而不是写死主库地址 |
| TLS | 是否支持证书、SNI、校验和连接池 |
| ACL | 是否支持 username / password |
| 超时 | 连接、读写、命令和重试策略是否可控 |
| 序列化 | 跨语言 value 是否可读,版本升级是否兼容 |
数据结构、TTL、事务和 Lua 实战
先建立一个隔离的实验数据库。以下命令都在测试实例执行,生产环境不要使用 FLUSHDB;以下实验使用专用临时实例和 dev:your-project:demo: 前缀隔离数据。
docker compose exec valkey sh -lc 'VALKEYCLI_AUTH="$VALKEY_APP_PASSWORD" valkey-cli --user "$VALKEY_APP_USER"'SELECT 0String 适合缓存序列化对象、计数器和状态值。写缓存时同时设置 TTL,避免写入成功后设置过期时间失败而形成永久 Key。
SET dev:your-project:demo:user:1001 '{"name":"Ada","level":7}' EX 300 NX
GET dev:your-project:demo:user:1001
TTL dev:your-project:demo:user:1001
INCR dev:your-project:demo:article:42:view
MGET dev:your-project:demo:user:1001 dev:your-project:demo:article:42:view预期第一次 SET ... NX 返回 OK,再次执行返回空;TTL 返回 1 到 300 之间的剩余秒数。业务读取 JSON 时必须处理 Key 不存在、反序列化失败和版本字段不兼容,不能把缓存命中等同于数据正确。
Hash 适合字段级更新,但整个 Hash 只有一个 TTL。字段数量持续增长的对象不能无限塞进同一个 Key。
HSET dev:your-project:demo:product:1001 name keyboard price 399 stock 82
HGETALL dev:your-project:demo:product:1001
HINCRBY dev:your-project:demo:product:1001 stock -1
EXPIRE dev:your-project:demo:product:1001 600
MEMORY USAGE dev:your-project:demo:product:1001HSET 首次返回 3,HINCRBY 返回 81,EXPIRE 返回 1,MEMORY USAGE 返回正整数。HGETALL 返回字段和值,字段顺序不应作为业务契约。
List 用于简单队列时需要明确消息丢失语义;可靠消费优先使用 Stream 消费组。
LPUSH dev:your-project:demo:jobs job-1001 job-1002
BRPOP dev:your-project:demo:jobs 1
XGROUP CREATE dev:your-project:demo:orders order-workers 0 MKSTREAM
XADD dev:your-project:demo:orders '*' orderId 1001 amount 199
XREADGROUP GROUP order-workers worker-1 COUNT 10 BLOCK 1000 STREAMS dev:your-project:demo:orders '>'
XPENDING dev:your-project:demo:orders order-workers
XACK dev:your-project:demo:orders order-workers <message-id>XADD 返回形如 1710000000000-0 的消息 ID,XREADGROUP 返回 Stream、消息 ID 和字段值,首次 XPENDING 显示一条待确认消息,使用实际消息 ID 执行 XACK 返回 1,再次查询 Pending 数量应下降。
BRPOP 取出后消息立即离开 List,消费者宕机时消息可能丢失。Stream 的消息在 XACK 前留在待处理列表,可通过 XPENDING 和 XAUTOCLAIM 识别并接管超时消息。 Set、Sorted Set、Bitmap、HyperLogLog 和地理位置索引分别用于去重关系、排行榜、布尔状态、近似基数统计和附近搜索。
SADD dev:your-project:demo:article:42:readers u1 u2 u3
SISMEMBER dev:your-project:demo:article:42:readers u2
ZADD dev:your-project:demo:rank 98 u1 76 u2 88 u3
ZREVRANGE dev:your-project:demo:rank 0 2 WITHSCORES
SETBIT dev:your-project:demo:signin:day-1 1001 1
BITCOUNT dev:your-project:demo:signin:day-1
PFADD dev:your-project:demo:uv:day-1 u1 u2 u3 u1
PFCOUNT dev:your-project:demo:uv:day-1
GEOADD dev:your-project:demo:shops 116.397 39.908 shop-a
GEOSEARCH dev:your-project:demo:shops FROMLONLAT 116.397 39.908 BYRADIUS 3 km WITHDISTSISMEMBER 返回 1;ZREVRANGE 按分数从高到低返回 u1、u3、u2 及对应分数;BITCOUNT 返回 1;PFCOUNT 返回近似值 3;GEOSEARCH 返回 shop-a 和距离。HyperLogLog 是近似统计,不能用于要求精确值的计费业务。
事务的 MULTI/EXEC 保证队列中的命令连续执行,但不提供关系数据库式回滚。需要乐观并发控制时使用 WATCH;竞争频繁、需要一次网络往返完成多步读写时使用 Lua。
SET dev:your-project:demo:stock:1001 10
WATCH dev:your-project:demo:stock:1001
GET dev:your-project:demo:stock:1001
MULTI
DECR dev:your-project:demo:stock:1001
EXEC要验证冲突,在客户端 A 执行 WATCH 和 GET 后暂停,在客户端 B 执行 DECR dev:your-project:demo:stock:1001,再回到客户端 A 执行 MULTI、DECR、EXEC。此时 EXEC 返回空结果,表示监视期间 Key 已变化,应用应重新读取并按有限次数重试或直接返回冲突,不能无限循环。
下面的 Lua 脚本只在库存大于零时扣减,返回 1 表示成功,0 表示库存不足或 Key 不存在。
cat > deduct.lua <<'LUA'
local stock = tonumber(redis.call('GET', KEYS[1]) or '-1')
if stock <= 0 then
return 0
end
redis.call('DECR', KEYS[1])
return 1
LUA
SCRIPT_SHA=$(docker compose exec -T valkey \
valkey-cli --user "$VALKEY_APP_USER" -a "$VALKEY_APP_PASSWORD" --no-auth-warning \
-x SCRIPT LOAD < deduct.lua)
docker compose exec -T valkey \
valkey-cli --user "$VALKEY_APP_USER" -a "$VALKEY_APP_PASSWORD" --no-auth-warning \
EVALSHA "$SCRIPT_SHA" 1 dev:your-project:demo:stock:1001Lua 能保证脚本内命令原子执行,但执行期间会占用命令执行线程。脚本中禁止大范围扫描、无界循环和外部 I/O;脚本耗时必须纳入 SLOWLOG 和延迟监控。
Key、TTL、淘汰策略和缓存工程
Key 应表达环境、业务、对象、标识和数据版本,例如 dev:your-project:prod:order:1001:detail:v2。Key 不能包含密码、身份证号等敏感数据,也不要使用 user:all、order:all 这类无界集合。Cluster 中需要多 Key 原子操作时使用 Hash Tag,例如 order:{1001}:detail 和 order:{1001}:items。
写入缓存时应原子设置 TTL,并用随机抖动分散集中失效:
SET dev:your-project:prod:product:1001:detail:v2 '{"id":1001,"price":399}' EX 1837
TTL dev:your-project:prod:product:1001:detail:v2
OBJECT ENCODING dev:your-project:prod:product:1001:detail:v2
MEMORY USAGE dev:your-project:prod:product:1001:detail:v2商品缓存若基础 TTL 为 1800 秒,可在应用中增加 0 到 300 秒随机值。不要先 SET 再 EXPIRE,两条命令之间的进程故障会留下永久 Key。
过期删除和内存淘汰是两套机制。TTL 到期由惰性删除和主动过期处理;达到 maxmemory 后才由 maxmemory-policy 决定淘汰。缓存实例常用近似 LRU 或 LFU,不能丢失的业务状态实例应使用 noeviction 并在达到水位前扩容。
maxmemory 8gb
maxmemory-policy allkeys-lfu
maxmemory-samples 10valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CONFIG GET maxmemory maxmemory-policy maxmemory-samples
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO stats | rg 'evicted_keys|expired_keys|keyspace_hits|keyspace_misses'缓存穿透是请求不存在的数据并持续回源。解决顺序是参数校验、鉴权、空值短 TTL、布隆过滤器和回源限流;不能只把空值永久缓存。缓存击穿是单个热 Key 到期后大量请求同时回源,可采用互斥重建、单飞请求或逻辑过期。缓存雪崩是大量 Key 同时失效或整个实例不可用,应使用 TTL 抖动、多级缓存、限流降级和高可用,而不是只把 TTL 调大。
Cache Aside 的标准写路径是先提交数据库事务,再删除缓存:
BEGIN
UPDATE product SET price = 399, version = version + 1 WHERE id = 1001;
COMMIT
DEL dev:your-project:prod:product:1001:detail:v2删除失败必须进入可靠重试,可通过本地消息表、事务消息或 CDC 订阅数据库变更补偿。延迟双删只能降低特定并发窗口,不提供严格一致性。强一致数据不能把 Valkey 当唯一真相源;需要使用数据库事务、版本号、幂等键和业务补偿共同保证。
分布式锁至少包含唯一令牌、原子获取、有限租期和“只释放自己的锁”。获取命令:
SET dev:your-project:lock:order:{1001} 6f7b9d3c NX PX 10000释放必须使用 Lua 比较令牌,不能直接 DEL:
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
end
return 0valkey-cli --user "$VALKEY_APP_USER" -a "$VALKEY_APP_PASSWORD" --no-auth-warning \
EVAL "$(cat unlock.lua)" 1 dev:your-project:lock:order:{1001} 6f7b9d3c返回 1 表示当前持有者成功释放,0 表示锁已过期或令牌不匹配。锁只能降低并发冲突,不能替代数据库唯一约束、幂等和 fencing token;进程暂停超过租期后,旧持有者仍可能继续操作下游资源。
RDB、AOF、备份和恢复演练
RDB 适合周期快照和快速加载,AOF 适合降低最近写入的丢失窗口。生产配置通常同时开启两者,由 AOF 承担正常重启恢复,由 RDB 承担离线备份、快速恢复和跨环境复制。
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes检查持久化状态和最近一次后台任务:
docker compose exec -T valkey sh -lc 'VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD" valkey-cli --user "$VALKEY_OPS_USER" --no-auth-warning INFO persistence'
docker compose exec -T valkey sh -lc 'VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD" valkey-cli --user "$VALKEY_OPS_USER" --no-auth-warning LASTSAVE'
docker compose exec -T valkey sh -lc 'VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD" valkey-cli --user "$VALKEY_OPS_USER" --no-auth-warning BGSAVE'
docker compose exec -T valkey sh -lc 'VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD" valkey-cli --user "$VALKEY_OPS_USER" --no-auth-warning BGREWRITEAOF'验收时至少确认 rdb_last_bgsave_status:ok、aof_enabled:1、aof_last_bgrewrite_status:ok,并观察后台任务期间 used_memory_rss、磁盘写入延迟和业务 P99。appendfsync everysec 把常见数据丢失窗口控制在秒级,但不是零丢失承诺;主机断电、主从复制尚未完成或磁盘故障仍可能丢数据。
备份不能只复制正在写入的 AOF 文件。先触发并确认 RDB 成功,再复制快照和配置到独立存储:
docker compose exec -T valkey sh -lc 'VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD" valkey-cli --user "$VALKEY_OPS_USER" --no-auth-warning BGSAVE'
docker compose exec -T valkey sh -lc 'while [ "$(VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD" valkey-cli --user "$VALKEY_OPS_USER" --no-auth-warning INFO persistence | sed -n "s/^rdb_bgsave_in_progress://p" | tr -d "\r")" = "1" ]; do sleep 1; done'
docker cp your-project-valkey:/data/dump.rdb ./backup/dump.rdb
sha256sum ./backup/dump.rdb > ./backup/dump.rdb.sha256恢复演练必须在隔离实例进行,不能覆盖唯一生产副本:
mkdir -p restore-test
cp ./backup/dump.rdb restore-test/dump.rdb
sha256sum -c ./backup/dump.rdb.sha256
docker run -d --name valkey-restore-test \
-p 127.0.0.1:6380:6379 \
-v "$PWD/restore-test:/data" \
valkey/valkey:9.1.1
valkey-cli -h 127.0.0.1 -p 6380 PING
valkey-cli -h 127.0.0.1 -p 6380 DBSIZE
valkey-cli -h 127.0.0.1 -p 6380 --scan --pattern 'dev:your-project:demo:*' | head
docker rm -f valkey-restore-test恢复验收不只看进程启动,还要核对 Key 数量、抽样值、TTL、业务版本字段和关键数据结构成员数。
主从复制、Sentinel 和 Cluster 实战
主从复制用于读扩展和高可用基础,但异步复制意味着故障切换时可能丢失尚未传播的写入。下面使用独立实验网络和密码认证构造可执行环境,不与前面的安全 Compose 混用:
docker network inspect valkey-net >/dev/null 2>&1 || docker network create valkey-net
docker run -d --name valkey-ha-primary --network valkey-net --network-alias valkey \
-p 127.0.0.1:6379:6379 \
valkey/valkey:9.1.1 \
valkey-server --requirepass "$VALKEY_OPS_PASSWORD"
docker run -d --name valkey-replica --network valkey-net \
-p 127.0.0.1:6380:6379 \
valkey/valkey:9.1.1 \
valkey-server --replicaof valkey 6379 --masterauth "$VALKEY_OPS_PASSWORD" \
--requirepass "$VALKEY_OPS_PASSWORD"
docker exec valkey-ha-primary valkey-cli -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO replication
docker exec valkey-replica valkey-cli -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO replication主节点应看到 connected_slaves:1,副本应看到 role:slave、master_link_status:up。若 master_repl_offset 与 slave_repl_offset 差值持续扩大,说明副本处理、网络或磁盘能力跟不上写入。
Sentinel 至少部署三个独立投票节点,且不能与同一故障域中的数据节点一起“伪三节点”。配置文件中的密码必须由密钥管理系统在部署阶段渲染,普通配置文件不会自动替换 Shell 环境变量。
port 26379
sentinel monitor cache-primary valkey 6379 2
sentinel auth-pass cache-primary <rendered-password>
sentinel down-after-milliseconds cache-primary 5000
sentinel failover-timeout cache-primary 60000
sentinel parallel-syncs cache-primary 1若数据节点关闭 default 用户并启用命名 ACL,还要设置 sentinel auth-user cache-primary sentinel-user,并在所有数据节点创建具备 Sentinel 所需命令权限的专用用户。
for i in 1 2 3; do
host_port=$((26378 + i))
mkdir -p "sentinel-$i"
cat > "sentinel-$i/sentinel.conf" <<CONF
port 26379
sentinel monitor cache-primary valkey 6379 2
sentinel auth-pass cache-primary $VALKEY_OPS_PASSWORD
sentinel down-after-milliseconds cache-primary 5000
sentinel failover-timeout cache-primary 60000
sentinel parallel-syncs cache-primary 1
CONF
docker run -d --name "valkey-sentinel-$i" --network valkey-net \
-p "127.0.0.1:$host_port:26379" \
-v "$PWD/sentinel-$i:/etc/valkey" \
valkey/valkey:9.1.1 valkey-sentinel /etc/valkey/sentinel.conf
done
valkey-cli -h 127.0.0.1 -p 26379 SENTINEL CKQUORUM cache-primary
docker stop valkey-ha-primary
valkey-cli -h 127.0.0.1 -p 26379 SENTINEL GET-MASTER-ADDR-BY-NAME cache-primary
docker start valkey-ha-primaryCKQUORUM 应确认可达 Sentinel 满足法定人数。停止主节点后,GET-MASTER-ADDR-BY-NAME 最终应返回副本地址;旧主恢复后应被重新配置为副本,应用通过 Sentinel 发现新主并恢复写入。 Cluster 用 16384 个槽位分片。至少准备三个主节点,生产通常为每个主节点配置副本。六台节点先分别创建配置,cluster-announce-ip 替换为本机可被其他节点和客户端访问的地址:
bind 0.0.0.0
protected-mode yes
port 6379
requirepass <VALKEY_OPS_PASSWORD 对应的真实值>
masterauth <VALKEY_OPS_PASSWORD 对应的真实值>
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 10.0.0.11
cluster-announce-port 6379
cluster-announce-bus-port 16379每台机器使用独立数据目录启动,并在节点之间开放普通端口 6379 和集群总线 16379:
install -d -o valkey -g valkey /var/lib/valkey-cluster
valkey-server /etc/valkey/cluster.conf --dir /var/lib/valkey-cluster
ss -lntp | rg '6379|16379'六个节点互相可达后创建集群:
valkey-cli -a "$VALKEY_OPS_PASSWORD" --cluster create \
10.0.0.11:6379 10.0.0.12:6379 10.0.0.13:6379 \
10.0.0.14:6379 10.0.0.15:6379 10.0.0.16:6379 \
--cluster-replicas 1
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER INFO
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER NODES
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER KEYSLOT 'order:{1001}:detail'
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER KEYSLOT 'order:{1001}:items'CLUSTER INFO 应返回 cluster_state:ok 和 cluster_slots_assigned:16384。两条 KEYSLOT 必须相同,才能执行涉及两个 Key 的 Lua 或事务。客户端必须支持拓扑刷新和 MOVED、ASK 重定向。
Cluster 扩容、缩容和槽位迁移
扩容先启动满足相同配置和安全要求的新节点,再把它加入集群。以下示例把 10.0.0.17:6379 加入现有集群:
valkey-cli -a "$VALKEY_OPS_PASSWORD" --cluster add-node 10.0.0.17:6379 10.0.0.11:6379
valkey-cli -a "$VALKEY_OPS_PASSWORD" --cluster check 10.0.0.11:6379
valkey-cli -a "$VALKEY_OPS_PASSWORD" --cluster reshard 10.0.0.11:6379reshard 会询问迁移槽位数、目标节点 ID 和源节点。迁移期间持续观察集群状态、业务 P99、重定向比例、网络和 CPU。完成后应满足:
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER INFO
valkey-cli -a "$VALKEY_OPS_PASSWORD" --cluster check 10.0.0.11:6379cluster_state:ok、cluster_slots_assigned:16384、cluster_slots_fail:0,且 --cluster check 不再报告未覆盖槽位或槽位归属不一致。
缩容不能直接停止节点。先把待删除主节点的全部槽位迁走,再处理其副本关系,最后删除节点:
valkey-cli -a "$VALKEY_OPS_PASSWORD" --cluster reshard 10.0.0.11:6379
valkey-cli -a "$VALKEY_OPS_PASSWORD" --cluster del-node 10.0.0.11:6379 <node-id>
valkey-cli -a "$VALKEY_OPS_PASSWORD" --cluster check 10.0.0.11:6379缩容前必须确认剩余容量、故障域和副本覆盖仍满足要求。任何阶段出现持续业务错误、槽位未覆盖或复制长时间不收敛,都应停止后续动作并按原槽位归属回迁。
TLS、ACL 和凭据轮换
Valkey 不应直接暴露在公网。网络层先限制安全组、防火墙和 Kubernetes NetworkPolicy,再使用 ACL 做最小权限;跨主机或不可信网络还应启用 TLS。下面展示双向 TLS 的核心配置:
port 0
tls-port 6379
tls-cert-file /etc/valkey/tls/server.crt
tls-key-file /etc/valkey/tls/server.key
tls-ca-cert-file /etc/valkey/tls/ca.crt
tls-auth-clients yes
tls-replication yes
tls-cluster yesvalkey-cli --tls -h valkey.internal -p 6379 \
--cacert ./ca.crt --cert ./client.crt --key ./client.key \
--user app --pass "$VALKEY_APP_PASSWORD" PING正常返回 PONG。使用错误 CA 时应得到证书校验失败,而不是仍然成功连接;这能证明客户端没有关闭证书校验。
ACL 用户应按应用和环境隔离,并限定 Key 前缀和命令类别:
ACL SETUSER order-api on >change-me ~dev:your-project:prod:order:* +@read +@write -@admin -@dangerous
ACL GETUSER order-api
ACL DRYRUN order-api GET dev:your-project:prod:order:1001
ACL DRYRUN order-api CONFIG GET '*'第一条 DRYRUN 应返回 OK,第二条应返回权限错误。密码轮换不要直接覆盖唯一凭据:先创建新用户或为同一用户增加新密码,更新应用并确认新连接建立,再撤销旧密码,最后检查连接池中是否仍有旧身份连接和 ACL LOG 拒绝记录。
性能、容量和可观测性
压测前先明确目标并发、读写比例、Value 大小、连接复用、Pipeline 批量、持久化策略和网络路径。只拿默认 valkey-benchmark 的极小 Value 吞吐量代表业务容量,会严重高估生产性能。
docker run -d --rm --name valkey-benchmark-lab -p 127.0.0.1:6389:6379 valkey/valkey:9.1.1
valkey-benchmark -h 127.0.0.1 -p 6389 \
-t set,get -n 200000 -c 100 -P 16 -d 1024 --threads 4
valkey-cli --user "$VALKEY_OPS_USER" -h 127.0.0.1 -p 6379 -a "$VALKEY_OPS_PASSWORD" --latency-history
valkey-cli --intrinsic-latency 30
docker stop valkey-benchmark-lab第一条命令模拟 1 KiB Value、100 个并发连接和 16 深度 Pipeline;第二条观察端到端延迟变化;第三条测量宿主机自身调度抖动。业务压测还应使用真实 Key 分布和 Value 分位数,并在开启正式 RDB/AOF 配置时测试。
容量估算不能只看 Value 大小:
目标内存 = Key 与 Value 实际内存 + 数据结构开销 + 内存碎片 + 客户端缓冲区 + 复制缓冲区 + AOF 缓冲区 + fork 写时复制峰值 + 安全余量用命令测量实际对象和实例开销:
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO memory
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning MEMORY STATS
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning MEMORY USAGE dev:your-project:demo:user:1001 SAMPLES 5
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO clients
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO stats
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning SLOWLOG GET 20
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning LATENCY DOCTOR核心监控至少覆盖 used_memory、used_memory_rss、mem_fragmentation_ratio、maxmemory、evicted_keys、expired_keys、connected_clients、blocked_clients、rejected_connections、instantaneous_ops_per_sec、命中率、P95/P99 延迟、复制延迟、RDB/AOF 状态和集群槽位状态。命中率应根据 keyspace_hits / (keyspace_hits + keyspace_misses) 计算,但低命中率不一定是故障,还要结合业务访问模式和穿透保护判断。
版本升级、迁移与回滚
升级和产品迁移必须先区分协议兼容、命令兼容、配置兼容、模块兼容和数据文件兼容。Valkey 与 Redis OSS 7.2 及更早版本具有明确兼容基础,但不能据此推断 Redis CE 7.4 及以后生成的 RDB/AOF 可以直接加载,也不能推断 Redis 8 的模块和新增命令都存在等价实现。
滚动升级前先固定目标补丁版本,在隔离环境执行旧版本数据加载和客户端回归:
docker pull valkey/valkey:9.1.1
docker image inspect valkey/valkey:9.1.1 --format '{{json .RepoDigests}}'
valkey-cli -h old-node INFO server
valkey-cli -h old-node INFO persistence
valkey-cli -h old-node MODULE LIST
valkey-cli -h old-node COMMAND INFO <业务使用的命令>主从或 Cluster 滚动升级遵循“先副本、再切换、后旧主”的顺序。每升级一个节点,都要确认复制恢复、偏移量收敛、持久化正常、客户端错误率和延迟正常,再继续下一个节点。不要在同一批次同时改变版本、配置、拓扑和客户端。
停机物理迁移适合可接受维护窗口且数据文件明确兼容的场景。流程是冻结写入、触发持久化、记录校验、停止源实例、复制文件、启动目标实例、业务抽样和切流:
valkey-cli -h source -a "$SOURCE_PASSWORD" --no-auth-warning BGSAVE
valkey-cli -h source -a "$SOURCE_PASSWORD" --no-auth-warning INFO persistence
valkey-cli -h source -a "$SOURCE_PASSWORD" --no-auth-warning DBSIZE
valkey-cli -h source -a "$SOURCE_PASSWORD" --no-auth-warning INFO keyspace
sha256sum dump.rdb在线迁移需要选择可验证的复制、双写或 CDC 工具。先做全量复制,再追平增量;切换窗口冻结或排空源端写入,确认源目标偏移和业务抽样一致后切换连接。双写期间必须记录每次写入的结果和版本,不能把“两边都发请求”当成一致性保证。
迁移验证
迁移验收必须同时证明目标端数据可读、客户端行为兼容、切换期间写入可控、异常时能够回滚。只比较 DBSIZE 不足以证明一致,因为过期 Key、不同逻辑数据库和迁移期间新增写入都会影响数量。
迁移前在源端记录版本、数据规模、类型分布、TTL 和持久化状态:
valkey-cli -h source --user "$SOURCE_USER" -a "$SOURCE_PASSWORD" --no-auth-warning INFO server
valkey-cli -h source --user "$SOURCE_USER" -a "$SOURCE_PASSWORD" --no-auth-warning INFO keyspace
valkey-cli -h source --user "$SOURCE_USER" -a "$SOURCE_PASSWORD" --no-auth-warning DBSIZE
valkey-cli -h source --user "$SOURCE_USER" -a "$SOURCE_PASSWORD" --no-auth-warning INFO persistence
valkey-cli -h source --user "$SOURCE_USER" -a "$SOURCE_PASSWORD" --no-auth-warning MODULE LIST
valkey-cli -h source --user "$SOURCE_USER" -a "$SOURCE_PASSWORD" --no-auth-warning --scan --pattern 'dev:your-project:*' > source.keys按数据结构、大小和 TTL 分层抽样,记录 String 值摘要、Hash/Set/ZSet 成员数、Stream 长度与消费组、无 TTL Key 和临近过期 Key:
while IFS= read -r key; do
type=$(valkey-cli -h source --user "$SOURCE_USER" -a "$SOURCE_PASSWORD" --no-auth-warning --raw TYPE "$key")
ttl=$(valkey-cli -h source --user "$SOURCE_USER" -a "$SOURCE_PASSWORD" --no-auth-warning --raw PTTL "$key")
bytes=$(valkey-cli -h source --user "$SOURCE_USER" -a "$SOURCE_PASSWORD" --no-auth-warning --raw MEMORY USAGE "$key")
printf '%s\t%s\t%s\t%s\n' "$key" "$type" "$ttl" "$bytes"
done < source.keys > source.inventory.tsv目标端生成同样的 target.inventory.tsv。TTL 应比较允许误差,业务值应使用规范化序列化后的摘要或领域字段比较。
sort source.inventory.tsv > source.inventory.sorted.tsv
sort target.inventory.tsv > target.inventory.sorted.tsv
diff -u source.inventory.sorted.tsv target.inventory.sorted.tsv切换前冻结或排空写入并记录最后复制偏移。切换后同时观察成功率、超时、重试、连接数、命中率、源目标写入量和数据库回源,再执行幂等读写探针:
valkey-cli -h target --user "$TARGET_USER" -a "$TARGET_PASSWORD" --no-auth-warning SET dev:your-project:migration:smoke ok EX 60
valkey-cli -h target --user "$TARGET_USER" -a "$TARGET_PASSWORD" --no-auth-warning GET dev:your-project:migration:smoke
valkey-cli -h target --user "$TARGET_USER" -a "$TARGET_PASSWORD" --no-auth-warning TTL dev:your-project:migration:smoke
valkey-cli -h target --user "$TARGET_USER" -a "$TARGET_PASSWORD" --no-auth-warning DEL dev:your-project:migration:smoke预期依次返回 OK、ok、正数 TTL 和 1。回滚触发条件必须在切换前确定。回滚时停止目标端新写入、恢复源连接并处理切换窗口增量,不能只把 DNS 改回去。
四、问题处理
问题处理统一从现象出发,先保留现场,再定位根因,随后采取可回退的修复,最后用业务指标和 Valkey 状态共同验证。禁止在未知影响范围时先执行 FLUSHALL、KEYS *、无条件故障切换或直接删除持久化文件。
连接被拒绝或连接超时
现象:本机或应用连接返回 Connection refused、Connection timed out,健康检查失败,业务回源量和错误率上升。
根因可能是进程未运行、监听地址或端口错误、防火墙和安全组拦截、容器端口未映射、应用把自身容器的 127.0.0.1 当成 Valkey、DNS 指向旧地址,或者连接请求已经进入服务端但被连接数上限拒绝。
先从进程、监听、网络和 Valkey 四层定位:
systemctl status valkey --no-pager
ps -ef | rg '[v]alkey-server'
ss -lntp | rg ':6379'
journalctl -u valkey -n 100 --no-pager
nc -vz valkey.internal 6379
getent hosts valkey.internal
valkey-cli -h valkey.internal -p 6379 PING容器环境继续检查:
docker compose ps
docker compose logs --tail=200 valkey
docker inspect your-project-valkey --format '{{json .NetworkSettings.Networks}}'
docker exec <application-container> getent hosts valkey
docker exec <application-container> valkey-cli -h valkey -p 6379 PINGConnection refused 通常说明目标主机主动拒绝,优先检查监听和端口映射;超时通常说明数据包被丢弃、路由错误或服务端严重阻塞。修复时只开放应用网段到 Valkey 端口,容器间使用服务名,不要把 bind 0.0.0.0 当作通用解决方案。
验证标准是本机和应用所在网络都返回 PONG,未授权网络仍然连接失败,应用错误率、回源量和重试量恢复到基线。
连接数耗尽或连接池持续等待
现象:新连接被拒绝,应用线程等待连接池,P99 突升;INFO clients 中客户端数接近上限,或 rejected_connections 持续增加。
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO clients
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO stats | rg 'rejected_connections|total_connections_received'
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CONFIG GET maxclients
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CLIENT LIST
ss -ant state established '( sport = :6379 )' | wc -l
pid=$(pgrep -x valkey-server); cat "/proc/$pid/limits" | rg 'open files'根因包括应用每次请求新建连接、连接未归还、连接池总量超过实例承载、阻塞命令长期占用连接、操作系统文件描述符不足。不要第一时间只增大 maxclients,因为每个连接都有输入输出缓冲区并消耗文件描述符。
处理时先停止异常实例的连接风暴,限制重试并复用连接;再根据应用副本数计算总连接预算,设置池的最大连接、最大等待和空闲回收。若服务端确有余量,再同步调整 systemd LimitNOFILE、内核限制和 maxclients。
验证时确认 rejected_connections 不再增加、连接数在预算内稳定、连接池等待接近零,并在应用滚动重启后仍不发生连接尖峰。
OOM command not allowed 或淘汰量突增
现象:写命令返回 OOM command not allowed when used memory > 'maxmemory',或者写入仍成功但 evicted_keys 快速增长、命中率下降、数据库回源上升。
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO memory
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO stats | rg 'evicted_keys|expired_keys'
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CONFIG GET maxmemory maxmemory-policy
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning MEMORY STATS
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning --bigkeys
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning --memkeys重点判断 used_memory 是否接近 maxmemory、used_memory_rss 是否远高于数据内存、策略是否为 noeviction、是否存在无 TTL Key 和大对象。maxmemory 不限制进程全部 RSS,fork 写时复制、复制缓冲区和客户端输出缓冲区仍可能把宿主机推向 OOM。
临时止损可以限流非关键写入、缩短可丢缓存的 TTL、异步 UNLINK 已确认无用的大 Key,或扩容并迁移流量。禁止在生产直接执行 KEYS * 或批量同步 DEL。永久方案是修正 Key 生命周期、对象上限、淘汰策略、容量水位和扩容机制。
验证标准是写入恢复、evicted_keys 增速符合缓存预期、宿主机无交换风暴,且故障窗口后端数据库没有持续超载。
所有命令延迟升高、热 Key 或大 Key 阻塞
现象:多种命令同时变慢,CPU 单核接近满载,网络带宽升高,应用 P99 抖动;也可能只有访问某个 Key 的请求变慢。
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning SLOWLOG GET 50
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning SLOWLOG LEN
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning LATENCY LATEST
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning LATENCY DOCTOR
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning --latency-history
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning --bigkeys
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning --hotkeys
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO commandstats
pidstat -p "$(pgrep -x valkey-server)" 1
sar -n DEV 1--hotkeys 依赖 LFU 淘汰策略提供的频率信息,不能在所有实例上得到同等结果。根因常见于 KEYS、大范围 SMEMBERS/HGETALL/LRANGE、一次返回巨大 Value、Lua 长脚本、fork/COW、AOF fsync、热 Key 或宿主机调度抖动。
先停止或限流已确认的危险调用,使用 SCAN 代替 KEYS,把全量读取改成分页,把大集合按业务维度拆分,用 UNLINK 异步删除大 Key。热 Key 可采用本地缓存、只读副本、Key 分片和请求合并,但拆分计数器时必须重新设计聚合一致性。
验证不以 SLOWLOG RESET 为完成标准,而是确认同样负载下 P95/P99 回落、慢命令不再新增、CPU 和网络恢复,并执行原业务请求验证结果完整。
RDB 失败、fork 内存突增或延迟抖动
现象:BGSAVE 失败,日志出现 fork 或写文件错误,持久化状态为失败;后台保存期间 RSS 突增、延迟上升,严重时进程被系统 OOM Killer 终止。
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO persistence
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CONFIG GET dir dbfilename save
df -h
df -i
free -h
vmstat 1
dmesg -T | rg -i 'oom|killed process'
journalctl -u valkey -n 200 --no-pager根因包括磁盘或 inode 满、目录权限错误、内存不足导致 fork 失败、写流量使 COW 页面大量复制、vm.overcommit_memory 不合理。临时止损是释放独立数据盘空间、降低非关键写入和停止并发重任务;不能通过删除唯一快照或关闭全部持久化掩盖问题。
永久方案要为数据内存、碎片和 COW 峰值预留空间,持久化目录使用可监控磁盘,把备份转移到独立存储,并按真实写入压力演练。修复后执行 BGSAVE,确认 rdb_last_bgsave_status:ok、时间戳更新、业务 P99 正常,并在隔离实例恢复新快照。
AOF 重写失败、AOF 损坏或实例无法启动
现象:aof_last_bgrewrite_status:err、AOF 目录快速增长,或者重启时日志报告 AOF 解析错误并退出。
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO persistence
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CONFIG GET dir appenddirname appendfilename appendfsync
du -sh /var/lib/valkey/*
journalctl -u valkey -n 200 --no-pager先复制完整 AOF 目录和 RDB 作为现场证据,不要直接在唯一文件上修复。磁盘满时先扩容或释放与 Valkey 无关的空间;重写失败时检查权限、磁盘 I/O 和 fork 内存。确认为尾部不完整且业务接受丢弃损坏尾部时,在副本上使用随发行版提供的 AOF 检查工具修复,再在隔离实例启动验证。
cp -a /var/lib/valkey/appendonlydir ./incident-aof-copy
valkey-check-aof --fix ./incident-aof-copy/<manifest-or-aof-file>工具名称和 multipart AOF 文件入口应以当前发行包帮助信息为准,执行 valkey-check-aof --help 后再操作。验证必须包含启动成功、DBSIZE、关键 Key 的类型和值、TTL 抽样、业务读写和与源端或备份清单的差异比较。
复制延迟、链路中断或反复全量同步
现象:副本 master_link_status:down,复制偏移差持续扩大,日志反复出现 full resync,读副本数据明显落后。
valkey-cli --user "$VALKEY_OPS_USER" -h primary -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO replication
valkey-cli --user "$VALKEY_OPS_USER" -h replica -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO replication
valkey-cli --user "$VALKEY_OPS_USER" -h primary -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CONFIG GET repl-backlog-size repl-timeout
valkey-cli --user "$VALKEY_OPS_USER" -h primary -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CLIENT LIST TYPE replica
ss -antp | rg ':6379'
sar -n DEV 1根因可能是网络抖动、认证错误、主机名解析变化、副本 CPU 或磁盘落后、复制 backlog 太小导致断线后无法部分同步、客户端输出缓冲区达到限制。临时止损时停止把强一致读取发送到落后副本,并限制大批量写入。
永久处理包括修复网络和认证、为实际写入速率与最长断线窗口配置 backlog、保证副本资源不低于主节点、监控偏移差和全量同步次数。验证时要求 master_link_status:up、偏移差收敛,并抽样比较主副本 Key 值和 TTL;异步复制仍不能提供零数据丢失承诺。
Sentinel 不能切换、误切换或反复切换
现象:主节点失联后没有选出新主,或网络抖动导致频繁切换;客户端仍连接旧主并持续写失败。
valkey-cli -h sentinel-1 -p 26379 SENTINEL MASTER cache-primary
valkey-cli -h sentinel-1 -p 26379 SENTINEL SENTINELS cache-primary
valkey-cli -h sentinel-1 -p 26379 SENTINEL REPLICAS cache-primary
valkey-cli -h sentinel-1 -p 26379 SENTINEL CKQUORUM cache-primary
valkey-cli -h sentinel-1 -p 26379 INFO sentinel
journalctl -u valkey-sentinel -n 200 --no-pager不能切换常见于 quorum 不足、Sentinel 不在独立故障域、无法访问副本、认证配置错误或没有合格副本;误切换常见于 down-after-milliseconds 过短和网络质量差。先恢复投票和数据节点通信,不要在多个 Sentinel 上同时强制切换。
修复后执行受控故障演练,确认新主产生、旧主恢复后成为副本、应用通过 Sentinel 发现新主、写入恢复且幂等机制没有产生重复业务。切换完成不代表数据完整,还要按 RPO 检查故障前后的最后写入。
Cluster 状态失败、槽位未覆盖或节点失联
现象:客户端大量收到 CLUSTERDOWN、MOVED、ASK,cluster_state:fail,某些 Key 可访问而另一些不可访问。
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER INFO
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER NODES
valkey-cli -a "$VALKEY_OPS_PASSWORD" --cluster check 10.0.0.11:6379
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER SLOTS
ss -lntp | rg '6379|16379'根因包括集群总线端口不通、节点地址通告错误、槽位未分配或重复、主副本同时位于同一故障域、扩缩容中断、客户端不支持拓扑刷新。不要通过关闭 cluster-require-full-coverage 掩盖槽位问题,这可能把明确失败变成部分数据静默不可用。
先恢复节点间普通端口和集群总线通信,核对 cluster-announce-*,再使用 valkey-cli -a "$VALKEY_OPS_PASSWORD" --cluster fix 前备份并人工确认槽位真实归属。验证要求 16384 槽全部覆盖、cluster_state:ok、无 fail 节点,客户端重定向和错误率回落,并对不同槽位的 Key 做读写抽样。
Stream 消费积压和 Pending 长期不下降
现象:Stream 长度持续增长,消费者组 Pending 数量增加,消息处理延迟扩大,消费者重启后仍有消息无人处理。
XLEN dev:your-project:demo:orders
XINFO GROUPS dev:your-project:demo:orders
XINFO CONSUMERS dev:your-project:demo:orders order-workers
XPENDING dev:your-project:demo:orders order-workers
XPENDING dev:your-project:demo:orders order-workers - + 20
XAUTOCLAIM dev:your-project:demo:orders order-workers recovery-worker 60000 0-0 COUNT 20根因包括消费者处理能力不足、处理后未 XACK、消费者崩溃、毒消息反复失败、BLOCK 和批量参数不合理。先扩容健康消费者并隔离毒消息,再用 XAUTOCLAIM 接管超过业务超时的 Pending;必须依靠业务幂等避免接管后重复执行产生副作用。
验证时观察 Pending 总数和最老消息空闲时间持续下降、生产消费速率恢复平衡、重复业务指标正常。删除历史消息前先明确保留和审计要求,不能用 XTRIM 掩盖未消费数据。
Lua 脚本阻塞或返回 BUSY
现象:所有请求延迟上升,客户端收到 BUSY,慢日志或日志显示脚本长时间运行。
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning SLOWLOG GET 20
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO commandstats
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning SCRIPT EXISTS <sha1>
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning SCRIPT KILL只有尚未执行写命令的脚本才能安全 SCRIPT KILL;已经写入的脚本不能简单中断,否则原子性语义会被破坏。根因通常是无界循环、大范围遍历、传入巨大集合或把长业务流程放入脚本。
先停止继续调用问题脚本并降级对应功能。永久处理是限制 Key 和参数规模,把长流程拆成短原子步骤,预加载并版本化脚本,监控脚本耗时。验证时用边界数据和最大允许输入回归,确认 P99、SLOWLOG 和业务结果正常。
缓存穿透、击穿、雪崩或数据库被回源压垮
现象:keyspace_misses 激增,数据库 QPS、连接池等待和 CPU 同时上升;热点 Key 过期时出现周期性尖峰,或大量 Key 在同一时间失效。
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO stats | rg 'keyspace_hits|keyspace_misses|evicted_keys'
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning --hotkeys
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning --scan --pattern 'dev:your-project:prod:product:*' | head先根据 Key、接口和回源 SQL 区分非法不存在数据、单个热 Key、批量同 TTL 和实例故障。止损顺序是入口限流、回源并发上限、热点请求合并、短期本地缓存和核心业务降级;不要在数据库已经过载时无限重试或全量预热。
永久方案分别使用参数拦截与空值短缓存、逻辑过期或单飞重建、TTL 抖动和多级缓存,并对缓存失效场景做数据库容量演练。验证必须同时看 Valkey 命中率、应用错误率、回源 QPS、数据库连接池和延迟,不能只看缓存恢复命中。
NOAUTH、WRONGPASS 或 NOPERM
NOAUTH 表示未认证,WRONGPASS 表示用户名或密码错误,NOPERM 表示身份有效但命令或 Key 不在授权范围。
export VALKEYCLI_AUTH="$VALKEY_OPS_PASSWORD"
valkey-cli --user "$VALKEY_OPS_USER" ACL WHOAMI
valkey-cli --user "$VALKEY_OPS_USER" ACL GETUSER "$VALKEY_APP_USER"
valkey-cli --user "$VALKEY_OPS_USER" ACL LOG 20
valkey-cli --user "$VALKEY_OPS_USER" ACL DRYRUN "$VALKEY_APP_USER" GET dev:your-project:prod:user:1001
valkey-cli --user "$VALKEY_OPS_USER" ACL DRYRUN "$VALKEY_APP_USER" CONFIG GET '*'正常时 WHOAMI 返回 cache_ops,第一条 DRYRUN 返回 OK,第二条返回权限错误。ACL LOG 中的 auth、key、command 分别指向认证、Key 前缀和命令权限问题。
修复 ACL 后先让一个应用实例建立新连接并通过读写探针,再滚动更新连接池。密码轮换应先增加新凭据、切换应用、确认旧连接消失,再撤销旧凭据。最后反向验证允许命令成功、危险命令仍被拒绝,且 ACL LOG 不再新增非预期记录。
protected mode 和端口暴露
本机可连接而远程失败,或关闭 protected mode 后端口意外暴露,都要从监听、容器网络和网络策略共同定位:
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CONFIG GET bind protected-mode port tls-port
ss -lntp | rg ':6379'
docker compose ps
nc -vz <valkey-host> 6379bind 127.0.0.1 只允许本机访问,容器中的 127.0.0.1 只表示当前容器。远程连接应同时设计监听地址、容器网络、安全组、防火墙、ACL 和 TLS,不能只改成 0.0.0.0。修复后授权应用网络的 TLS PING 应成功,未授权网络的端口探测应失败。
Redis 客户端兼容假设过大
普通 GET/SET 正常,不代表 Sentinel、Cluster、RESP3、Lua、Pipeline、模块和监控都兼容。客户端可能依赖 Redis CE 新命令、模块、产品标识或特定拓扑实现。
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO server
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning MODULE LIST
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning COMMAND INFO <command-name>
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning HELLO 3在影子环境回放真实命令,覆盖超时、重连、故障切换、Cluster 重定向、脚本和序列化。缺失关键能力时应替换命令、选择兼容客户端版本或保留原产品。验证标准是业务回归和故障演练通过,不是仅能建立 TCP 连接。
Redis CE 7.4+ 数据文件不能直接迁
Valkey 加载 Redis CE 7.4 及以后生成的 RDB/AOF 失败时,先保留源文件和日志,不要在唯一副本上修复:
redis-cli -h source INFO server
redis-cli -h source INFO persistence
redis-cli -h source MODULE LIST
sha256sum dump.rdb
journalctl -u valkey -n 200 --no-pagerRedis OSS 7.2 及更早版本是明确兼容基础,不能据此推导后续 Redis CE 数据文件兼容。应改用逻辑迁移、受支持的在线复制工具或经过验证的中间版本,先全量再追平增量;模块数据单独验证。
验收要比较 Key 类型、成员数、值摘要、TTL、Stream 消费组和业务查询。目标端不通过时按冻结窗口回滚,禁止跳过错误记录强行启动。
Cluster 返回 MOVED、ASK 或非 0 DB 不可用
MOVED 表示槽位长期归属变化,ASK 表示槽位迁移中的临时重定向,CROSSSLOT 表示多 Key 不在同一槽位。
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER INFO
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER NODES
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER KEYSLOT 'order:{1001}:detail'
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER KEYSLOT 'order:{1001}:items'使用原生 Cluster 客户端并配置多个种子节点,不要手工固定某个主节点。多 Key 操作使用 Hash Tag 落入同一槽位。多个逻辑数据库还要核对目标版本和 cluster-databases;跨产品兼容时优先使用 DB 0 和 Key 前缀隔离。
验证要求 cluster_state:ok、16384 槽完整覆盖、相关 Key 的 KEYSLOT 相同,槽位迁移期间客户端能刷新拓扑且错误率回落。
Valkey 不是 Redis 8 的无风险替代
迁移前使用业务命令清单验证协议、命令、脚本、模块、数据文件、拓扑发现和运维工具:
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO server
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning MODULE LIST
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning COMMAND INFO <command-name>任何关键能力无等价实现时,应修改业务、采用逻辑迁移或继续保留原系统。验证结果必须是可复现的命令和业务用例,不以“客户端能连”作为成功证据。
redis_version 使监控和自动识别误判
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO server |
rg 'server_name|valkey_version|redis_version'产品识别优先使用 server_name 和 valkey_version,redis_version 只用于兼容判断。修复采集规则、资产字段和告警标签后,在监控平台确认产品、版本和拓扑维度正确,不能只修改仪表盘显示名称。
Cluster 暴露 Key 与数据库设计问题
Lua、事务和多 Key 命令出现 CROSSSLOT 时,先验证槽位:
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER KEYSLOT 'order:{1001}:detail'
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER KEYSLOT 'order:{1001}:items'
valkey-cli -c -h 10.0.0.11 -p 6379 -a "$VALKEY_OPS_PASSWORD" CLUSTER KEYSLOT 'order:{1002}:detail'同一订单的前两条应返回相同槽位,不同订单通常落在其他槽位。围绕真正需要原子操作的聚合根设计 Hash Tag,不能把所有业务 Key 放进同一个 Tag,否则会制造热点槽位。修复后回归 Lua、事务和批量操作,并检查各主节点槽位、内存和 QPS 是否均衡。
GUI 或连接名称导致误连生产
连接名称必须同时包含产品、环境、区域和用途,例如 valkey-prod-cn-cache-readonly。GUI 默认只配置只读 ACL 用户,生产写权限通过临时授权和审计流程获得,连接不得关闭 TLS 证书校验。
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning INFO server
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning ACL WHOAMI
valkey-cli --user "$VALKEY_OPS_USER" -a "$VALKEY_OPS_PASSWORD" --no-auth-warning CLIENT LIST高风险操作前核对 server_name、valkey_version、主机、端口、ACL 身份和 Key 前缀。修复后用只读账号反向验证 GET 成功而 DEL、FLUSHALL、CONFIG SET 被拒绝,并确认审计记录能定位连接来源和身份。
