7AA7 博客

Redis 数据结构实战:五种基础类型的使用场景

码同客 UID:88
2026-10-16 14:50
18 0 0

Redis 的价值不在于”快”本身,而在于它把一组精心挑选的数据结构放在了内存里,并且保证了单个命令的原子性。用好 Redis 的关键,是让业务模型尽量贴合这些结构的原生语义——一旦发现需要在客户端把多个命令拼成一个”复合操作”,就该停下来想想选型是不是出了问题。

一、五种基础类型怎么用

String 是最简单的类型,但能力不止存字符串。它可以存整数做计数器,也可以序列化整个对象存成 JSON(慎用,见后文大 key 问题)。

# 页面浏览计数
INCR article:1001:views
# 带过期的幂等令牌
SET order:dedupe:20261021:1001 1 NX EX 300

SET key value NX EX seconds 这个组合在分布式锁、防重复提交、幂等去重里出现频率极高:NX 保证只成功一次,EX 保证即使客户端崩溃锁也能自动释放。注意这是”尽力而为”的锁,对正确性要求极高的场景还需要看门狗续期和 fencing token 之类的补强。

Hash 适合存对象的部分字段,读写单个字段不需要序列化整个对象。

HSET user:1001 name "tom" level 3 last_login 1761024000
HINCRBY user:1001 level 1
HGET user:1001 name

用户资料、商品属性这类”字段多但每次只改一两个”的场景,Hash 比 String 存 JSON 更省带宽和 CPU。字段数上千时要考虑拆分,因为小 hash 在字段少时用 ziplist 紧凑编码,字段多了会转成 hashtable,内存占用有台阶式变化。

List 是双向链表语义,常做轻量级队列和时间线。

LPUSH notify:user:1001 '{"type":"sys","id":9001}'
LTRIM notify:user:1001 0 199   # 只保留最近 200 条
RPOP notify:user:1001          # 消费端取一条

List 当队列有个硬伤:消费者取走消息后如果处理失败,消息就丢了。BRPOPLPUSH 到一个备份列表可以缓解,但需要消费者自己确认删除。对可靠性有要求的场景,更常见的做法是用专业的消息队列,List 只做”通知条数”这类可丢弃的场景。

Set 提供去重和集合运算,标签、共同关注、抽奖去重都很好用。

SADD tag:article:1001 "redis" "cache" "db"
SINTER tag:article:1001 tag:article:1002   # 两篇文章的共同标签
SISMEMBER lottery:20261021 1001            # 是否已参与
SRANDMEMBER lottery:20261021 10            # 随机抽 10 个

ZSet(有序集合)是 Redis 里最能打的结构:每个成员带一个分值,按分值排序,支持范围查询和排名。

ZADD leaderboard 98.5 "user:1001" 87.2 "user:1002"
ZREVRANGE leaderboard 0 9 WITHSCORES    # 前十名
ZSCORE leaderboard "user:1001"          # 查分数
ZREVRANK leaderboard "user:1001"        # 查排名

排行榜、延时队列(分值存执行时间戳,配合 ZRANGEBYSCORE 取到期任务)、滑动窗口限流(分值存时间戳,窗口外的先 ZREMRANGEBYSCORE 清掉再数数量),都是 ZSet 的经典应用。

二、过期、内存与淘汰策略

Redis 的过期删除是惰性加定期的组合:访问到已过期的 key 时删除,同时后台周期性抽样检查。这意味着过期不等于立刻释放内存,用大量短命 key 时内存曲线会有滞后。

内存达到 maxmemory 时的行为由淘汰策略决定,常见几种:

策略 行为 适用场景
noeviction 不淘汰,写入报错 数据不能丢,宁可拒绝写
allkeys-lru 所有 key 里淘汰最近最少使用的 纯缓存
allkeys-lfu 淘汰访问频率最低的 访问分布倾斜明显的缓存
volatile-lru/ttl 只在设置了过期时间的 key 里淘汰 混合存储,缓存与持久数据共存

缓存用途选 allkeys-lru 或 allkeys-lfu 是常规做法;如果实例里既有缓存数据又有不能丢的数据(比如分布式锁的 key),就要用 volatile-* 系列并给缓存 key 统一设置过期时间,避免把锁 key 挤掉。

三、key 设计规范

