对象缓存怎么存
缓存一个业务对象有两种常见做法:整体序列化成字符串存进 String,或拆成字段存进 Hash。前者实现简单,读取一次就能拿到完整对象,解析工作交给应用;后者支持只取部分字段、只更新某个字段,并在字段较少时享受紧凑编码带来的内存优势。选择的关键在于读写模式:整对象读写为主就用 String,单字段频繁更新或需要按字段过期就用 Hash。
基本信息
| String 存法 | SET key '<序列化内容>' EX <ttl>,应用负责序列化与反序列化 |
|---|---|
| Hash 存法 | HSET key field value ...,配合 HMGET 取部分字段 |
| 字段级原子操作 | HINCRBY 递增数值字段,HSETNX 仅在字段不存在时写入 |
| 字段级过期 | 7.0 起支持对单个字段设置过期时间 |
| 内存差异 | 字段较少的 Hash 使用紧凑编码,通常比序列化字符串更省 |
典型使用场景
- 商品详情与用户资料:整对象读取为主,String 加序列化最直接
- 计数器与状态位集合:一个主体的多种计数用 Hash 合并到一个键,字段级原子递增
- 购物车:按商品维度增删改,Hash 的字段操作天然贴合
- 配置与开关集合:字段数量可控、读多写少,Hash 便于单独读取与更新
- 需要部分字段独立过期的场景:例如限时优惠标记与基础资料共存在一个键里
两种写法对照
两种写法对照
redis-cli SET user:1001 '{"name":"alice","age":30}' EX 600 # 整对象序列化
redis-cli GET user:1001
redis-cli HSET user:1002 name alice age 30 # 字段化存储
redis-cli HMGET user:1002 name age # 只取需要的字段
redis-cli HSET user:1002 age 31 # 单字段更新,无需读改写整条
redis-cli HINCRBY user:1002 login_count 1 # 字段级原子递增
redis-cli OBJECT ENCODING user:1002 # 字段少时应为 listpack两种存法对比
| 维度 | String 加序列化 | Hash 字段 |
|---|---|---|
| 整对象读取 | 一次 GET 拿到全部 | HGETALL 取全部字段 |
| 部分字段读取 | 需要全部取回后在应用侧筛选 | HMGET 只取需要的字段 |
| 单字段更新 | 读改写整条,存在并发覆盖风险 | HSET 直接改一个字段 |
| 字段级过期 | 不支持 | 7.0 起支持按字段设置过期 |
| 内存 | 序列化体积大时偏高 | 字段少时紧凑编码更省 |
字段极少的对象用 Hash 未必更省:每个键本身都有对象头与字典条目的固定开销,如果对象只有一两个字段,键数量翻倍带来的固定开销可能超过序列化字符串的体积优势。判断标准是对象字段数与键总数,而不是只看单键占用。
String 存序列化内容在并发更新时有两个写者互相覆盖的风险:两个请求各自读回旧值、修改不同字段、再整体写回,后写的会覆盖前者的修改。需要字段级并发更新时,Hash 的字段操作或乐观锁版本号才是正确做法。