玩转Redis-Lua脚本入门到实战-树形结构存储及查询

  《玩转Redis》系列文章 by zxiaofan主要讲述Redis的基础及中高级应用,穿插企业实战案例。本文是《玩转Redis》系列第【16】篇,最新系列文章请前往 公众号 “zxiaofan”(点我点我)查看,或 百度搜索 “玩转Redis zxiaofan”(点我点我)即可。

本文关键字:玩转Redis、Lua脚本入门到实战、树形结构、树形结构的解决方案、“邻接表”、“路径枚举”、查找部门的所有上级部门

往期精选《玩转Redis-干掉钉子户-没有设置过期时间的key》

大纲

  • 树形结构的常见场景及解决方案
    • 树形结构的常见场景
    • 树形结构的解决方案:“邻接表”、“路径枚举”
  • Redis下树形结构的存储
  • Redis中如何使用Lua脚本
    • Redis支持的Lua脚本命令简述
    • Redis支持的Lua脚本命令详解
    • Redis如何调试Lua脚本
  • Lua脚本实战Redis树形结构
  • Redis中使用Lua脚本的注意事项

前言:

  在toB公司负责中台业务,众多企业的部门关系是树形结构,前段时间有业务诉求是“在大数据量下高效查询指定部门的所有上级部门,企业的部门树形关系可能随时变更”。在MySQL的基础上遂想到了利用Redis缓存树形结构并实现高效查询。

1、树形结构的常见场景及解决方案

1.1、树形结构的常见场景

  生活中我们有很多树形结构的数据场景,比如:

  • 国家行政区域编码;
  • 企业组织架构;

国家行政区域编码
企业组织架构

  树形结构数据的特征是 有明显的所属关系,比如 “行政区域编码”示例中“朝阳区”属于“北京市”,“组织架构”示例中“社交产品部”属于“产品中心”。

1.2、树形结构的解决方案

1.2.1、邻接表

  业界最常使用的方案恐怕就是“邻接表”了,简而言之,“邻接表”的每条数据都存储了“上级数据ID”。

数据ID 数据名称 上级数据ID
cpzx 产品中心 总公司ID
sjcpb 社交产品部 cpzx
bgcpb 办公产品部 cpzx
bgcpyz 办公产品一组 bgcpb
bgcpez 办公产品二组 bgcpb

“邻接表”的优点:

  • 添加数据高效;
  • 修改数据的上级高效;
  • 删除叶子节点数据高效;

“邻接表”的缺点:

  • 删除中间节点需移动其子节点;
  • 查询节点的所有叶子节点、所有父节点复杂;
    • 这里指MySQL,Oracle是支持递归查询的;

1.2.2、路径枚举

  另一个比较常用的方案就是“路径枚举”了,其核心思想是,每条数据都有字段存储了其所有的上级信息。

数据ID 数据名称 路径
1 中国 1
11 北京市 1,11
110105 朝阳区 1,11,110105
51 四川省 1,51
5101 成都市 1,51,5101
510104 锦江区 1,51,5101,510104

“路径枚举”的优点:

  • 查询节点的所有父节点高效;
    • select 路径 where 数据ID = ‘节点ID’;
  • 查询节点的所有子节点高效;
    • select 数据ID where 路径 like ‘1,51%’;

“路径枚举”的缺点:

  • 依赖复杂逻辑维护路径;
  • 路径长度可能失控;
  • 非叶子节点删除或变更上级节点时,所有子节点都将变动;

  除了“邻接表”、“路径枚举”,还有存储子孙节点范围的“嵌套集”、维护独立数据表存储所有子孙节点关系的“闭包表”等方案用于存储树形结构数据。由于不是本文重点,此处不再赘述。

  在实际的生产方案中,我们也不用拘泥于以上某个方案,适当的将方案整合使用,往往事半功倍。比如我们的生产系统,就有将“邻接表”、“路径枚举”方案混合使用的场景,综合性能也相当出色。

不管黑猫白猫,抓到耗子就是好猫。

2、Redis下树形结构的存储

  在阐述存储方案前,我们先详细梳理下现有的业务场景,技术都是为业务服务的

  toB系统,系统中有众多企业(company1 ~ company666),每个企业都有自己的部门树。示例:某公司 A0 下有 B1 ~ B50 这 50 个一级部门;每个一级部门 下 又有若干 个二级部门(比如 B1 下 有 CB1-1 ~ CB1-30 这 30 个二级部门,B3 下 有 CB3-1 ~ CB3-40 这 40 个二级部门);同理,每个二级部门下又有若干个三级部门。对于大企业而言,部门达到几千上万。此外需要注意的是,企业的部门信息是可能随时变动的。而现在我们的诉求是:查询 第N级某个部门的所有上级部门信息。

