Redis数据类型之哈希 (Hash)

作者:

在

哈希的特点和应用

==Redis哈希是一个键值对集合,特别适合存储对象==

Redis 的 Hash 就是一个大字典(大 Key),字典里面装着小字典(若干个 field-value 键值对)。

主要特点:

  • 结构化存储:键值对形式存储
  • 内存优化:小哈希使用压缩编码
  • 部分更新:可以只更新某个字段
  • 原子操作:字段级别的原子操作

常见应用场景:

  • 用户信息存储
  • 配置信息管理
  • 购物车实现
  • 对象缓存
  • 计数器组合

基本操作命令

设置和获取字段

# 用法:HSET key field value
HSET user:1001 name "张三"
HSET user:1001 age 28
HSET user:1001 city "北京"
HSET user:1001 email "zhangsan@example.com"

# 用法:HMSET key field value [field value ...]
HMSET user:1002 name "李四" age 25 city "上海" email "lisi@example.com"

# 仅当字段不存在时设置
# 用法:HSETNX key field value
HSETNX user:1001 phone "13800138000"

# 获取单个字段
# 用法:HGET key field
HGET user:1001 name
HGET user:1001 age

# 获取多个字段
# 用法:HMGET key field [field ...]
HMGET user:1001 name age city

# 获取所有字段和值
# 用法:HGETALL key
HGETALL user:1001

# 获取所有字段名
# 用法:HKEYS key
HKEYS user:1001

# 获取所有值
# 用法:HVALS key
HVALS user:1001

设置字段(存数据)

  • HSET key field value(单条设置)
    • 解释:向名为 user:1001 的 Hash 中,设置一个字段(field)和对应的值。相当于给对象赋值。
    • 例如:HSET user:1001 name "张三" 就是把 1001 用户的姓名设为张三。
  • HMSET key field value [field value ...](批量设置)
    • 解释:一次性设置多个字段和值。
  • HSETNX key field value(仅当字段不存在时设置)
    • 解释:NX 还是那个熟悉的意思(Not eXists)。如果 user:1001 里面已经有 phone 这个字段,命令直接返回 0 失败,不会覆盖旧值。常用于防重复写入。

获取字段(读数据)

  • HGET key field(获取单条)
    • 解释:获取指定字段的值。比如 HGET user:1001 name 会返回 "张三"。
  • HMGET key field [field ...](批量获取)
    • 解释:一次性获取多个指定字段的值(返回一个列表)。适合渲染页面时,一次性把需要的字段全捞出来,减少网络请求。
  • HGETALL key(获取所有)
    • 解释:获取该 Hash 下的所有字段和值(返回值是一个键值对列表)。
    • ⚠️ 性能避坑:如果这个 Hash 里有几万个字段,用 HGETALL 会瞬间阻塞 Redis。生产环境大 Hash 严禁全量获取。

获取字段名或值(只看一半)

  • HKEYS key
    • 解释:只获取所有的字段名(如 name, age, city),不获取具体的值。
  • HVALS key
    • 解释:只获取所有的字段值(如 张三, 28, 北京),不获取字段名。

删除和检查字段

# 删除字段
# 用法:HDEL key field [field ...]
HDEL user:1001 email
HDEL user:1001 phone city

# 检查字段是否存在
# 用法:HEXISTS key field
HEXISTS user:1001 name      # 返回 1(存在)
HEXISTS user:1001 phone     # 返回 0(不存在)

# 获取字段数量
# 用法:HLEN key
HLEN user:1001

删除字典里的字段:HDEL

  • 语法:HDEL key field [field ...]
  • 代码示例:
    • HDEL user:1001 email -> 把 user:1001 这个大字典里的 email 字段删掉。
    • HDEL user:1001 phone city -> 一次删掉 phone 和 city 两个字段。
  • 详细解释:它只删除字典里的指定字段,整个 Key(字典)依然存在。

检查字段是否存在:HEXISTS

  • 语法:HEXISTS key field
  • 代码示例:
    • HEXISTS user:1001 name -> 返回 1(真/存在)。
    • HEXISTS user:1001 phone -> 返回 0(假/不存在)。
  • 详细解释:检查大字典里有没有某个特定的字段。时间复杂度是 O(1),查得极快。

