成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

用規則引擎讓你一天上線十個需求

開發 前端
如果是本號老讀者,可能知道我是做數據系統的,作為一個在線數據服務組,我們這邊承接的需求是小而多的。我在一家打車公司上班,運營大佬們認為不同用戶在不同場景下有不同打車需求,設計出來很多子品類。

各位讀者朋友大家好,我是薯條,好久沒更文章,不知還有多少讀者記得這個號,這篇文章寫的有點精分,如果你有耐心看完本文,可以翻翻留言區,我會發個新年紅包。

業務背景

如果是本號老讀者,可能知道我是做數據系統的,作為一個在線數據服務組,我們這邊承接的需求是小而多的。我在一家打車公司上班,運營大佬們認為不同用戶在不同場景下有不同打車需求,設計出來很多子品類。于是我們組會承接這樣一類需求:計算用戶不同品類的各種實時單量,如:快車呼單量、拼車完單量。

這樣的需求,一般處理流程是這樣的:

描述一下這個圖:用戶在訂單流轉狀態關鍵節點發生動作時,系統會發一個MQ消息讓供其他系統消費。其他系統通過一個明確的據口徑判斷這條msg是否符合當前業務邏輯,進而存db或是丟棄。比如一個需求要計算:拼車完單量,一個靠譜的拼車rd告訴你口徑是:

  1. If aa.bb.cc == 1  // 說明是多車型發單 
  2.   Unmarshal(bb.cc.ee) 
  3.   看type是否為 4  
  4. else  // 單車型發單 
  5.  Unmarshal(bb.cc.ff) 
  6.   看type是否為 4  
  7. (type = 4 的是拼車) 

你對著這個口徑,訂閱mq,寫數據提取和訂單判斷的邏輯,整個流程寫代碼1小時,自測一小時,由于你們機器太多,上線花了1整天,整體研發效率還行。

第二天,產品又給你提了個需求,想計算拼車的發單量,你又去找對應業務線的開發同學尋求一個取數口徑,然后重復上面的過程。

第三天,產品上線了,效果不錯,數據漲了3個點,老板非常開心,在周會上讓PM講兩句他的心路歷程,PM同學重點感謝了老板的栽培,然后輕描淡寫的說了產品的底層邏輯和關鍵抓手。而他的幾個精明的同事都get到了抓手是你,于是連夜趕PRD,要求你這個抓手把他們負責品類的各種實時單量全給抓出來。

第四天,你崩潰了,因為你收到4個PM并行的8個單量需求,正當你奮筆疾書再次準備重復上述流程時,你睿智的老板告訴你這樣做有不妥之處:來一個坑填一個蘿卜是小農時代的做法,現在都21世紀了,時代變了,讓你想一種通用解決方案,讓系統走向工業時代!

你似懂非懂點了點頭,查了各種CSDN、博客園、知乎、github,又在技術交流群各種@群里大佬有沒有遇到這種場景。一番折騰后終于有了頭緒,于是你高興的向老板匯報:老板,我懂了,這個場景可以用JPATH + Expression Eval來解決!這樣一來,再來新的需求只需要寫在db里插入倆表達式就可以了,20個需求提過來也不用怕。

你的老板微笑著點了點頭,看了一眼自己手上的勞力士,有意無意的晃了晃,說:小伙子很上道,自己也琢磨出解法了,趕緊設計方案,爭取本周上線,盡快拿到業務結果,到時候升職加薪少不了你的!

實現方案

這個系統的核心需求有兩點:

  1. 數據提取
  2. 規則判斷

數據提取即ETL,把mq的msg中關鍵信息提取出來,提取之后可能還需要簡單處理一下(比如msg中事件時間是timestamp,你想轉化為RFC3339格式) ,這里可以用JPATH 做數據提取 (如果你寫過爬蟲,一定知道用xpath去提取HTML中的node消息,jpath就是json數據的提取規則)。配置一個ETL rule,如圖所示:

然后是數據規則判斷,即題目中提到的規則引擎,我們這里使用 開源庫govaluate,比如上面拼車完單的例子,我們可以配置這樣的規則:

  1. cc == 1 ? ( in(4, ee)? 1:0 ) : ( ff ==4 ? 1:0) 

govaluate會把這個表達式構建出一顆ast,然后輸入參數進行求值(是不是回憶起來了編譯原理?)。接下來讓我們研究一下這個庫~

govaluate介紹與使用注意

govaluate支持對C風格的算數/字符串的表達式進行求值。比如這些例子(例子來源于evaluation_test.go):

  1. 1. 100 ^ (23 * (2 | 5)) 
  2. 2. 5 < 10 && 1 < 5 
  3. 3. (foo == true) || (bar == true) // foo、bar為變量 
  4. 4. theft && period == 24 ? 60     // theft、period為變量 

這個庫幾乎支持你能想象到的任何表達式,有興趣可以去這個test文件。除此之外它支持拓展UDF, 你可以自己寫一些函數支持你的定制業務邏輯,它還支持執行類方法,更多信息可以看README。