企业组织架构树
  在MySQL场景下,我们可以“邻接表”或者“路径枚举”方案,甚至于像上面提及的需要做方案混合。对于诉求“查询节点的所有父节点”,通过先前的方案分析,“路径枚举”是较优的方案。但如果当QPS很高,需要进一步提升性能呢,除了提升DB的性能响应外,我们是否还有其他的出路?

  高性能的Redis进入了我们的视野,那么如何使用Redis完成树型结构的存储呢?

  此处我们使用的Redis数据结构是 Hash,Redis的key为企业ID(depttree:企业ID),field 为 部门ID,field 对应的value是 该部门ID对应的上级部门ID。示例如下:

Redis下部门树的存储

业务逻辑:

  • 查询所有父部门时,先从缓存中查询,缓存缺失时从DB查询并更新到Redis;
  • 部门关系变更时,则删除Redis缓存;
  • 部门删除时,则删除Redis缓存;
  • Redis中的数据存储采用的是“邻接表”的方式;
  • 由于任意部门的父部门都可能变动,Redis中的数据存储不采用“路径枚举”方案;

需要注意的是:

  • 更新Redis时采用批量更新提升性能,HMSET key field value [field value …];
  • 实际生产中,我们采用的是二级缓存,方案更复杂,此处不展开;
HMSET depttree:企业001 B1 A0 B2 A0 B3 A0 CB1-1 B1

3、Redis中如何使用Lua脚本

  在上一节中部门关系数据已经存到Redis了,从hash的结构看,无法一次性查询指定部门的所有上级部门,所以我们需要使用到 Lua 脚本。正式实战之前,我们先学习下Redis中如何使用 Lua 脚本。

  Redis 2.6.0 版本开始支持 Lua 脚本。Redis中使用 Lua 脚本应直接提供程序体,不需要也不能定义一个函数。下面我们来开始Redis Lua脚本的入门吧。

3.1、Redis支持的Lua脚本命令简述

命令 功能 参数
EVAL 执行Lua脚本 EVAL script numkeys key [key …] arg [arg …]
SCRIPT LOAD 将脚本内容导入Redis的脚本缓存 SCRIPT LOAD script
EVALSHA 通过导入脚本的SHA1摘要值执行脚本 EVALSHA sha1 numkeys key [key …] arg [arg …]
SCRIPT EXISTS 判断指定SHA1摘要值的脚本是否存在 SCRIPT EXISTS sha1 [sha1 …]
SCRIPT FLUSH 清空所有的Lua脚本缓存 SCRIPT FLUSH
SCRIPT KILL 杀死正在执行的没有写操作的Lua脚本 SCRIPT KILL
SCRIPT DEBUG 设置Lua脚本debug模式 SCRIPT DEBUG YES | SYNC | NO

3.2、Redis支持的Lua脚本命令详解

3.2.1、EVAL

  • 参数
    • EVAL script numkeys key [key …] arg [arg …]
  • 功能
    • 执行Lua脚本
  • 可用版本
    • 2.6.0
  • 时间复杂度
    • 取决于执行的脚本;
  • 参数说明
    • script:脚本内容;
    • numkeys:key的个数;
    • [key …]:key值,个数必须和numkeys匹配;
    • [arg …]:附加参数,[key …]后的均为附加参数,个数不固定;
  • 返回值
    • 脚本执行结果;
  • 备注
    • Lua 脚本中通过 KEYS[1]、KEYS[2]、ARGV[1] 获取传入的参数;
127.0.0.1:6379> eval "return redis.call('set','公众号','zxiaofan')" 0
OK

127.0.0.1:6379> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK

3.2.2、SCRIPT LOAD

  • 参数
    • SCRIPT LOAD script
  • 功能
    • 将脚本内容导入Redis的脚本缓存;
  • 可用版本
    • 2.6.0
  • 时间复杂度
    • O(N),N取决于脚本字节长度;
  • 参数说明
    • script:脚本内容;
  • 返回值
    • 导入脚本的SHA1摘要值;
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值