機票自研實時規則引擎應用實踐
0000、複雜業務如何解耦
機票業務由於其資源來源多樣性,業務複雜,修改、增加業務邏輯繁瑣耗時,成本奇高,在這個大背景下,急需一套解決方案來解耦複雜的業務邏輯。
0001、解決方案之《規則引擎》
經過思考和調研,決定採用規則引擎來解決以上問題。 規則引擎由推理引擎發展而來,是一種嵌入在應用程序中的組件,實現了將複雜常變的業務邏輯從應用程序代碼中解耦出來,並使用預定義的語義模塊編寫業務決策規則。
規則引擎具體執行基本上可以分為三步:
0x1:接受數據輸入
0x2:解釋業務規則
0x3:根據業務規則做出業務決策幾個過程。
使用規則引擎可以把複雜、冗餘的業務規則同整個核心支撐系統分離開,做到業務上的解耦,架構上的可復用移植。
0010、規則引擎的實際用途和價值
規則引擎和流程引擎結合使用,是解決大規模複雜業務場景下兩大利器,流程引擎本文不做說明,僅講解規則引擎。
0x1:舉個例子
0x2:hard code的代價是很大的,隨著業務的發展,越來越複雜會使開發人員和產品人員,釐清邏輯的難度和成本,成指數的倍增,而且會「心累」
相對於業務系統,規則引擎可以認為是一個獨立於業務系統的模塊,負責一些規則的計算等。
一般來說,規則引擎主要應用在下面的場景中:
0x1:風控模型配置,風控是規則引擎
0x2:實時推薦系統配置,如根據用戶流量屬性,進行規則計算,輸出計算結果
0x3:簡單的離線計算,各類數據量比較小的統計等
0x4:一切需要解耦複雜業務邏輯的應用系統
0011、業界流行和先例
目前的規則引擎中,使用較多的開源規則引擎是Drools、和商用的ILOG JRules,經過調研,前者我們認為缺點是由於其引入了DRL語言比較複雜,學習曲線陡峭,而且很難二次開發符合當前的架構,後者是商用的,也不符合互聯網行業的習性,因此要輕量、快速、接地氣的規則引擎,只能自己開發了。
0100、《規則引擎》的一般實現方式
一套規則引擎總體就是要做到以下的目標:
0x1:定義規則語言語義標準(我們採用的Groovy實現)
0x2:動態編輯規則條件,例如大於、小於、等於、如果、那麼等等語義
0x3:動態編輯規則腳本
0x4:動態發布更新集群的能力
0x5:自動化執行
0101、規則引擎的一些難點要求
0x1:優先順序問題,先執行什麼判斷、後執行什麼判斷,先執行什麼邏輯、後執行什麼邏輯
0x2:規則衝突如何避免
0x3:如何設計一套足夠支撐全業務場景的規則管理模型。
0110、具體實現方案
業務規則我們認為可以抽象為以下幾點:
名稱:一種唯一的規則名稱
code:每一條規則的唯一標識
描述:對規則的簡要描述
內容:規則腳本具體內容
優先順序:相對於其他規則的優先順序
狀態:規則腳本是測試中還是開發中還是驗證中還是已上線
規則判斷條件抽象為:
大於
小於
等於
如果
那麼
否則
等等等
基於以上描述,我們提供一個完整的配套規則引擎管理平台:
提供以下功能
定義規則腳本名稱、編碼、類型等
採用Codemirror實現在線編寫規則腳本內容
提供在線配置規則判斷流程
實時在線測試規則腳本
在線審核、上線
採用Zookeeper發布推送集群工作流
如圖:
規則引擎如何執行&優化性能
通過規則條件、規則腳本預初始化,實現全部內存載入,我們的流程如下:
首先通過Zookeeper,發送通知,服務收到後,主動獲取變更的規則
獲取規則內容後,實例化規則對象
同時,使用規則code區分每一個規則
在集群中每一台伺服器上的JVM內存中,緩存下來所有的規則對象,其中包含了Class文件(後文會說明,為何一定要緩存)
業務系統通過在管理平台,配置每一種業務在哪一種場景下,需要調用哪些規則,並且指定優先順序
規則引擎,通過輸入的code,在本地緩存中,找到在管理平台中,定義的code對應的規則執行流程
在流程中,依優先順序,在本地緩存中獲取每一條規則對象,取出其中的Class文件,獲取實例對象Script
根據使用方輸入參數,使用Binding對象,進行參數綁定,並使用setBinding(Binding binding)方法,進行綁定。
最後 call method run,進行規則執行。
部分代碼實例如下:
1、規則提前初始化實現
2、規則緩存實現
3、規則執行介面
4、規則執行實現
0111、性能踩坑&優化
Groovy與Java結合有三種實現方式:
GroovyClassLoader
GroovyShell
GroovyScriptEngine
GroovyClassLoader
用 Groovy 的 GroovyClassLoader ,動態地載入一個腳本並執行它的行為。GroovyClassLoader是一個定製的類裝載器,負責解釋載入Java類中用到的Groovy類。
GroovyShell
GroovyShell允許在Java類中(甚至Groovy類)求任意Groovy表達式的值。您可使用Binding對象輸入參數給表達式,並最終通過GroovyShell返回Groovy表達式的計算結果。
GroovyScriptEngine
GroovyShell多用於推求對立的腳本或表達式,如果換成相互關聯的多個腳本,使用GroovyScriptEngine會更好些。GroovyScriptEngine從指定的位置(文件系統,URL,資料庫,等等)載入Groovy腳本,並且隨著腳本變化而重新載入它們。如同GroovyShell一樣,GroovyScriptEngine也能傳入參數值,並能返回腳本的值。
我們採用了GroovyClassLoader的方式實現
其中的問題在於,使用不當, 會造成非常頻繁的GC 通常使用如下代碼在Java 中執行 Groovy 腳本:
每次執行
Groovy 為了保證每次執行的都是新的腳本內容,會每次生成一個新名字的Class文件。當對同一個規則腳本每次都執行這個方法時,會導致的現象就是裝載的Class會越來越多,從而導致JVM的P區越來越大,而且無法被釋放。
同時這裡也存在性能瓶頸問題,去分析時會發現90%的耗時佔用在Class的生成上
為了避免這一問題通常做法是緩存Script對象,從而避免以上2個問題。在這過程中通常又會引入新的問題:
高並發情況下,binding對象混亂導致計算出錯 在高並發的情況下,在執行賦值binding對象後,真正執行run操作時,拿到的binding對象可能是其它線程賦值的對象,所以出現數據計算混亂的情況。
長時間運行仍然出現oom,無法解決生成Class耗時耗力的問題由於groovyClassLoader會緩存每次編譯groovy腳本的Class對象,下次編譯該腳本時,會優先從緩存中讀取,這樣節省掉編譯的時間。導致被載入的Class對象因為存在引用而無法被卸載,雖然通過緩存避免了短時間內大量生成新的class對象,但時間長了,無法被卸載的class會越來越多。
解決問題的辦法是:
每個script都new一個GroovyClassLoader來裝載;
對於parseClass後生成的Class對象進行本地cache。
1000、總結
乾貨總結,總要有點乾貨是不,在此之前呢,先總結下整個設計研發過程
規則引擎研發設計歷時 2周
上線方應用:機票實時推薦系統集成規則引擎
性能指標數據如下:
持續1小時峰值TPS 2.99萬
99.9%規則執行響應時間10ms內
99.99%規則執行響應時間 50ms內
99.999%規則執行響應時間在500ms內
最後來點超級乾貨,附上我們的規則引擎的完成UML類圖和調用時序圖如下:
TAG:同程藝龍研發中心 |