當我們需要使用它時,只需要這幾行代碼

  1. expression, err := govaluate.NewEvaluableExpression("foo > 0"); 
  2.  
  3. parameters := make(map[string]interface{}, 8) 
  4. parameters["foo"] = -1; 
  5.  
  6. result, err := expression.Evaluate(parameters); 
  7. // result is now set to "false", the bool value. 

通過這個demo我們可以看到它的api被設計成了兩步, 第一步NewEvaluableExpression的功能主要是把表達式拓展為一顆AST,Evaluate的主要功能是把用戶參數填入ast求值。。舉個例子:比如1 + foo + 4 * boo這個表達式,在兩個階段分別做的事情是這樣:

那么生產項目代碼中直接把這兩步抄進去就可以了嗎?顯然不是。通過觀察就可以發現, 第一步構造ast依賴的表達式其實是可以預先確定的,且表達式一般不會變化,沒有必要用戶每次傳一個api就構造一顆ast然后求值。可以把表達式存入db,在項目啟動or更新配置時加載到內存中, 比如搞一個map[string]*EvaluableExpression, 把不同表達式的ast進行cache,這樣用戶每次請求時只需遍歷ast進行求值。

預編譯所帶來的收益很顯著,尤其是在你表達式比較復雜的情況下。我對foo > 2? 1:0這個表達式分別做了現編譯和預編譯的benchmark,結果如下:

現編譯

(現編譯 構建ast占用62.3%的cpu開銷,而eval只占2%)

預編譯

(預編譯省掉了構建ast的成本,節約了大量cpu資源) 建議大家如果使用這個庫,有條件要用預編譯版本。

govaluate 原理

看起來govaluate很有意思, 接下來讓我們挖一下它的源碼。首先來看第一階段,把表達式拓展為AST時的邏輯,我簡單畫了一張圖:

下面以1 + foo + 4 * boo為例,parserToken后,我們可以得到一堆token:

checkBalance沒啥好說的,核心功能就看小括號是否成對出現:

而checkExpressionSyntax階段主要是check token之間是否符合預設規則,核心是這個函數:

這個函數會check當前的token是否是上一個token的合法值,合法值是預設的,比如NUMERIC的合法值是后面這些:

接下來的 optimizeTokens 函數沒啥好說的,主要就是編譯一下正則。

比較有意思的是planStages這個步驟。planStages這個大步驟內部大概分成了planTokens、reorderStages、elideLiterals這三個小步驟,下面來一一介紹:

planTokens

這個函數寫的讓我大開眼界,首先它用func做不同運算符的優先級計算,原理是func接收struct作為參數,而參數中的next為這個函數連接的下一個優先級的func。

這個func優先級打印出來是這樣的:

有了運算符優先級之后,對于具體的節點,會繼續看節點類型,比如是func,accesser還是valueType,valueType的節點對于不同的詳細類型也有不同策略,比如數字節點會構建一個Node,而小括號節點會直接parser下一個token來構建優先級更高的樹。

對于不同的運算符,在這個函數鏈上會下沉構建出優先級比較高的節點,保證符合數學計算的規律。

reorderStages

這里主要把ast重排序,讓ast由普通tree變成avl tree,樹旋轉的代碼寫的特別騷氣,比如1 + foo + 4 * boo 這個表達式,planToken執行完后,會變成這樣一顆樹:

重排序的過程是把相同優先級的節點進行旋轉,第一步是交換左右節點:

第二步是LL左旋:

這樣就平衡了,一個非常騷氣的算法。

elideLiterals

這個步驟是看葉子節點是否為LITERAL,比如這棵樹:

在這個階段,各個子節點會進行dfs計算直接變成:

至此第一階段的邏輯梳理完畢。而第二個階段Evaluate的主要功能是把用戶參數填入ast,進行求值。這個過程比較簡單,本文不在贅述。

govaluate 不足

govaluate 看起來很美好,真的是這樣嗎?其實不然,這個項目最后一次commit是2017年,距今已經6年了。我們在使用期間也發現了很多小bug和代碼優美度欠缺的地方。下面來簡單列舉幾個:

弱類型

govaluate所有數字類型都是被解析為float64進行計算的,這么玩寫代碼爽了,但是當你用1+2+9做表達式時,可能會得到一個類型為fload64的interface{}結果。

函數限制

govaluate的函數有的返回值無法繼續做運算。比如這個case:

看起來沒有任何問題,但是執行會報錯: 

參數會去除轉義符

比如這段代碼: 

理論上結果應該含有轉義符,實際上結果是: 

實際上是這段代碼搞的鬼,代碼比較簡單,就不解釋了。

奇奇怪怪的代碼

  1. this關鍵字:這個就不舉例子了,這個庫里所有方法的接收者都是this,被官方建議熏陶過的我,看的我著實蛋疼...
  2. 雙重否定表肯定:token解析階段有這樣的代碼,不知道作者為啥要搞個雙重否定,我的話,會用一個isQuote代替。

govaluate改進

