nested set model应用系列文章-基于后根跳跃遍历的规则匹配算法

红猪,饿了么资深PHP,专注后端搬砖

前言

本篇文章是《nested set model应用系列文章》的第一篇文章,更多nested set model应用相关的文章,欢迎持续关注我们的专栏哦~

名字解释

后跟跳跃遍历,是指在树结构的后根遍历过程中,跳过那些对计算结果不再起作用的节点,让遍历速度达到最快的一种遍历方式。可以在涉及到规则匹配的系统中使用。

研发背景

老的广告运营位的设计存在一些问题:

  • 数据结构化很糟糕,没有明显的规律,容易受规则的影响
  • 为满足需求,业务代码中也会写死匹配逻辑,扩展性差,耦合性高
  • 匹配性能比较差

需要设计一套新的算法,使广告运营位支持任意规则的可配(匹配性能要好)。

结构与特性

树结构,使用Nested set model存mysql,根结点存储规则的作用对象(例如运营广告位,以下简称对象),,子节点存储规则,规则类型相同的规则处于同一个直线分支,这样限制树结构,使根节点外的子节点最多只有一个子节点,类似这种:

《nested set model应用系列文章-基于后根跳跃遍历的规则匹配算法》

每个节点使用左值node(lft)、右值node(rgt)、深度node(depth)来表示树型结构,这种改进后的结构有以下特性:

  • 任意节点node的子节点数:(node(rgt) – node(lft) – 1) / 2
  • 任意节点所处直线分之的节点总数(根节点除外):(node(rgt) – node(lft) – 1) / 2 + node(depth) – 1(根节点深度是1,从0开始不用减1)
  • 叶子节点:node(rgt) – node(lft) = 1

上面左值、右值的计算请参考Nested set model,遍历的时候会依据这些特性进行跳跃

数据承载

对象和它的规则按照树结构存储于同一张表中,建议表结构设计如下:

CREATE TABLE `demo` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`gid` int(10) unsigned NOT NULL,//用于表示不同的运营广告位,同一个运营广告位,gid相同
`pid` int(10) unsigned NOT NULL,//辅助阅读字段,不参与计算
`topic` varchar(255) NOT NULL DEFAULT '',//规则名OR对象名
`value` blob NOT NULL,//规则的值OR对象的值
`op` varchar(255) NOT NULL DEFAULT '',//规则运算符
`lft` int(10) unsigned NOT NULL,
`rgt` int(10) unsigned NOT NULL,
`depth` int(10) unsigned NOT NULL,
`add_time` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
);
复制代码

上节结构属性之外,还有三个关键属性:node(topic)、node(value)、node(op),用于存储业务数据,比如运营广告位,则存储运营广告位的内容和它下面的限定规则。

  • node(topic),当节点存运营广告位内容的时候,node(topic)的值用来标识运营广告位的位置,存规则的时候,用于标识规则的类型。例如,城市规则,用于限制运营广告位生效的城市;版本号规则,用于限制登陆用的客户端版本,年龄规则,用于限制登陆用户的年龄,等等任意的规则
  • node(value),当节点存储运营广告位的时候,node(value)存储广告位的内容,例如,文字链接的文本、链接,图片链接的图片地址,并以json健值对存储,存规则的时候,该属性存储规则的值
  • node(op),仅当存储规则的时候用于表示规则的计算类型

设计的运算类型一共十种:

运算备注
eq等于
neq不等于
gt大于
egt大于等于
lt小于
elt小于等于
btw区间内
nbtw区间外
in包含
nin不包含

in量超过总量一半,推荐使用nin)

各种规则、操作组合最大支持的不同配置数达(任意规则可配):

《nested set model应用系列文章-基于后根跳跃遍历的规则匹配算法》

其中,m为规则类型数,例如城市规则、版本号规则,用户的年龄规则等等(规则名不受限制,存什么规则名就是是什么规则),10是十种运算类型。

匹配过程

后跟遍历的顺序读取运营广告位规则数据列表后:

《nested set model应用系列文章-基于后根跳跃遍历的规则匹配算法》

注意当op为in、nin时,value存储的只是redis指针,并非规则的真实值。这里也可以用mysql存储指针指向的真实值,选择redis主要是使用了redis可以设置过期时间跟活动截止时间一致,以达到过期数据的自动清理。

拉到列表之后,最多只需遍历一次就可以算出满足规则的所有对象。遍历过程中如遇到规则不匹配,会产生跳跃,即直接忽略该对象的其他规则的匹配过程,所以速度非常快。

相同的规则可以有多条,他们之间是或的关系,不同规则之间是与的关系。匹配时,相同规则的多条规则(这里称之为同组规则)只要匹配到一条就会跳过同组的其他规则去匹配不同组规则的其他规则,直到所有组的规则全部配成功,对象有效;如果遇到任一组规则匹配失败,则跳过剩下的所有组规则,对象无效。

由于同一个广告位只能显示一个对象,在遍历匹配的过程中如果同一个广告位匹配到多个对象,后匹配到的会覆盖之前的(列表按照加入的时间升 序排列),因此,最终只有一个对象生效。

最坏情况下匹配的复杂度:log(n)

冲突解决

下图A表示能看到广告A的用户集合,B表示能看到广告B的用户集合

《nested set model应用系列文章-基于后根跳跃遍历的规则匹配算法》

集合A包含于集合B时,相同时间段内,如果仍希望广告A和广告B都能被用户看到,这是就需要解决冲突。

《nested set model应用系列文章-基于后根跳跃遍历的规则匹配算法》

如上,左图中,集合B完全覆盖了集合A,导致集合A的用户看不到广告A而看到广告B,这时应将B广告先于A广告配置,这样集合A的用户能正常看到广告A,集合B中除去集合A以外的用户能看到B广告,冲突就解决了。

当A、B不是包含于的关系,而只是存在交集,配置的先后对结果是有一定影响,但不存在冲突,各发布方沟通协调决定谁先谁后。

超过两个广告的冲突解决依此类推。充分发挥你的想象力,没有配不了的,只有你没想到。

小结

本篇文章旨分享个人在实际工作过程中处理类似应用场景下的问题的思路和方向,具体代码的实现只是一种表达方式,且因人而异,相关的代码并没有贴出来。像树结构如何生成,以及如何做到后根遍历可以参考wiki nested set model,而后根遍历过程中跳跃的原理本文中已经做了比较详细的文字描述。如果还是觉得有需要的,可以贴在评论的回复里面。

参考文献

Nested set model

阅读博客还不过瘾?

欢迎大家扫二维码通过添加群助手,加入交流群,讨论和博客有关的技术问题,还可以和博主有更多互动

《nested set model应用系列文章-基于后根跳跃遍历的规则匹配算法》

博客转载、线下活动及合作等问题请邮件至 shadowfly_zyl@hotmail.com 进行沟通

    原文作者:算法小白
    原文地址: https://juejin.im/post/5bd673ca518825287847a840
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