Redis 的工程价值不只是把数据放进内存,而是为计数、集合、排序和短期状态提供直接可用的操作。要用好它,需要同时回答:一次命令会占用多久,数据采用什么编码,故障后可以恢复到哪里。
本文以 Redis 7.2 的常见开源部署为主要范围,避免将早期 zipmap 配置、后续版本特性和当前运行参数混用。这里讨论五种基础类型,不代表 Redis 只有这五种类型。
命令执行集中,也会形成瓶颈
普通核心命令主要在主线程上执行,减少了对核心数据结构的并发协调。网络 I/O、后台持久化、异步释放等工作可由其他线程或进程协作完成。因此“Redis 整个进程只有一个线程”“完全没有锁和上下文切换”都不准确。
单个核心可以被命令执行打满。大型集合运算、长 Lua 脚本、序列化和大量小命令,都可能使 CPU 成为瓶颈;网络、内存容量和持久化 I/O 也可能限制吞吐。先看实际负载,不能预先宣布瓶颈永远在网络。
Pipeline 可以减少逐条等待响应造成的往返开销,但不保证固定提升倍数,也不等于所有字节只形成一个网络包。批量太大会增加客户端和服务器的缓冲需求。基准至少要记录命令组合、值大小、连接数、批次、持久化配置、硬件和尾延迟。
排查时可使用:
SLOWLOG GET 10
INFO commandstats
INFO memory
INFO persistence
SLOWLOG 关注服务器执行命令的时间,不包含完整网络往返。简单命令偶发慢只是线索,需要结合缺页、CPU 调度、持久化与系统负载分析,不能直接据此确认发生了 swap。
用数据语义选类型,再检查实际编码
String:计数与单值状态
String 的值可采用整数、嵌入式字符串或一般字符串编码。SDS 记录长度并支持二进制内容,但 Redis 7.2 的实际头结构有多种形式,不再用一份早期 len/free 结构代表所有字符串。
SET cache:item:1 payload EX 60
INCR page:views
MGET item:1 item:2
String 通常以 512 MiB 作为单值大小限制的说明基准,还需核对服务端相关配置和命令边界;实际业务应远早于这一上限评估大值风险。每个 key 的元数据开销取决于长度、编码、分配器和平台,不能固定为 90 字节。
若用于锁,带过期时间的普通 SET 还不够,需要 NX、唯一持有者标识,以及校验持有者后删除的协议;租约到期也不会自动停止旧持有者正在做的工作。
List:顺序队列与可靠消费是两回事
Redis 7.2 的 List 通常使用 quicklist,将紧凑的 listpack 块组织起来,不能简单描述为“每个元素一个双向链表节点”。
LPUSH jobs job-1
LRANGE jobs 0 9
BLPOP jobs 30
LMOVE jobs processing RIGHT LEFT
BLPOP 把元素移除后,如果消费者在处理完成前崩溃,任务不会自动回到列表。LMOVE 可把待处理任务转移到 processing 列表,但调用方仍要实现确认删除、超时认领、重试和幂等。Redis 持久化和复制的故障窗口也独立存在。需要消费组与 pending 管理时,可以进一步评估 Streams。
Hash:把同一对象的属性放在一起
小 Hash 可使用 listpack,超过相应限制后转换为哈希表。Redis 7.2 默认配置中的 hash-max-listpack-entries 为 512、hash-max-listpack-value 为 64;它们是编码阈值,不是 Hash 业务容量上限。Redis 7.2 配置
HSET user:1 name 张三 age 28
HMGET user:1 name age
HINCRBY user:1 age 1
将几个相关字段合成一个 Hash 可以减少重复 key 开销,但节省比例取决于字段长度和是否保持紧凑编码。还要考虑是否需要独立 TTL:这里的 7.2 基准不应直接套用后续版本的字段过期能力。
Set:去重与集合运算
Set 的编码与元素类型和规模有关,可能使用整数集合、listpack 或哈希表。哈希编码下单元素查找通常为期望 O(1),集合交并差的成本则随输入和结果规模变化,不能都概括为 O(1)。
SADD tags:user:1 java redis
SINTER tags:user:1 tags:user:2
SDIFF tags:user:1 tags:user:2
大集合运算可能长时间占用命令执行线程,先评估最大集合和结果大小。SCAN 类迭代可分批遍历,但不是一致性快照,调用方需处理重复以及扫描期间的变更语义。
Sorted Set:排序与结果规模共同决定成本
小有序集合可采用 listpack;较大集合常用字典配合跳表,分别服务成员查分和顺序访问。范围查询一般要计入返回 M 个元素的成本,而不只是定位的 O(log N)。
ZADD board 1000 user:1
ZINCRBY board 10 user:1
ZRANGE board 0 9 REV WITHSCORES
ZRANGE board 90 100 BYSCORE
排行榜和延迟任务都可利用 score 排序,但延迟队列还要设计原子领取、失败恢复和重复执行处理。数据结构提供排序能力,不会自动提供整个任务系统的可靠性。
过期删除与内存淘汰解决不同问题
过期机制让 TTL 到期的数据在读取语义上失效,并通过访问检查与主动清理回收空间。主动清理受时间预算和配置影响,不是固定“每秒十次、每次一百个、超过四分之一就无限重复”的通用算法。
复制场景下,主节点协调过期删除并传播相应操作;副本也会跟踪过期信息,读取过期 key 时不会简单地一直返回旧值直到收到 DEL。故障切换后,副本成为主节点则承担主动过期职责。复制与过期
淘汰则是在 maxmemory 策略下释放容量,即使 key 尚未过期也可能被选中:
| 策略 | 适用范围与行为 |
|---|---|
| noeviction | 不为容量淘汰 key,可能增加内存的写命令被拒绝 |
| allkeys-lru / allkeys-lfu | 在所有 key 中按近似访问历史选择 |
| volatile-lru / volatile-lfu | 只考虑设置过期时间的 key |
| volatile-ttl | 优先淘汰剩余 TTL 较短的候选 |
Redis 7.2 默认 maxmemory-policy 为 noeviction,不是 volatile-lru。maxmemory 也不是整个进程 RSS 的硬上限:复制缓冲、碎片及后台任务等需要额外空间。按工作负载设置容量和容器资源,不能仅给数据量乘一个固定倍数。
持久化关注确认点与恢复点
操作写入内核缓冲区,并不等于已经完成持久化。fsync 请求存储栈兑现相应刷盘契约;实际可靠性仍依赖文件系统、设备和故障类型,不能因为用了企业级控制器就声称“到缓存后绝对安全”。
RDB 保存某个时点的快照。BGSAVE 的子进程生成文件,父进程继续处理请求;fork 和写时复制仍带来停顿及额外内存成本。新文件完成后再替换旧快照,减少生成中断破坏旧文件的风险,但不能覆盖所有介质损坏情形。
AOF 记录用于重建数据的写操作。Redis 7 起采用多部件组织,包括基文件、增量文件及 manifest,基文件还可以使用 RDB 格式,因此不应把整个 AOF 描述为单一纯文本文件。持久化机制
| 选择 | 需要核对的恢复语义 |
|---|---|
| RDB | 恢复到最近一次成功快照,丢失窗口取决于成功生成时间 |
| AOF everysec | 正常条件下以秒为量级同步,异常 I/O 下不能承诺固定一秒或两秒硬上限 |
| AOF always | 以同步成本换更强的已确认写持久性,仍需验证存储契约 |
| AOF no | 同步交给 OS,不把某个系统默认写回周期当成保证 |
| 不持久化 | 适用于可重建缓存,并评估重建压力 |
AOF 重写按当前数据重建基线,不保存完整业务审计历史。备份多部件 AOF 时要取得相互一致的文件集合,不能只复制其中一个增量文件。是否同时开启 RDB/AOF,取决于可接受丢失窗口、恢复时间和备份策略;最终验收应是从备份实际恢复。
高可用:检测、选举与数据复制分别处理
Redis 复制默认是异步的,主节点确认的写入可能尚未到达被提升的副本。全量同步建立基线,复制流继续追赶;断连后能否部分重同步取决于复制标识、偏移和 backlog 覆盖。全量过程还可能使用无盘复制,不一定先在主机上生成一个持久 RDB 文件。
Sentinel 中需要区分:
- 单个 Sentinel 根据通信与配置判定主观下线
- 达到配置 quorum 的判断形成客观下线
- 执行故障转移还要取得 Sentinel 多数派授权
- 选择符合条件的副本提升,并重新配置其他节点与客户端发现信息
quorum 是配置值,不等于所有场景中的多数派;故障转移也没有固定五秒完成保证。通常部署至少三个独立 Sentinel,具体数量与故障域设计共同决定可用性。Sentinel 机制
Cluster 进一步按 16384 个槽分片。hash tag 可以让相关 key 落在同一槽,MOVED/ASK 用于位置发现和迁移期间的路由处理。多 key 命令、脚本和事务须核对同槽要求。自动故障转移仍依赖可用副本与投票条件,不是“任意主节点故障都必然无损切换”。
把原子操作放回业务流程
Lua 脚本执行期间不会与其他普通命令交错,但执行中遇错不会自动撤销此前已完成的写入。写前应完成参数校验,并控制运行时间。单纯设置值和过期时间可直接用 SET 的 EX/PX 选项,无须拆成容易部分成功的脚本。
在性能治理上,先控制单次工作的上界:
- 大对象删除评估 UNLINK;后台释放仍有资源成本
- 分批扫描和分页返回,避免 HGETALL 或巨量范围结果占用过久
- 为 pipeline、Lua 和连接池设置与延迟目标一致的边界
- 根据权限控制清库等操作,不将所有运维命令永久不可用当成唯一方案
连接池大小由请求到达率、单次占用时长和服务器容量共同决定;连接越多不等于吞吐越高。Redis 与 Memcached 的比较也应落在需要的数据操作、可靠性、内存和现有运维体系上,而不是套用某个固定大小阈值。
Redis 的优势在于用合适的数据操作减少应用协调;把编码、最坏命令成本和故障恢复点一起纳入设计,才能把这种优势延伸到真实业务。