查看字典有多少字段:HLEN

  • 语法:HLEN key
  • 代码示例:HLEN user:1001
  • 详细解释:返回这个 Hash 里字段(field)的总个数。

批量操作

哈希支持高效的批量操作,减少网络往返次数。

# 批量设置用户信息
HMSET user:1003 
  name "王五" 
  age 30 
  city "广州" 
  email "wangwu@example.com" 
  phone "13900139000" 
  department "技术部" 
  position "高级工程师"

# 批量获取用户信息
HMGET user:1003 name age city department position

# 获取完整用户信息
HGETALL user:1003

批量设置:HMSET(图中的换行符 )

  • 图中代码:

    HMSET user:1003 
      name "王五" 
      age 30 
      city "广州" 
      ...

    详细解释:

    • 这里使用了 Linux 终端里的反斜杠 ,它的作用是“命令换行”。这其实是一整行命令,只是为了在终端里方便人类阅读,才折行写的。
    • 如果不用 ,你必须把所有字段写在同一行:HMSET user:1003 name "王五" age 30...
    • 运维意义:如果用单条的 HSET 设置 7 个字段,客户端和 Redis服务器要来回通信 7 次。用 HMSET 一次性打包发送,只通信 1 次。在高并发场景下,极大地节约了网络带宽,降低了延迟。

批量获取:HMGET

  • 图中代码:HMGET user:1003 name age city department position
  • 详细解释:一次性把这 5 个指定字段的值全取出来,返回一个列表。
  • 运维意义:同样是减少网络 IO。比如研发需要展示用户卡片(只要名字、年龄、城市),不要让他们在代码里循环调 5 次 HGET,而是让他们直接发一条 HMGET 指令。

全量获取:HGETALL

  • 图中代码:HGETALL user:1003
  • 详细解释:把这个字典里所有的字段和值全部取出来。

数值操作

哈希字段支持数值操作,常用于计数器场景。

# 增加字段的数值
# 用法:HINCRBY key field increment
HSET user:1001 login_count 0
HINCRBY user:1001 login_count 1     # login_count = 1
HINCRBY user:1001 login_count 5     # login_count = 6

# 增加字段的浮点数值
HINCRBYFLOAT key field increment
HSET user:1001 balance 100.50
HINCRBYFLOAT user:1001 balance 25.30   # balance = 125.80
HINCRBYFLOAT user:1001 balance -10.00  # balance = 115.80

整数增减:HINCRBY

  • 语法:HINCRBY key field increment
  • 代码示例:
    • HSET user:1001 login_count 0 (初始化:用户 1001 的登录次数为 0)
    • HINCRBY user:1001 login_count 1 (加 1,结果返回 1)
    • HINCRBY user:1001 login_count 5 (加 5,结果返回 6)
  • 详细解释:将 user:1001 字典中的 login_count 字段的值加上指定的整数 increment。
  • ⚠️ 极客提示:如果 increment 填负数(如 -1),就变成了减法。

浮点数增减:HINCRBYFLOAT

  • 语法:HINCRBYFLOAT key field increment
  • 代码示例:
    • HSET user:1001 balance 100.50 (初始化:余额 100.50)
    • HINCRBYFLOAT user:1001 balance 25.30 (加 25.30,返回 125.80)
    • HINCRBYFLOAT user:1001 balance -10.00 (减 10.00,返回 115.80)
  • 详细解释:支持对浮点数(小数)进行加减。完美避开由于精度问题导致的金额计算错误。

对象存储应用

哈希非常适合存储对象数据,提供了比字符串更好的结构化存储。

用户信息管理

# 创建用户信息
HMSET user:1001 
  name "张三" 
  age 28 
  email "zhangsan@example.com" 
  city "北京" 
  department "研发部" 
  position "软件工程师" 
  salary 15000 
  join_date "2023-01-15" 
  status "active"

# 更新用户信息
HSET user:1001 age 29
HSET user:1001 salary 16000
HSET user:1001 position "高级软件工程师"

# 获取用户基本信息
HMGET user:1001 name age email city

# 获取用户工作信息
HMGET user:1001 department position salary

# 检查用户状态
HGET user:1001 status

