尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Redis 数据结构底层与 Hash 优于 JSON 的工程实践

Redis 数据结构底层与 Hash 优于 JSON 的工程实践
📅 发布时间:2026/6/19 8:04:47

Redis 数据结构底层与 Hash 优于 JSON 的工程实践


一、Redis 对象模型与编码机制

  • Redis 对外提供5 种数据类型:String、List、Hash、Set、Sorted Set。每个键值对在内部由一个redisObject表示,包含type(类型)与encoding(编码)两个关键字段;同一类型在不同场景下可切换不同编码,从而在性能与内存之间取得平衡。常见编码有:RAW/INT/EMBSTR(String)、LISTPACK/HASH(Hash)、INTSET/HASH(Set)、LISTPACK/SKIPLIST(Sorted Set)、QUICKLIST(List)。例如:String 在值为整数且较短时可用INT/EMBSTR,否则用RAW;Hash/Set/ZSet 在小数据量时可用紧凑编码(LISTPACK/INTSET),大数据量时转为HASH/SKIPLIST。这种设计让 Redis 能在不同数据规模下自动优化存储与访问路径。Redis 7 之后用LISTPACK替代了ZIPLIST,进一步解决级联更新问题并提升稳定性。

二、各数据类型的底层实现与差异

数据类型常见底层编码关键特性典型触发条件与阈值
StringRAW / INT / EMBSTRO(1)取长;二进制安全;EMBSTR 与对象头一次分配更省时INT:值为可解析的 long;EMBSTR:短字符串(如 ≤44字节,版本差异);否则 RAW
ListQUICKLIST(Redis 3.2+)双向链表 + 分段ziplist,兼顾内存与随机访问历史:ziplist 小对象;现由 quicklist 统一承载
HashLISTPACK / HASH小对象紧凑;大对象随机访问快同时满足:字段数 ≤hash-max-listpack-entries(默认 512)且 字段/值长度 ≤hash-max-listpack-value(默认 64 字节)时用 LISTPACK,否则转 HASH
SetINTSET / HASH整数集合更省内存;通用元素用 HASH同时满足:元素全为整数且数量 ≤set-max-intset-entries(默认 512)用 INTSET,否则转 HASH
Sorted SetLISTPACK / SKIPLIST跳跃表支持O(logN)范围/排名操作同时满足:元素数 ≤zset-max-listpack-entries(默认 128)且 成员长度 ≤zset-max-listpack-value(默认 64 字节)用 LISTPACK,否则转 SKIPLIST
  • 关键实现要点
    • SDS(简单动态字符串):记录len/free,实现O(1)取长、二进制安全、预分配与惰性释放,避免缓冲区溢出。
    • LISTPACK:连续内存、字段紧凑,适合小对象;Redis 7 后替代 ZIPLIST,避免级联更新。
    • HASH/INTSET/SKIPLIST/QUICKLIST:标准哈希表、整数集合、跳跃表、分块链表,分别面向通用、整数、排序、列表场景优化。

三、为什么经常说用 Hash 比用 JSON 好

  • 语义与操作粒度更匹配对象
    • 对象通常具有多个属性(如name、age、loginCount)。用Hash可按字段独立HGET/HSET/HINCRBY,无需反序列化整个对象;而JSON 字符串要更新某个字段必须读-改-写整串,既繁琐又易产生并发写覆盖问题。
  • 性能与网络开销
    • 字段级读写避免了序列化/反序列化与整串拷贝,CPU 与网络字节量都更低;对计数器、状态位等高频更新尤其明显(如HINCRBY原子自增)。
  • 内存与编码优化空间更大
    • 小对象时Hash可用LISTPACK紧凑存储,节省内存;当数据变大自动转为HASH,保持访问性能。JSON 始终是字符串,缺少这种“小对象省内存”的弹性。
  • 部分更新与局部读取
    • 业务常只需读取/修改少数字段(如展示层只要nickname、avatar)。用Hash可HMGET只取所需字段;JSON 往往被迫GET整串再解析,浪费带宽与 CPU。
  • 原子性与并发控制
    • Hash提供字段级原子操作(如HSETNX/HINCRBY),更易编写无锁/少锁的并发逻辑;JSON 在 Redis 层面缺少字段级原子指令,通常需要Lua脚本才能保证一致性。

四、代码示例对比:JSON 与 Hash 的读写与更新

  • 场景:维护用户资料与计数器(昵称、年龄、登录次数、余额)
    1. JSON 方式(String 存序列化对象)