作為一個17年后就沒更新過的項目, 也不知道作者還會不會維護。業務發展是不等人,govaluate對于我們服務來說并不能滿足需求,很多時候用起來比較別扭,所以我基于我們的場景對于govaluate做了一些定制改造。我個人還是非常喜歡這個庫的,于是把代碼fork了一份,加了個eplus后綴,改造了上面那幾個匪夷所思的問題。并加了個比較定制化的feature:type promotion。

這個聽起來比較唬人, 其實就是支持更弱類型的表達式運算,比如我的庫支持:'2' -1, '4' * 3,要支持這種功能,核心需要改兩種地方:

  1. 第一種地方是typeCheck。比如subStage會check兩個字節點必須是float64類型, 我們要支持string operator num, 可以把typeCheck擴大為可以是chek node是否為 floatOrStr。
  2. 第二種地方是OpeatorOperate。前面我們把String類型也放進來讓它支持計算了,但是在go里str和float終究是無法計算的, 所以到了計算階段需要做一個type promotion,即把string類型轉化為數字類型之后再計算。

總結與反思

總結

govaluate在我心中還是有一些不完美的地方,我們這里用它也是因為項目初期就引入了這個庫,在大量的線上用例使用后要遷移這個庫成本巨大, 對于用的不爽的地方只能改了。如果讀者朋友有需求, 可以看一下市面上其他的表達式開源庫, 比如gval。當然,如果你的場景比較復雜, 需要很多if else 或者for循環,那簡單的規則引擎可能滿足不了你的需求,此時可以考慮內嵌個更完整的腳本庫或者嵌入lua, 不過這樣就更復雜了, 慎重考慮把這樣的東西直接放在db里面, 后期不好維護。

反思

govaluate這個庫對我有很多啟發,最主要就是表達式的預編譯可以節省大量CPU開銷,組內某個項目目前的運行方式是隨著請求現編譯,構建執行計劃dag圖,理論上如果能預編譯,請求到來只是對于對應param訪問存儲,可以節省大量CPU開銷。

跳脫出govaluate本身,我們系統選擇JPATH + Expr做數據提取和條件描述做需求,本質上是因為這邊的mq數據是JSON格式,JSON有一定的局限性,描述數據沒啥問題,但是描述條件就比較困難了,理論上如果用XML這種技能描述條件,又能描述數據的交互形式,那我們可能會構建一個完全不同的系統。

最后稍微打個廣告吧, 如果你也想使用govaluate,又有一些定制化的需求,歡迎star我的庫然后提個issue, 我想試著維護一個開源庫, 嘿嘿。

 

責任編輯:武曉燕 來源: 薯條的編程修養
相關推薦

2014-04-03 15:34:42

開放平臺

2023-05-24 10:24:56

代碼Python

2019-07-15 15:59:32

高維數據降維數據分析

2022-10-08 07:54:24

JavaScriptAPI代碼

2024-12-17 15:00:00

Python代碼

2015-05-11 10:39:19

2019-07-11 14:45:52

簡歷編程項目

2025-03-11 00:00:00

2025-06-25 10:02:55

2025-04-09 00:01:05

2023-05-16 06:50:50

prompt郵件語法

2009-03-03 16:50:52

需求分析軟件需求需求管理

2023-03-09 15:01:21

PythonVSCode程序員

2023-06-27 17:42:24

JavaScript編程語言

2024-01-12 07:32:35

數據科學Python庫項目

2025-04-30 05:58:20

2024-10-17 16:13:23

Shell開發運維

2022-07-07 09:19:24

JavaScript代碼樣式規則

2009-06-25 10:15:41

糟糕的程序員

2025-03-10 08:00:00

開源VS Code開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91精品国产色综合久久不卡98 | 黄色大片免费观看 | 精品日韩 | 亚洲精品一区二区久 | 91伦理片| 欧美日韩高清免费 | 男女爱爱网站 | 精品欧美一区二区精品久久久 | 99视频在线 | 国产在线麻豆精品入口 | 久久精品国产一区 | 天天精品综合 | 国产黄视频在线播放 | 一区二区福利视频 | 亚洲最大的黄色网址 | 国产1区2区3区| 先锋资源在线 | av国产精品毛片一区二区小说 | 波多野结衣在线观看一区二区三区 | 91精品国产欧美一区二区 | 亚洲成av人影片在线观看 | 91热在线| 亚洲国产精品99久久久久久久久 | 欧美2区 | 国产成人精品网站 | 成人免费视频网站在线看 | av网址在线 | 国产精品成人一区二区三区 | 国产一级片av | 国产日韩精品一区 | 夜夜夜夜夜夜曰天天天 | 天天操天天拍 | 国产主播第一页 | 亚洲乱码一区二区三区在线观看 | 欧美成人激情视频 | 国产综合久久久久久鬼色 | 国产免费xxx | 国产欧美久久精品 | 蜜桃av鲁一鲁一鲁一鲁 | 奇米av | 亚洲性人人天天夜夜摸 |