创建对象(全量写入):HMSET

  • 代码:使用 HMSET user:1001 ... 建立了一个包含姓名、年龄、邮箱、部门、薪水等 9 个属性的用户字典。
  • 业务优势:对比把整个用户数据转成 JSON 存在 String 里,Hash 结构在 Redis底层非常节省内存(Key 少,元数据开销小)。

更新对象(局部更新):HSET(核心亮点)

  • 代码:
    • HSET user:1001 age 29 (改年龄)
    • HSET user:1001 salary 16000 (改薪水)
    • HSET user:1001 position "高级软件工程师" (升职了)
  • 为什么这是核心亮点?
    • 如果这 9 个字段是存成一个大 JSON 字符串,你要改薪水,必须:把整个 JSON 查出来 -> 反序列化成对象 -> 改薪水 -> 序列化回 JSON -> 覆盖写回 Redis。极度浪费 CPU 和网络带宽。
    • 用 Hash,你只需要 HSET 修改那一个字段,指哪打哪,极其高效,且天然防并发覆盖。

==Redis key 和二维表关系==

   [ Redis 客户端发送指令 ]
              |
              v
+-------------------------------------------------------+
|  Key (大字典名 / 表名) : user:1001                    |
+-------------------------------------------------------+
|  Field (字段/行)       |  Value (值/列)                |
+------------------------+------------------------------+
|  name                  |  "张三"                      |
|  age                   |  29  (被HSET更新过)          |
|  email                 |  "zhangsan@example.com"      |
|  city                  |  "北京"                      |
|  department            |  "研发部"                    |
|  position              |  "高级软件工程师"(被更新过)  |
|  salary                |  16000 (被更新过)            |
|  join_date             |  "2023-01-15"                |
|  status                |  "active"                    |
+-------------------------------------------------------+

购物车实现

# 用户购物车:cart:user_id,字段为商品ID,值为数量
HSET cart:1001 product:123 2
HSET cart:1001 product:456 1
HSET cart:1001 product:789 3

# 增加商品数量
HINCRBY cart:1001 product:123 1     # 数量变为3

# 减少商品数量
HINCRBY cart:1001 product:456 -1    # 数量变为0

# 删除商品(数量为0时)
HDEL cart:1001 product:456

# 获取购物车所有商品
HGETALL cart:1001

# 获取购物车商品数量
HLEN cart:1001

# 清空购物车
DEL cart:1001

==配置信息管理==

# 应用配置
HMSET app:config 
  database_host "localhost" 
  database_port 3306 
  database_name "myapp" 
  redis_host "localhost" 
  redis_port 6379 
  cache_ttl 3600 
  max_connections 100 
  debug_mode "false"

# 获取数据库配置
HMGET app:config database_host database_port database_name

# 获取缓存配置
HMGET app:config redis_host redis_port cache_ttl

# 更新配置
HSET app:config debug_mode "true"
HSET app:config max_connections 200

# 获取所有配置
HGETALL app:config

初始化应用配置:HMSET(全量写入)

  • 代码:使用 HMSET app:config ... 创建了一个名为 app:config 的大字典。
  • 解释:把数据库的 host、port、name,Redis 的 host、port,以及缓存时间、最大连接数等 8 个配置项一次性写入 Redis。

按模块获取配置:HMGET(按需拉取)

  • 代码 1:HMGET app:config database_host database_port database_name
    • 解释:应用启动时,只需要连接数据库,所以精准拉取这 3 个配置项,不拉取无关的 Redis 配置。
  • 代码 2:HMGET app:config redis_host redis_port cache_ttl
    • 解释:初始化缓存模块时,精准拉取 Redis 相关的 3 个配置项。

动态更新配置:HSET(局部热更新)

  • 代码:
    • HSET app:config debug_mode "true" (开启调试模式)
    • HSET app:config max_connections 200 (调大最大连接数)
  • 解释:在系统运行过程中,运维或研发只需要修改特定的字段。不需要重写整个配置字典。
  • 优势:应用只要监听这个 Key 的变化(或者定时去 HGET),就能实现配置热加载,不用重启服务。

获取全量配置:HGETALL(全量拉取)

  • 代码:HGETALL app:config
  • 解释:一次性获取所有的配置项。通常用于后台管理系统的“配置详情”页面展示,或者用于配置的备份与审计。

📚 ==Redis Hash 核心应用:配置信息管理(轻量级配置中心)==

1. 核心目的