// 写Useru=newUser("Alice",25,0,newBigDecimal("99.50"));jedis.set("user:1001",newObjectMapper().writeValueAsString(u));// 读Stringjson=jedis.get("user:1001");Useru2=newObjectMapper().readValue(json,User.class);// 更新(读改写,存在并发覆盖风险)u2.setLoginCount(u2.getLoginCount()+1);u2.setBalance(u2.getBalance().add(newBigDecimal("10.00")));jedis.set("user:1001",newObjectMapper().writeValueAsString(u2));
    1. Hash 方式(字段级存取)
// 写(可一次设置多个字段)jedis.hset("user:1001","name","Alice");jedis.hset("user:1001","age","25");jedis.hincrBy("user:1001","loginCount",1);jedis.hincrByFloat("user:1001","balance",10.00);// 读(只取需要的字段)Stringname=jedis.hget("user:1001","name");LongloginCount=Long.valueOf(jedis.hget("user:1001","loginCount"));// 批量取List<String>fields=Arrays.asList("name","age","balance");Map<String,String>profile=jedis.hmget("user:1001",fields.toArray(newString[0]));
    1. 并发安全更新(Hash 原子指令)
// 仅当余额充足时扣款(原子性由 Redis 保证)Stringlua="local bal = tonumber(redis.call('HGET', KEYS[1], 'balance')) "+"if bal >= tonumber(ARGV[1]) then "+" redis.call('HINCRBYFLOAT', KEYS[1], 'balance', -tonumber(ARGV[1])) "+" return 1 "+"else "+" return 0 "+"end";Longok=(Long)jedis.eval(lua,1,"user:1001","5.00");
    1. 大对象遍历(避免阻塞)
// 使用 HSCAN 分批遍历,避免一次性 HGETALL 造成阻塞ScanResult<Map.Entry<String,String>>scan;Stringcursor="0";do{scan=jedis.hscan("user:1001",cursor,newScanParams().count(50));for(Map.Entry<String,String>e:scan.getResult()){// 处理字段}cursor=scan.getCursor();}while(!"0".equals(cursor));
    1. 何时仍用 JSON(或 RedisJSON)
# 需要路径查询/局部更新且希望服务端完成解析与合并JSON.SET user:1001 $'{"name":"Alice","profile":{"age":25}}'JSON.GET user:1001 $.profile.age JSON.SET user:1001 $.profile.age26
  • 说明:Redis 7 起Hash 使用 LISTPACK替代 ZIPLIST;小对象更省内存,大对象自动转为 HASH。JSON 适合整对象快照或需要JSONPath的场景;若对象字段多且频繁局部更新,优先考虑Hash或RedisJSON。

五、工程实践与避坑清单

  • 合理控制Hash 字段数量与单字段大小:避免把“大对象”塞进一个 Hash;大对象可拆分为多个子 Hash(如user:{uid}:base、user:{uid}:ext)。
  • 小对象争取命中LISTPACK:理解并合理设置hash-max-listpack-entries/value,在内存与性能间取平衡;数据增长后自动转HASH无需人工干预。
  • 避免对大 Hash 使用HGETALL:改用HSCAN分批遍历,降低阻塞与网络抖动风险。
  • 需要过期控制时记住:EXPIRE 作用于 key,不能对单个 field 设置 TTL;若业务需要字段级过期,考虑拆分 key 或用 RedisJSON 的过期策略。
  • 高并发更新同一对象时,优先使用Hash 的原子指令(如HINCRBY/HSETNX);若用 JSON,请配合Lua脚本保证读改写一致性。
  • 大 Key 与热 Key 治理:字段过多考虑Hash 分片;热点数据结合本地缓存(Caffeine)+ Redis + MQ 失效广播;必要时用Bloom Filter防穿透。

相关新闻

  • 编码器测速思路,以及如何进行测速,速度调整
  • Router_编程式路由
  • 别再“+”到天亮!String.format 一键拯救Java字符串拼接,高可读+可维护神操作

最新新闻

  • 兰州瓷砖空鼓松动修复:本地口碑好的 5 家正规靠谱门店推荐 | 卫生间 / 客厅空鼓专修(2026 最新) - 金修达家庭维修
  • AI论文写作工具的合规使用指南:如何让AI生成内容通过严格学术审查
  • 在海口出黄金别乱选,走访多家实体店,避开压价扣费陷阱 - 奢侈品回收评测
  • 南京黄金贵金属回收宝藏店铺推荐 | 闲置变现不踩坑指南 - 清奢黄金上门回收
  • 终极指南:Elasticvue - 5分钟掌握Elasticsearch可视化管理
  • 想快速周转资金?沈阳黄金回收上门交易完整流程详解 - 奢侈品回收评测

日新闻

  • 5分钟掌握Python进化算法:Geatpy高性能优化工具完全指南
  • Microchip 24AA044 EEPROM选型与应用全指南:从参数解析到实战编程
  • 华为的鸿蒙到底有多牛?为什么称作遥遥领先?

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号