1. 程式人生 > >nested set model應用系列文章-基於後根跳躍遍歷的規則匹配演算法

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

曹小清,餓了麼資深php工程師。曾就職於學霸君,先後負責學霸君app的後端、題庫錄入,期間首次將自主研發的後根跳躍遍歷演算法用於學霸君的廣告運營系統。專注於後端開發、架構設計,效能調優

前言

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

名字解釋

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

研發背景

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

  • 資料結構化很糟糕,沒有明顯的規律,容易受規則的影響
  • 為滿足需求,業務程式碼中也會寫死匹配邏輯,擴充套件性差,耦合性高
  • 匹配效能比較差

需要設計一套新的演算法,使廣告運營位支援任意規則的可配(匹配效能要好)。

結構與特性

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

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),用於儲存業務資料,比如運營廣告位,則儲存運營廣告位的內容和它下面的限定規則。

  • 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




閱讀部落格還不過癮?

歡迎大家掃二維碼通過新增群助手,加入交流群,討論和部落格有關的技術問題,還可以和博主有更多互動

部落格轉載、線下活動及合作等問題請郵件至 [email protected] 進行溝通