String vs Hash 存储对象
🟡 中等题目描述
在 Redis 中存储对象,应该使用 String 还是 Hash?它们的区别是什么?
示例场景
# 用户对象
{
"id": 1,
"name": "Alice",
"age": 25,
"email": "alice@example.com"
}
# 方式 1:String 序列化存储
SET user:1 '{"id":1,"name":"Alice","age":25,"email":"alice@example.com"}'
GET user:1
# 方式 2:Hash 存储字段
HSET user:1 id 1 name "Alice" age 25 email "alice@example.com"
HGET user:1 name
HGETALL user:1提示
- 考虑内存占用
- 考虑读写性能
- 考虑灵活性
- 考虑编码方式
解法
参考答案 (3 个标签)
String Hash 对象存储
String 存储对象
# 序列化存储
SET user:1 '{"id":1,"name":"Alice","age":25}'
# 获取
GET user:1
# {"id":1,"name":"Alice","age":25}
# 修改单个字段(需要先获取再设置)
GET user:1
# 解析 JSON
# 修改字段
# 序列化 JSON
SET user:1 '{"id":1,"name":"Bob","age":25}'特点:
- 整体存储,一次性读写
- 修改需要序列化/反序列化
- 内部使用 SDS(Simple Dynamic String)
Hash 存储对象
# 设置字段
HSET user:1 id 1 name "Alice" age 25
# 获取单个字段
HGET user:1 name
# "Alice"
# 获取所有字段
HGETALL user:1
# 1) "id"
# 2) "1"
# 3) "name"
# 4) "Alice"
# 5) "age"
# 6) "25"
# 修改单个字段
HSET user:1 age 26
# 删除字段
HDEL user:1 email特点:
- 按字段存储,灵活读写
- 可以单独修改某个字段
- 内部使用压缩表或哈希表
对比分析
| 特性 | String | Hash |
|---|---|---|
| 内存占用 | 较大 | 较小(压缩表) |
| 读取性能 | O(1) | O(1) |
| 修改性能 | 低(需要序列化) | 高(直接修改字段) |
| 灵活性 | 低(整体替换) | 高(字段级别操作) |
| 过期控制 | 整个 key 过期 | 整个 key 过期 |
| 网络开销 | 小(一次命令) | 大(多次命令) |
底层实现
String 编码
// SDS (Simple Dynamic String)
struct sdshdr {
int len; // 已使用长度
int free; // 剩余空间
char buf[]; // 字节数组
}
// 编码方式
// int: 整数且值 < 1e10
// embstr: 字符串 ≤ 39 字节(一次分配)
// raw: 字符串 > 39 字节(两次分配)Hash 编码
// 压缩表(ziplist)
// 条件:
// 1. 所有键值对的键和值的字符串长度都 < 64 字节
// 2. 键值对数量 < 512 个
// 哈希表(hashtable)
// 条件:
// 1. 任一键或值的字符串长度 >= 64 字节
// 2. 键值对数量 >= 512 个
struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小
unsigned long used; // 已有节点数量
}使用建议
使用 String 的场景
# 1. 对象字段固定,整体读写
SET session:abc123 '{"user_id":1,"login_time":"2024-01-01"}'
# 2. 需要原子性修改
INCR user:1:login_count
# 3. 二进制数据
SET avatar:1 <binary_data>使用 Hash 的场景
# 1. 对象字段多,需要单独修改
HSET user:1 name "Alice" age 25 email "alice@example.com"
HSET user:1 age 26 # 只修改 age
# 2. 购物车(商品ID + 数量)
HSET cart:1 item:1 5 item:2 3 item:3 2
HINCRBY cart:1 item:1 1 # 增加 item:1 的数量
# 3. 用户属性
HSET user:1 name "Alice" age 25 city "Beijing"
HGET user:1 city扩展:内存优化
Hash 的内存优化
# 配置 ziplist 参数
hash-max-ziplist-entries 512 # 键值对数量
hash-max-ziplist-value 64 # 字节数
# 查看 encoding
OBJECT encoding user:1
# "ziplist" 或 "hashtable"
# 强制使用 ziplist
# 确保字段少且值小
HSET small key1 "value1" key2 "value2"
OBJECT encoding small
# "ziplist"String 的内存优化
# 使用整数编码
SET counter 1000
OBJECT encoding counter
# "int"
# 使用 embstr 编码
SET short "hello"
OBJECT encoding short
# "embstr"
# 避免 raw 编码
SET long "very very long string..."
OBJECT encoding long
# "raw"性能测试
String 读写
# 写入 10000 次
time redis-benchmark -t set -n 10000 -q
# SET: 80000 requests/sec
# 读取 10000 次
time redis-benchmark -t get -n 10000 -q
# GET: 90000 requests/secHash 读写
# HSET 10000 次
time redis-benchmark -t hset -n 10000 -q
# HSET: 70000 requests/sec
# HGET 10000 次
time redis-benchmark -t hget -n 10000 -q
# HGET: 75000 requests/sec
# HGETALL 10000 次
time redis-benchmark -t hgetall -n 10000 -q
# HGETALL: 50000 requests/sec