用 Hash 存对象优于 String 存 JSON,因 Hash 支持字段级读写、避免反序列化开销和并发覆盖,内存更省;Sorted Set 做排行榜需确保 score 全局唯一(如 timestamp×1000000+uid),List 作队列应区分阻塞/非阻塞场景,Set 求交集前须用 scard 预判大小以防性能陡降。

用 Hash 存对象,别用 String 存整个 JSON
把一个 User 对象序列化成 JSON 存 String 类型,看着简单,但改某个字段(比如只更新 lastLoginTime)就得反序列化→修改→再序列化→全量写回,既慢又容易出并发覆盖问题。Hash 天然支持字段级操作,内存也更省。
-
hset("user:1001", "name", "Alice")和hset("user:1001", "email", "a@example.com")可分别写入 - 用
hget("user:1001", "name")直接取字段,不碰其他数据 - Spring Boot 中推荐用
redisTemplate.opsForHash().put(),配合自定义的StringRedisSerializer(key)和GenericJackson2JsonRedisSerializer(value) - 注意:Hash 的 field 是字符串,不要用嵌套对象当 field 名,否则序列化会混乱
Sorted Set 做排行榜时,score 别直接存时间戳
用 ZADD leaderboard 1623456789 "uid:123" 看似能按登录时间排序,但一旦用户重复登录,score 不变就无法刷新位置;更糟的是,如果多个用户同一秒登录,score 冲突会导致 zset 排序不稳定(Redis 不保证同分元素顺序)。
- 正确做法是组合 score:比如
timestamp * 1000000 + uid,确保全局唯一且可逆 - 或者用
System.nanoTime()生成高精度单调递增值(需服务端时钟稳定) - 查询时用
zrevrange leaderboard 0 9 WITHSCORES拿前 10 名,别依赖zrangebyscore做分页——它在大数据量下性能差 - Spring Boot 里建议封装成工具方法,避免每次手算 score
List 当队列用,得区分阻塞 vs 非阻塞场景
lpush + rpop 看起来像队列,但没消费者时 rpop 返回 null,轮询浪费 CPU;而 brpop 虽能阻塞等待,却没法做超时兜底或优雅退出。
- 高并发任务队列优先选
brpop,配合合理timeout(比如 2s),避免线程永久挂起 - 实时性要求低的后台任务(如日志归档),用
lpush+ 定时lrange批量拉取,减少连接压力 - 别在 List 里存大对象——每次
lrange都要全量加载,容易 OOM;拆成小批次,用ltrim及时清理已处理项 - Spring Boot 中
redisTemplate.opsForList().rightPop()默认非阻塞,要阻塞得用boundListOps().rightPop(2, TimeUnit.SECONDS)
Set 做标签系统,交集运算前先做 size 预判
查“同时打上 java 和 spring 标签的用户”,用 sinter user:tag:java user:tag:spring 很直观。但如果其中一个集合有 500 万成员,另一个只有 200,Redis 会遍历大集合去匹配小集合,耗时飙升。
- 先用
scard查两个集合大小,始终让小集合做基准(sismember循环查)或用sscan流式处理 - 生产环境慎用
sinterstore——它会阻塞主线程,大集合计算期间其他命令全卡住 - 标签数据量大时,改用 Sorted Set + score 权重,用
zrangebyscore做带权交集模拟 - Spring Boot 里
redisTemplate.opsForSet().intersect()底层就是sinter,调用前务必加 size 判断逻辑
结构化数据存 Redis,核心不是“能不能存”,而是“怎么让读写路径最短、扩容最平滑、出错最可控”。Hash 的字段隔离、Sorted Set 的 score 设计、List 的消费模型选择、Set 的集合大小敏感——这些细节不提前想清楚,上线后查慢查询、内存暴涨、数据不一致的问题都会集中爆发。


















