Hash比多个String更省内存且更快,因其小数据时默认用ziplist连续存储、无指针开销,而多个String需重复元数据;实测省40%~60%内存,HGETALL也比多次GET更少网络往返。

用 Hash 存多字段比多个 String 更省内存也更快
Redis 的 Hash 在小数据量时默认用 ziplist 编码,连续内存布局、无指针开销;而等价的多个 String key 每个都要独立分配内存 + 维护哈希表槽位,元数据开销翻倍。实测 10 个字段的用户信息,HSET user:1001 name age city ... 比 10 个 SET user:1001:name xxx 类 key 节省内存约 40%~60%,且 HGETALL 一次取全比 10 次 GET 网络往返更少。
注意:HGETALL 对大 Hash 是阻塞操作,如果字段数超千级,应改用 HSCAN 分批读取,或提前按业务维度拆分(如 user:1001:base / user:1001:ext)。
排行榜必须用 ZSet,别用 List + SORT
ZSet 的底层是跳表(skiplist)+ 哈希表,ZADD/ZRANGE 时间复杂度稳定在 O(log N),插入和范围查询都高效;而用 List 存分数再配 SORT,每次排序都是 O(N log N),且无法去重、不能按分值精确查——ZSCORE 是 O(1),LIST 里找某人分数得遍历。
常见误用:LPUSH + SORT key BY score* GET #。这不仅慢,还让 Redis 主线程卡顿。换成 ZADD rank:week 95.5 "uid:123",ZRANGE rank:week 0 9 WITHSCORES 即可。
- score 必须是数字,字符串会报错
(error) ERR value is not a valid float - 重复
ZADD同一 member 会更新 score,天然支持动态刷新排名 - 需要按时间窗口隔离时,key 名加日期后缀,比如
rank:20260713,避免单 key 膨胀
去重场景优先选 Set,而非 List + LREM
Set 的 SADD 和 SISMEMBER 都是 O(1),底层用 intset(小整数)或 hashtable,插入即去重;而用 List 存 ID 再靠 LREM 清重,每次都要遍历,O(N) 且无法原子判断是否存在。
典型陷阱:用 LPUSH seen:1001 "20260713" 记录用户当天访问,再用 LLEN 判断是否已存在——这完全无效,LLEN 只返回长度,不查内容。正确做法是 SADD seen:1001 20260713,返回值 1 表示新增,0 表示已存在。
注意:SMEMBERS 对大 Set 同样阻塞,超 1 万元素务必改用 SSCAN 游标遍历。
大 Value 别硬塞进 String,拆或压
单个 String value 超 10KB 就该警惕:序列化/反序列化耗 CPU,网络传输易超时,GET 时主线程卡顿明显。比如存用户完整档案 JSON,300KB 的 String 会让一次读取延迟飙升到几十毫秒。
可行方案:
- 拆成
Hash字段:用HSET user:1001 profile:name profile:avatar ...,按需读取子字段 - 启用压缩:客户端序列化前用
zlib或snappy压缩,value 存二进制 blob,读取后解压(注意 CPU 开销平衡) - 转存外部存储:只在 Redis 存 ID 或短 token,真实数据走对象存储或数据库
别忘了用 MEMORY USAGE key 实时验证优化效果——有些“优化”只是把问题从内存转移到 CPU 或网络。



















