當前位置:
首頁 > 最新 > 機票自研實時規則引擎應用實踐

機票自研實時規則引擎應用實踐

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:同程藝龍研發中心 |