nested set model應用系列文章-基於後根跳躍遍歷的規則匹配算法

紅豬,餓了麼資深PHP,專一後端搬磚node

前言

本篇文章是《nested set model應用系列文章》的第一篇文章,更多nested set model應用相關的文章,歡迎持續關注咱們的專欄哦~mysql

名字解釋

後跟跳躍遍歷,是指在樹結構的後根遍歷過程當中,跳過那些對計算結果再也不起做用的節點,讓遍歷速度達到最快的一種遍歷方式。能夠在涉及到規則匹配的系統中使用。redis

研發背景

老的廣告運營位的設計存在一些問題:算法

  • 數據結構化很糟糕,沒有明顯的規律,容易受規則的影響
  • 爲知足需求,業務代碼中也會寫死匹配邏輯,擴展性差,耦合性高
  • 匹配性能比較差

須要設計一套新的算法,使廣告運營位支持任意規則的可配(匹配性能要好)。sql

結構與特性

樹結構,使用Nested set model存mysql,根結點存儲規則的做用對象(例如運營廣告位,如下簡稱對象),,子節點存儲規則,規則類型相同的規則處於同一個直線分支,這樣限制樹結構,使根節點外的子節點最多隻有一個子節點,相似這種: json

tree

每一個節點使用左值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),用於存儲業務數據,好比運營廣告位,則存儲運營廣告位的內容和它下面的限定規則。ui

  • node(topic),當節點存運營廣告位內容的時候,node(topic)的值用來標識運營廣告位的位置,存規則的時候,用於標識規則的類型。例如,城市規則,用於限制運營廣告位生效的城市;版本號規則,用於限制登錄用的客戶端版本,年齡規則,用於限制登錄用戶的年齡,等等任意的規則
  • node(value),當節點存儲運營廣告位的時候,node(value)存儲廣告位的內容,例如,文字連接的文本、連接,圖片連接的圖片地址,並以json健值對存儲,存規則的時候,該屬性存儲規則的值
  • node(op),僅當存儲規則的時候用於表示規則的計算類型

設計的運算類型一共十種:

運算 備註
eq 等於
neq 不等於
gt 大於
egt 大於等於
lt 小於
elt 小於等於
btw 區間內
nbtw 區間外
in 包含
nin 不包含

in量超過總量一半,推薦使用nin)

各類規則、操做組合最大支持的不一樣配置數達(任意規則可配):

$$\sum_{i=1}^m C_m^i10^i$$

其中,m爲規則類型數,例如城市規則、版本號規則,用戶的年齡規則等等(規則名不受限制,存什麼規則名就是是什麼規則),10是十種運算類型。

匹配過程

後跟遍歷的順序讀取運營廣告位規則數據列表後:

圖片標題

注意當op爲in、nin時,value存儲的只是redis指針,並不是規則的真實值。這裏也能夠用mysql存儲指針指向的真實值,選擇redis主要是使用了redis能夠設置過時時間跟活動截止時間一致,以達到過時數據的自動清理。

拉到列表以後,最多隻需遍歷一次就能夠算出知足規則的全部對象。遍歷過程當中如遇到規則不匹配,會產生跳躍,即直接忽略該對象的其餘規則的匹配過程,因此速度很是快。

相同的規則能夠有多條,他們之間是或的關係,不一樣規則之間是與的關係。匹配時,相同規則的多條規則(這裏稱之爲同組規則)只要匹配到一條就會跳過同組的其餘規則去匹配不一樣組規則的其餘規則,直到全部組的規則所有配成功,對象有效;若是遇到任一組規則匹配失敗,則跳過剩下的全部組規則,對象無效。

因爲同一個廣告位只能顯示一個對象,在遍歷匹配的過程當中若是同一個廣告位匹配到多個對象,後匹配到的會覆蓋以前的(列表按照加入的時間升 序排列),所以,最終只有一個對象生效。

最壞狀況下匹配的複雜度:log(n)

衝突解決

下圖A表示能看到廣告A的用戶集合,B表示能看到廣告B的用戶集合

圖片標題

集合A包含於集合B時,相同時間段內,若是仍但願廣告A和廣告B都能被用戶看到,這是就須要解決衝突。

圖片標題

如上,左圖中,集合B徹底覆蓋了集合A,致使集合A的用戶看不到廣告A而看到廣告B,這時應將B廣告先於A廣告配置,這樣集合A的用戶能正常看到廣告A,集合B中除去集合A之外的用戶能看到B廣告,衝突就解決了。

當A、B不是包含於的關係,而只是存在交集,配置的前後對結果是有必定影響,但不存在衝突,各發布方溝通協調決定誰先誰後。

超過兩個廣告的衝突解決依此類推。充分發揮你的想象力,沒有配不了的,只有你沒想到。

小結

本篇文章旨分享我的在實際工做過程當中處理相似應用場景下的問題的思路和方向,具體代碼的實現只是一種表達方式,且因人而異,相關的代碼並無貼出來。像樹結構如何生成,以及如何作到後根遍歷能夠參考wiki nested set model,然後根遍歷過程當中跳躍的原理本文中已經作了比較詳細的文字描述。若是仍是以爲有須要的,能夠貼在評論的回覆裏面。

參考文獻

Nested set model




閱讀博客還不過癮?

歡迎你們掃二維碼經過添加羣助手,加入交流羣,討論和博客有關的技術問題,還能夠和博主有更多互動

博客轉載、線下活動及合做等問題請郵件至 shadowfly_zyl@hotmail.com 進行溝通

相關文章
相關標籤/搜索