整理了下最近在項目中使用drools出現的問題,幸好都在開發與測試階段解決了,未波及到prod。
首先看這樣兩條規則:
/** * 規則1_set默認利率a */ rule "rate_default_a" no-loop true when $request:AmountRateRequest(calculateEnum == CalculateEnum.INTEREST || calculateEnum == CalculateEnum.AMOUNT_INTEREST) $response:AmountRateResponse(rateMap not contains LoanTermEnum.LOANTERM3) $data:DroolsData() then DroolsClient.logger.debug("{}執行規則{}", $response.getUserId(), drools.getRule().getName()); $response.getRateMap().put(LoanTermEnum.LOANTERM3, RateFactory.DEFAULT_RATE_A); update($response); end /** * 規則2_set默認利率b */ rule "rate_default_b" no-loop true when $request:AmountRateRequest(calculateEnum == CalculateEnum.INTEREST || calculateEnum == CalculateEnum.AMOUNT_INTEREST) $response:AmountRateResponse(rateMap not contains LoanTermEnum.LOANTERM3) then DroolsClient.logger.debug("{}執行規則{}", $response.getUserId(), drools.getRule().getName()); $response.getRateMap().put(LoanTermEnum.LOANTERM3, RateFactory.DEFAULT_RATE_B); update($response); end
理想的情況:當規則fire后,rate_default_a規則實行,並set3期利率,得到結果后,由於不滿足b規則的when條件(rateMap中3期利率已經存在),則不會繼續執行rate_default_b,一切正常,
實際的結果:a執行后觸發b、b執行后觸發a,造成死循環
原因分析:
肯定是因為when條件約束失敗,導致重復觸發,而與規則中修改所相關的就是
$response:AmountRateResponse(rateMap not contains LoanTermEnum.LOANTERM3)
在drools中,不能通過contains來判斷java的map對象是否containsKey。contains 只能用於對象的某個Collection/Array 類型的字段與另外一個值進行比較,作為比較的值可以是一個靜態的值,也可以是一個變量(綁定變量或者是一個global 對象),不能操作map。如果需要判斷map,建議使用map[keyName]的方式,比如我們這條規則,可以修改為:
$response:AmountRateResponse(rateMap[LoanTermEnum.LOANTERM3] == null)
來做,即可正確判斷map的某個值是否為空。
同時對規則中出現的no-loop進行分析:
網絡中能找到的大部分回答:no-loop屬性的作用是用來控制已經執行過的規則在條件再次滿足時是否再次執行。默認情況下規則的no-loop屬性的值為false,如果no-loop 屬性值為true,那么就表示該規則只會被引擎檢查一次
實際效果:no-loop所說的只執行一次,是說當本條規則內如果更新了fact,不會重新觸發本條規則的執行。如果像我們上面代碼中的情況,a規則和b規則本身都有no-loop true,但a中的udpate仍可以觸發b的執行,b也可以觸發a。
如果需要讓某條規則只能觸發一次,則不能靠no-loop,需要使用lock-on-active true來做。同時注意:雖然規則只能被執行一次是可以做到的,但對於一些場景中,某些規則不希望被觸發,並不只是限制次數,還需要結合具體業務來做。
結論和改進:
1. 不要使用contains操作map,采用map[keyName]的形式
2. 規則導致的死循環可能有很多種形式,a觸發a、ab間相互觸發,都可能引起很壞的結果,上線前要謹慎。
3. 建議在每條規則執行前后增加日志,當出現死循環、或其他不符合預期的結果時能快速定位,方便追蹤。
4. 建議把懷疑有問題的語句拆成最小的單元執行。比如對我們上面的代碼稍微修改,只保留一條規則:
/** * 規則1_set默認利率a */ rule "rate_default_a" no-loop true when $request:AmountRateRequest(calculateEnum == CalculateEnum.INTEREST || calculateEnum == CalculateEnum.AMOUNT_INTEREST) $response:AmountRateResponse(rateMap not contains LoanTermEnum.LOANTERM3) $data:DroolsData() then DroolsClient.logger.debug("{}執行規則{}", $response.getUserId(), drools.getRule().getName()); $response.getRateMap().put(LoanTermEnum.LOANTERM3, RateFactory.DEFAULT_RATE_A); update($response); end
雖然不會導致死循環,但也無法說明到底是因為contains有效、還是no-loop true不觸發自身,具體是哪行導致的結果正常。如果我們去掉no-loop true,就會發現依然出現了死循環,發現是contains的問題。