Redis 的 hash 是什么?
- 标签:后端、Redis
🏛 业务架构师
Redis Hash 是字段级 KV 结构:一个 key 下挂多个 field→value,支持单字段的读写(hget/hset)、增减(hincrby)、部分获取(hmget)。
它回答的业务问题是:当对象的更新粒度是"字段"而不是"整个对象"时。对比两种存法就清楚:
- String 存整个 JSON:改一个字段要 GET 全量→改→SET 全量,并发改不同字段还会互相覆盖(丢失更新);读一个字段也要拉全量,网络与反序列化都是浪费;
- Hash 分字段存:hset 购物车
商品1001 2只动一个字段;hgetcart 只取数量——粒度对齐业务操作,就省了全量搬运的成本。
我的典型落地场景:
- 购物车:key=用户 ID,field=商品 SKU,value=数量。加购/改数量/删单品都是字段级操作,天然无覆盖冲突;
- 商品详情缓存:标题、价格、库存分开字段——价格高频变、标题基本不变,拆开后价格更新不触碰其他字段,配合 binlog 订阅精准失效更顺;
- 用户画像/会话存储:按需取部分属性,节省大对象传输;
- 每日指标看板:field=小时/维度,局部更新。
边界提醒:小 hash 在 Redis 内部自动紧凑编码(listpack),省内存且高效;但字段数失控会退化——超大 hash(十万 field)要评估拆分(按 field 前缀拆多个 key)。设计时给"一个 hash 最多多少字段"定规范(经验值 1000 内)。
一句话:Hash 的架构意义是"更新粒度与业务操作粒度对齐"——粒度对齐了,网络、并发、内存三项成本同时下降。
🏗 系统架构师
Redis Hash 是string 类型的 field-value 映射表,适合"一个对象下多个字段独立读写"的场景:
- 与 String 存整对象 JSON 对比:String 整存整取(改一个字段要整读反序列化→改→写回,网络放大、并发覆盖);Hash 支持 HSET 单字段更新、HGET 单字段读取,字段级操作原子且省流量;
- 内存优化:小 Hash(field/ value 都短、条数少)底层用 **listpack(7.0 前 ziplist)**紧凑存储,省掉大量指针开销;超阈值转 hashtable(hash-max-listpack-entries 默认 128、value 64 字节);
- 大 Hash 风险:field 数十万后变 big key,HGETALL 全量拉取会阻塞——我规范里限制单 Hash field 数(超过就按 field 再哈希拆成 user:1:basic / user:1:ext,或按 field 分片到多个 key),只 HGETALL 小对象,大对象按需 HMGET 指定字段。
典型落地:购物车(key=cart:userId,field=商品ID,value=数量,HINCRBY 增减)、用户资料缓存、商品多维属性、AB 实验标记(field=实验,value=分组)。
选型判断:字段级并发修改、部分读、字段数可控 → Hash;整体读写且并发低 → String+JSON 更简单序列化成本低。
💻 全栈开发专家
Hash = field-value 映射的组,适合"一个对象的部分字段频繁读写"的场景。
bash
HSET cart:1001 sku:88 2 sku:95 1 # 购物车:用户1001 加两个商品
HINCRBY cart:1001 sku:88 1 # 数量+1,原子
HGETALL cart:1001 # 结算时取全量
HDEL cart:1001 sku:95 # 移除
EXPIRE cart:1001 604800 # 购物车7天过期为什么不用 String 存整个 JSON:改一个字段要 GET 全量 → 反序列化 → 修改 → 序列化 → SET 回去,大对象高频更新又费网络又非原子。Hash 天然字段级操作 + HINCRBY 原子计数。
典型场景:购物车、用户资料分散更新、商品多维度计数(库存/浏览/点赞分别一个 field)、分布式会话存储。
两个工程坑:
- 大 Hash:几十万元素后
HGETALL卡主线程(bigkey)。按业务拆:cart:1001→ 按类目拆cart:1001:clothes;读用HMGET指定 field,别全量;必要时HSCAN游标遍历; - 编码选择:小 hash 用 ziplist/listpack 省内存,超过
hash-max-ziplist-entries(128)/value-length(64)转标准 hashtable,内存跳涨——监控大 hash 的字段数,超限前主动拆。