宁波市网站建设_网站建设公司_会员系统_seo优化
2025/12/17 14:53:14 网站建设 项目流程

懂 Redis 的数据结构原理(比如 String 是动态字符串、Hash 是压缩列表 / 哈希表),就知道存 “用户信息” 用 Hash 比 String 更省内存;

 

一、为什么用 Hash 存用户信息更省内存?

场景对比

假设存储用户信息:uid: 1001, name: "张三", age: 25, city: "北京" 方案A:用 String 存储

 
# 存为多个独立的 String
SET user:1001:uid 1001
SET user:1001:name "张三"
SET user:1001:age 25
SET user:1001:city "北京"# 或存为 JSON 格式的 String
SET user:1001 '{"uid":1001,"name":"张三","age":25,"city":"北京"}'

 

方案B:用 Hash 存储

 
HSET user:1001 uid 1001 name "张三" age 25 city "北京"

 

二、底层数据结构原理对比

1. String 的存储结构(SDS - Simple Dynamic String)

 
struct sdshdr {int len;        // 已使用的字节数int free;       // 未使用的字节数char buf[];     // 字节数组
};

 

  • 每个 String 都需要独立的 SDS 头(len + free,至少 8 字节)
  • 每个 key 都需要独立的 Redis 对象头(redisObject,16 字节)
typedef struct redisObject {unsigned type:4;        // 数据类型(4位)unsigned encoding:4;     // 编码方式(4位)unsigned lru:LRU_BITS;  // LRU时间(24位)int refcount;           // 引用计数(4字节)void *ptr;              // 指向实际数据的指针(8字节)
} robj;

 

多个 String 存储的问题

 
key1 -> redisObject(16B) + SDS头(8B) + 数据
key2 -> redisObject(16B) + SDS头(8B) + 数据
...
每个key都有重复的对象头开销!

 

2. Hash 的存储结构(ZIPLIST 或 HT)

a) ZIPLIST(压缩列表) - 小数据量的默认选择

  • 当 Hash 满足条件时(Redis 7.x 默认配置):
    • 元素数量 ≤ 512
    • 所有 value 长度 ≤ 64 字节
  • 会使用压缩列表存储
 
// 压缩列表结构:紧凑的连续内存块
[zlbytes][zltail][zllen]|[entry1][entry2]...[entryN][zlend]

 

 

优点

  • 只有 1 个 redisObject 头
  • 字段和值在内存中紧密排列
  • 没有指针开销,内存利用率极高

b) HT(哈希表) - 大数据量时自动转换

  • 当元素过多或 value 过大时,自动转为哈希表
  • 虽然比 ZIPLIST 开销大,但仍然比多个 String 节省内存,因为:
    • 只有一个主 key
    • 字段名是共享的(复用 SDS)

三、内存占用对比(示例计算)

假设存储 10000 个用户,每个用户 4 个字段: String 方案

 
总内存 ≈ 10000 × 4 × (redisObject 16B + SDS头 8B + 数据)≈ 10000 × 4 × 24B = 960,000B(不包含数据内容)≈ 937.5KB 的纯元数据开销!

 

Hash 方案(ZIPLIST)

总内存 ≈ 10000 × (1个redisObject 16B + ZIPLIST开销)≈ 10000 × 20B = 200,000B≈ 195KB 的元数据开销

节省约 75% 的内存

 

四、实战验证

# 1. 测试 String 方案内存占用
127.0.0.1:6379> FLUSHALL
127.0.0.1:6379> SET user:1001:uid 1001
127.0.0.1:6379> SET user:1001:name "张三"
127.0.0.1:6379> SET user:1001:age 25
127.0.0.1:6379> SET user:1001:city "北京"
127.0.0.1:6379> MEMORY USAGE user:1001:uid
(integer) 56  # 每个key占用~56字节# 2. 测试 Hash 方案内存占用
127.0.0.1:6379> FLUSHALL
127.0.0.1:6379> HSET user:1001 uid 1001 name "张三" age 25 city "北京"
127.0.0.1:6379> MEMORY USAGE user:1001
(integer) 120  # 整个Hash只占~120字节

 

五、其他优势

1. 原子操作更方便

# Hash 支持原子操作
HINCRBY user:1001 age 1      # 年龄+1
HSET user:1001 city "上海"    # 修改城市
HGETALL user:1001            # 获取全部# String 需要事务或Lua脚本保证原子性
MULTI
SET user:1001:city "上海"
EXEC

 

2. 网络开销更小

# 获取用户所有信息
# Hash:1次网络往返
HGETALL user:1001# String:4次网络往返 或 管道
MGET user:1001:uid user:1001:name user:1001:age user:1001:city

 

3. 序列化/反序列化更高效

  • String(JSON) 方案需要序列化/反序列化整个对象
  • Hash 可以按需获取部分字段

六、什么时候用 String 更好?

虽然 Hash 通常更优,但 String 在以下场景更适合:

  1. 需要设置过期时间:Hash 只能整体过期,String 可以分别控制
  2. 需要 incr/decr 操作:String 的原子计数器更简单
  3. 超大 value:单个字段 value 特别大时
  4. 需要利用 String 的其他特性:如 bitmap、JSON 嵌套查询等

七、最佳实践建议

  1. 优先使用 Hash​ 存储对象类型数据
  2. 控制 Hash 的 field 数量,避免从 ZIPLIST 转成 HT
  3. 小对象用 ZIPLIST,大对象考虑分拆
  4. 监控内存redis-cli --bigkeysMEMORY USAGE
  5. 合理配置
 
# redis.conf
hash-max-ziplist-entries 512    # 元素数量限制
hash-max-ziplist-value 64       # 单个value大小限制
 

总结

这个知识点体现了:

  • 知其然更要知其所以然:不只是会用命令,还要懂底层实现
  • 性能优化源于细节:数据结构的选择直接影响内存、网络、CPU
  • Redis 哲学:用合适的数据结构做合适的事

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询