Skip to content
清晨的一缕阳光
返回

Redis Hash 数据类型详解

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 │
4B4B2B   │ 可变    │
└────────────────────────────────────┘

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
典型应用对象存储、购物车、计数器

最佳实践

  1. 小对象用 Hash,大对象考虑拆分
  2. 使用批量操作减少网络往返
  3. 控制 Hash 大小(< 1 万字段)
  4. 设置合理的过期时间
  5. 监控大 Key

掌握 Hash,高效存储对象数据!

参考资料


分享这篇文章到:

上一篇文章
RAG 架构设计模式
下一篇文章
MySQL 表分区与分表实战