key 是 Redis 里唯一的索引,设计得好坏直接影响运维和排障。几个被验证过的习惯:

  1. 命名带上业务语义和对象 ID,用冒号分隔:user:1001:profile、article:1001:views。这样用 SCAN 配合 MATCH 前缀能做业务级巡检和清理。
  2. key 不要太长。key 本身就占内存,百万级 key 时命名冗余会放大占用。语义要够,长度要省,常见做法是缩写模块名。
  3. 统一过期策略。能设过期的 key 一律设过期,别让”临时数据”变成永久数据。
  4. 用 SCAN 而不是 KEYS 做遍历。KEYS 是 O(N) 且阻塞单线程,生产环境执行等于给自己制造一次故障。SCAN 是游标式增量遍历,可能重复但不阻塞。

四、大 key 与热 key

大 key 指单个 key 的 value 过大,比如一个 Hash 存了几十万个字段,或者一个 List 塞了几百万元素。危害是多方面的:读写单个大 key 的耗时会长到阻塞其他请求(Redis 单线程处理命令),删除大 key 会造成长时间阻塞甚至超时,主从同步时大 key 传输会造成延迟尖峰。

预防比治理容易:拆分(把 user:1001:history 拆成按日期分页的多个 key)、设上限(List 用 LTRIM 限制长度)、避免用 Redis 存大对象(该放对象存储的就别塞 Redis)。已经存在的大 key,用 UNLINK(异步删除)替代 DEL,至少不会阻塞请求。

热 key 是访问集中在少数 key 上,单实例带宽或 CPU 被打满。识别手段是 --hotkeys(需要 LFU 策略)或客户端埋点统计。缓解方式包括本地缓存挡住大部分读、把热 key 复制成多份(user:1001:profile:0 到 :3,读时随机取一个)、以及从业务上减少对单点的依赖。

五、Pipeline、事务与 Lua

Redis 的单条命令是原子的,但多条命令不是。需要一次执行多条命令时有三个层次的选择:

Pipeline 解决的是网络往返开销,不是原子性。把多个命令打包发过去,省掉多次 RTT,适合批量写入。批量 HSET 一万个字段,Pipeline 比逐条发送快一个数量级。

MULTI/EXEC 事务提供原子执行,但它是”批量顺序执行”,不支持回滚,也不支持命令间的条件判断。事务里的命令排队时不会执行,其他客户端的命令会插进来——它保证的是这一批命令连续执行完,不被其他命令打断。

Lua 脚本提供真正的原子性加可编程性:脚本执行期间不会有其他命令插入,脚本里可以做条件判断和循环。计数器限流、库存扣减(判断余额够不够再扣)这类”读-判断-写”的复合操作,Lua 是标准解法。

-- 库存扣减的原子操作示意
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock and stock > 0 then
  redis.call('DECR', KEYS[1])
  return 1
end
return 0

调用用 EVALSHA 加脚本缓存,避免每次传脚本内容。要注意脚本里别做耗时操作,单个脚本执行时间过长同样会阻塞整个实例。Redis 7 之后引入的 Function 机制把脚本管理做得更规范,可以注册后按名字调用,适合团队共享脚本。

六、持久化对业务的影响

Redis 提供 RDB 和 AOF 两种持久化,都是快照式或追加式的内存数据落盘,特点差别很大。

RDB 是某个时间点的全量内存快照,文件紧凑、恢复快,适合做备份和容灾。代价是两次快照之间的数据可能丢失,而且 BGSAVE 时会 fork 子进程,数据集大时 fork 本身的耗时和写时复制带来的内存开销都值得留意。

AOF 记录写命令,丢失窗口更小(按 appendfsync 策略,always 每条命令刷盘、everysec 每秒刷盘、no 交给操作系统)。always 最安全但性能代价明显,everysec 是常见的折中。AOF 文件会持续膨胀,需要定期重写(BGREWRITEAOF)把重复命令压缩掉。

对业务的影响主要在两处:一是持久化开销高峰时段(fork、AOF 刷盘)请求延迟会抖动,压测和容量规划要把这部分算进去;二是恢复时间决定故障切换的时长,RDB 快、AOF 文件大时回放慢。多数生产配置是 AOF 开启加 everysec,配合 RDB 做定期备份,兼顾恢复速度和丢失窗口。

选型时先想清楚一件事:这份数据丢了能不能从别处重建?能重建的缓存数据可以不持久化或用宽松策略,不能重建的(比如计数器、去重集合)就必须认真对待持久化参数和备份。这个判断比纠结 appendfsync 取哪个值更重要。

274浏览量
18赞
0评论
0参与用户
11 分钟阅读时间

评论(0)

还没有评论,来抢沙发。