「五种数据类型」只是入门结论。真实项目里更关键的是:这个场景该用哪一层结构,以及它在内存里实际怎么存。

Redis 数据结构选型:计数、排行、去重与分布式锁 示意图

计数与单值:string

INCR page:view:1024
EXPIRE page:view:1024 86400
SET user:42:token "abc" EX 7200 NX

单值计数、锁标记、序列化后的小对象都放 string。INCR 是原子操作,比「读出来加一再写回」安全。计数器要配合过期或定时归档,否则长期累积会挤压内存。

对象字段:hash

HSET user:42 name "张三" city "杭州" level 3
HINCRBY user:42 level 1
HMGET user:42 name level

用户资料这类字段多、整体读取的对象用 hash,可以单独更新某个字段,也省掉整体序列化的开销。字段特别多时不要一次 HGETALL,用 HMGET 只取需要的字段,避免长时间占用单线程。

队列与时间线:list

LPUSH queue:mail 1024
BRPOP queue:mail 5

BRPOP 阻塞式取任务,比轮询省资源。但 list 没有确认机制,进程崩溃会丢任务;要求可靠投递时用 Stream(消费组 + ACK)或专业消息队列。时间线场景则控制在固定长度(LTRIM),防止单个 key 无限增长。

去重与集合运算:set

SADD article:1024:liked 42
SCARD article:1024:liked
SINTER user:1:follow user:2:follow

点赞去重、标签集合、共同关注这类需求用 set,交并差直接在服务端算完返回。成员数量很大时注意单次运算的耗时,避免在大 key 上做全量差集。

排行榜:zset

ZINCRBY rank:daily 1 user:42
ZREVRANGE rank:daily 0 9 WITHSCORES
ZRANK rank:daily user:42

zset 按 score 有序,取 Top N、查排名都是 O(log N)。同分时按成员字典序排列,需要「先到先得」就把时间戳编进 score。日榜、周榜用不同的 key,再配一个定时归并任务。

签到与布尔标记:bitmap

SETBIT sign:2026:10:42 20 1
BITCOUNT sign:2026:10:42

一年 365 天只占 46 字节,比每天一个 key 省得多。统计「大致多少人访问过」这类基数问题换成 HyperLogLog,固定 12KB 换 0.81% 误差,适合 UV,不适合需要精确对账的金额统计。

分布式锁:加锁用 SET,释放用 Lua

SET lock:order:1024 <token> NX PX 10000

释放时必须比对 token 再删除,避免删掉别人已经重新加上的锁:

if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
end

return 0

三点必须做到:加锁带过期时间防止死锁、value 用唯一 token、释放走 Lua 保证比对与删除的原子性。业务执行时间可能超过 TTL 时,需要看门狗续期,或者改用支持租约的组件。

内存与编码

数据量小时 hash、list、zset 使用紧凑编码(listpack、intset),超过阈值会转成哈希表或跳表,内存明显上涨。理解 hash-max-listpack-entries 这类参数,能解释「同样数据、两个实例内存差一倍」。用 MEMORY USAGE key 看单个 key 的真实占用,比按字段数估算可靠。

小结

选型口诀:单值 string、对象 hash、队列 list 或 stream、去重 set、排行 zset、布尔位图 bitmap、基数 HyperLogLog。缓存故障的三种形态见穿透、击穿与雪崩。