Redis Hash 数据类型详解
Hash 是 Redis 的字段 - 值对集合,适合存储对象。本文将深入 Hash 的底层结构,掌握对象存储等核心应用场景。
一、Hash 基础概念
1.1 什么是 Hash
Hash 是字段 - 值对的集合:
- 类似字典/映射
- 支持字段级操作
- 适合存储对象
# 设置字段
HSET user:1001 name "John"
HSET user:1001 age 25
HSET user:1001 email "john@example.com"
# 批量设置
HMSET user:1001 name "John" age 25 email "john@example.com"
# 获取字段
HGET user:1001 name # "John"
HMGET user:1001 name age # ["John", "25"]
# 获取所有
HGETALL user:1001
1.2 应用场景
| 场景 | 说明 |
|---|---|
| 对象存储 | 用户信息、商品信息 |
| 购物车 | 用户 - 商品 - 数量 |
| 计数器 | 多维度统计 |
| 配置管理 | 系统配置项 |
二、底层数据结构
2.1 压缩列表(ziplist)
小对象使用:
// ziplist 结构
┌────────────────────────────────────┐
│ zlbytes │ zltail │ zlen │ entries │
│ 4B │ 4B │ 2B │ 可变 │
└────────────────────────────────────┘
entries (field-value 对):
┌───────────┬───────────┬───────────┬───────────┐
│ field1 │ value1 │ field2 │ value2 │
│ "name" │ "John" │ "age" │ "25" │
└───────────┴───────────┴───────────┴───────────┘
触发条件:
# Redis 配置
hash-max-ziplist-entries 512 # 字段数 < 512
hash-max-ziplist-value 64 # 字段值 < 64 字节
2.2 字典(dict)
大对象使用:
// 字典结构
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx;
unsigned long iterators_count;
} dict;
// 哈希表
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
// 字典条目
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d64;
} v;
struct dictEntry *next;
} dictEntry;
2.3 渐进式 rehash
当负载因子过高时,触发 rehash:
1. 分配新哈希表(2 倍大小)
2. 逐步迁移数据
3. 删除旧哈希表
迁移过程:
- 每次操作迁移少量数据
- 避免一次性迁移的性能问题
三、核心命令详解
3.1 字段操作
# 设置字段
HSET key field value
HSETNX key field value # 仅当不存在
# 获取字段
HGET key field
HMGET key field [field ...]
# 删除字段
HDEL key field [field ...]
# 检查字段
HEXISTS key field
# 字段数量
HLEN key
3.2 批量操作
# 批量设置
HMSET key field value [field value ...]
# Redis 4.0+ 已废弃,使用 HSET 替代
# 批量获取
HMGET key field1 field2 field3
# 返回:["value1", "value2", "value3"]
# 获取所有
HGETALL key
# 返回:["field1", "value1", "field2", "value2"]
3.3 数值操作
# 自增自减
HINCRBY key field increment
HINCRBYFLOAT key field increment
# 示例
HSET user:1001 score 100
HINCRBY user:1001 score 10 # 110
HINCRBYFLOAT user:1001 score 0.5 # 110.5
3.4 遍历操作
# 获取所有字段
HKEYS key
# 获取所有值
HVALS key
# 获取所有字段和值
HGETALL key
# 迭代(适合大 Hash)
HSCAN key cursor [MATCH pattern] [COUNT count]
四、对象存储
4.1 用户信息存储
# 存储用户信息
HSET user:1001 \
id 1001 \
name "John" \
age 25 \
email "john@example.com" \
created_at "2024-01-01"
# 获取单个字段
HGET user:1001 name
# 获取多个字段
HMGET user:1001 name age email
# 更新字段
HSET user:1001 age 26
# 删除字段
HDEL user:1001 email
Python 实现:
class UserModel:
def __init__(self, redis_client):
self.redis = redis_client
def create(self, user_data):
"""创建用户"""
key = f"user:{user_data['id']}"
self.redis.hset(key, mapping=user_data)
def get(self, user_id):
"""获取用户"""
key = f"user:{user_id}"
data = self.redis.hgetall(key)
return {k.decode(): v.decode() for k, v in data.items()} if data else None
def update(self, user_id, data):
"""更新用户"""
key = f"user:{user_id}"
self.redis.hset(key, mapping=data)
def delete(self, user_id):
"""删除用户"""
key = f"user:{user_id}"
self.redis.delete(key)
# 使用示例
user_model = UserModel(redis)
# 创建
user_model.create({
'id': 1001,
'name': 'John',
'age': 25,
'email': 'john@example.com'
})
# 获取
user = user_model.get(1001)
# 更新
user_model.update(1001, {'age': 26})
4.2 购物车实现
# 添加商品
HSET cart:user:1001 product:101 2 # 2 件
HSET cart:user:1001 product:102 1 # 1 件
# 获取购物车
HGETALL cart:user:1001
# 修改数量
HSET cart:user:1001 product:101 3
# 删除商品
HDEL cart:user:1001 product:102
# 获取商品数量
HGET cart:user:1001 product:101
# 获取购物车商品数
HLEN cart:user:1001
4.3 配置管理
# 系统配置
HSET config:system \
site_name "My Site" \
max_upload_size "10MB" \
enable_cache "true" \
cache_ttl "3600"
# 获取配置
HGET config:system site_name
HMGET config:system site_name enable_cache
# 更新配置
HSET config:system enable_cache "false"
# 删除配置
HDEL config:system cache_ttl
五、高级应用
5.1 多维度计数器
# 文章统计
HSET article:1001:stats \
views 1000 \
likes 100 \
comments 50 \
shares 20
# 增加阅读量
HINCRBY article:1001:stats views 1
# 增加点赞
HINCRBY article:1001:stats likes 1
# 获取统计
HGETALL article:1001:stats
5.2 缓存对象
class CacheManager:
def __init__(self, redis_client, key_prefix, ttl=300):
self.redis = redis_client
self.key_prefix = key_prefix
self.ttl = ttl
def set(self, obj_id, obj_data):
"""缓存对象"""
key = f"{self.key_prefix}:{obj_id}"
pipe = self.redis.pipeline()
pipe.hset(key, mapping=obj_data)
pipe.expire(key, self.ttl)
pipe.execute()
def get(self, obj_id, fields=None):
"""获取对象"""
key = f"{self.key_prefix}:{obj_id}"
if fields:
return self.redis.hmget(key, *fields)
return self.redis.hgetall(key)
def update(self, obj_id, data):
"""更新对象"""
key = f"{self.key_prefix}:{obj_id}"
self.redis.hset(key, mapping=data)
self.redis.expire(key, self.ttl) # 续期
def delete(self, obj_id):
"""删除对象"""
key = f"{self.key_prefix}:{obj_id}"
self.redis.delete(key)
# 使用示例
cache = CacheManager(redis, "product", ttl=600)
# 缓存商品
cache.set(1001, {
'id': 1001,
'name': 'Product A',
'price': 99.99,
'stock': 100
})
# 获取商品
product = cache.get(1001)
# 更新库存
cache.update(1001, {'stock': 99})
5.3 排行榜(带详细信息)
# 使用 ZSet 存储分数
ZADD leaderboard 1000 "user:1001"
ZADD leaderboard 2000 "user:1002"
# 使用 Hash 存储详细信息
HSET user:1001 name "Alice" score 1000
HSET user:1002 name "Bob" score 2000
# 获取排行榜
ZREVRANGE leaderboard 0 9 WITHSCORES
# 获取用户信息
HMGET user:1001 name score
六、性能优化
6.1 批量操作
# ❌ 不推荐:逐个操作
HSET key field1 value1
HSET key field2 value2
HSET key field3 value3
# ✅ 推荐:批量操作
HSET key field1 value1 field2 value2 field3 value3
# 或使用 Pipeline
PIPELINE
HSET key field1 value1
HSET key field2 value2
HSET key field3 value3
END
6.2 避免大 Hash
# ❌ 不推荐:单个 Hash 超过 10 万字段
HSET user:1001:orders order1 ...
HSET user:1001:orders order2 ...
# ... 10 万个订单
# ✅ 推荐:分片存储
HSET user:1001:orders:0 order1 ...
HSET user:1001:orders:1 order2 ...
# 每个 Hash 最多 1000 个字段
6.3 内存优化
# 1. 使用整数 ID
HSET user:1001 id 1001 # 整数更省内存
# 2. 缩短字段名
HSET u:1001 n "John" a 25 # n=name, a=age
# 3. 设置过期时间
EXPIRE user:1001 3600
# 4. 使用合适编码
# 小对象用 ziplist,大对象用 dict
6.4 监控指标
# 查看 Hash 信息
HLEN key
MEMORY USAGE key
# 监控大 Hash
redis-cli --bigkeys | grep hash
# 扫描字段
HSCAN key 0 COUNT 100
七、常见问题
Q1: Hash vs String?
Hash:
- 支持字段级操作
- 内存更紧凑(ziplist)
- 适合对象存储
String:
- 简单键值对
- 支持更多操作
- 适合 JSON 序列化
选择建议:
- 需要字段操作 → Hash
- 整体读写 → String (JSON)
Q2: Hash 最大字段数?
理论最大值:2^32 - 1
实际建议:< 1 万字段
Q3: 如何清空 Hash?
# 方式 1: 删除
DEL key
# 方式 2: 删除所有字段
HDEL key (HKEYS key)
# 性能:DEL 更快
Q4: Hash 如何序列化?
# 方式 1: Hash(推荐)
redis.hset("user:1001", mapping=user_dict)
# 方式 2: JSON String
redis.set("user:1001", json.dumps(user_dict))
# 对比:
# Hash: 支持字段操作,内存紧凑
# JSON: 整体读写,跨语言兼容
总结
Redis Hash 核心要点:
| 特性 | 说明 |
|---|---|
| 底层结构 | ziplist(小)/ dict(大) |
| 操作复杂度 | 字段操作 O(1) |
| 最大字段数 | 2^32 - 1 |
| 典型应用 | 对象存储、购物车、计数器 |
最佳实践:
- 小对象用 Hash,大对象考虑拆分
- 使用批量操作减少网络往返
- 控制 Hash 大小(< 1 万字段)
- 设置合理的过期时间
- 监控大 Key
掌握 Hash,高效存储对象数据!