将应用配置(如数据库地址、缓存策略、开关参数)从代码/本地文件中剥离,集中存储在 Redis Hash 中,实现集中管理、按需获取与动态热更新。

2. 核心业务闭环

操作阶段 使用命令 业务作用 架构优势
1. 初始化配置 HMSET 一次性写入所有配置项(DB、Redis、连接数等)。 减少网络 RTT,原子性写入。
2. 按需读取 HMGET 各模块启动时,仅拉取自己需要的字段(如 DB 模块只拉取 database_*)。 精准获取,避免拉取全量数据造成带宽浪费。
3. 动态更新 HSET 运行期间修改单个配置项(如将 debug_mode 改为 true)。 局部覆盖,实现配置热更新,无需重启服务。
4. 全量查看 HGETALL 后台管理页面展示或配置审计。 方便可视化面板展示所有配置项。

3. 架构设计优势(为什么用 Hash 而不用 String?)

  1. 内存开销极低:如果使用 String 存配置,每个配置项都会产生独立的 Key(如 config:db_host、config:db_port),产生大量的元数据开销。Hash 将所有配置聚合成一个 Key(app:config),极大节省内存。
  2. 操作原子性:通过 HSET 更新单个字段,天然防止并发覆盖整个配置文件(对比 JSON 字符串的“读-改-写”流程更安全)。
  3. 结构高度契合:Hash 的 Field-Value 结构完美对应配置文件的 Key-Value 键值对。

4. ⚠️ 生产环境避坑与运维监控指南

作为 SRE/运维,在落地此方案时必须关注以下红线:

  1. 高可用与持久化要求极高:配置中心是核心命脉。一旦Redis 宕机或数据丢失,可能导致所有微服务无法启动。必须配置 AOF 持久化 + 主从高可用。
  2. 严禁高频全量拉取:坚决禁止在业务主流程(如每次 HTTP 请求)中执行 HGETALL,否则极易打满Redis的 QPS 与网络带宽。正确姿势:应用启动时拉取一次放入本地内存,后续通过 Pub/Sub 或长轮询监听变更。
  3. ==权限与误删风险==:必须对 app:config 这种核心 Key 做严格的 ACL权限控制,严禁研发误操作执行 DEL app:config(如图中清空购物车的命令,在这里执行将是毁灭性的灾难)。
  4. 热更新监控:运维需监控配置的变更频率。如果某个配置项被频繁改动(如日志级别反复横跳),可能会引发频繁的 GC或 IO 抖动。

5. 总结金句(面试)

“Redis Hash 是构建轻量级配置中心的绝佳方案。它利用聚合存储省内存、局部更新防并发的特性,实现了配置的动态热更新。但在架构层面,必须配合持久化保证与严格的ACL 权限,并杜绝高频的 HGETALL 全量拉取,才能保障系统的稳定。”

[ 👨‍💻 后台管理系统 / 运维 ] 
      │
      ├─(1. 初始化 HMSET)───────────────┐
      ├─(3. 动态更新 HSET)──────────────┤ 
      ├─(4. 全量审计 HGETALL)───────────┤
      │                                 v
      │                     +-----------------------+
      │                     |   Redis Hash (核心)   |
      │                     |  Key: app:config      |
      │                     |-----------------------|
      │                     | db_host: 127.0.0.1    |
      │                     | db_port: 3306         |
      │                     | debug_mode: false     |
      │                     +-----------------------+
      │                                 │
      │                                 ├─(2. 按需精准拉取 HMGET)──┐
      │                                 │                          v
      │                                 │                 [ 🖥️ 业务服务 A (DB模块) ]
      │                                 │                 [ 🖥️ 业务服务 B (缓存模块) ]
      │                                 │                          │
      └─(5. Pub/Sub 广播配置变更)───────┴──────────────────────────┘
                                                                   │
                                                                   v
                                                        (服务本地内存更新,无需重启)

========================================================================================
🚨 [ SRE 运维红线 / 监控告警 ]
1. 坚决禁止业务高频调 HGETALL (防止打满网络带宽和 Redis QPS)
2. 强制开启 AOF 持久化 + 主从高可用 (配置丢失 = 全体微服务瘫痪)
3. 严格 ACL 权限控制,严禁研发执行 DEL app:config (防毁灭性误删)

image-20261020010309795