1. 引言
什么是規則引擎
一個業務規則包含一組條件和在此條件下執行的操作,它們表示業務規則應用程序的一段業務邏輯。業務規則通常應該由業務分析人員和策略管理者開發和修改,但有些復雜的業務規則也可以由技術人員使用面向對象的技術語言或腳本來定制。業務規則的理論基礎是:設置一個或多個條件,當滿足這些條件時會觸發一個或多個操作。
規則引擎(rule engine)是指將復雜的業務邏輯抽象成規則,然后使用特定的算法(比如Rete)對規則進行求值等操作。簡單點說,規則引擎就是實現復雜業務邏輯的框架。
為什么要用規則引擎
在維護和更新項目的業務邏輯代碼時,大家深有體會:
- 因編碼風格的問題,不同人有不同的代碼實現,而造成代碼理解的困難;
- 每一次業務邏輯的更改會導致項目的重編譯;
- 為了能實時響應更改,而不得不做服務重啟的無縫銜接
從上面的需求出發,規則引擎應滿足如下特點:
- 腳本化,允許用類Python的腳本語言或DSL來描述規則;
- 動態化,實時動態地加載規則腳本,規則的修改能實時地反饋於服務系統;
- 快速的執行速度。
2. 實現
已有的開源方案
Drools應該是Java界名頭最響、功能最豐富、社區最活躍的開源規則引擎了,目前的版本號為6.5.0.Final
。淘寶也開源了自己的一套規則引擎QLExpress,但是似乎在2011年后項目沒有再維護了。相較於QLExpress,Drools設計一套類DSL的腳本語言,用起來非常爽。但是在我看來,對於特定場景(比如用戶畫像),Drools顯得過於重量級了,而QLExpress設計的API過於Java化了。
Scala方案
為了做到腳本化與動態化,Scala化的規則引擎將會把規則作為Scala Script獨立出來,采用動態編譯來加載。第三方Scala庫hammurabi與scalascriptengine完美地契合了需求。其中,hammurabi是規則引擎的具體實現,scalascriptengine用於對scala文件做動態編譯。雖然hammurabi沒有wiki,但是DZone上有一篇介紹使用的文章。scalascriptengine的版本號"com.googlecode.scalascriptengine" % "scalascriptengine_2.11" % "1.3.10"
。
ScalaScriptEngine的API為java.io.File;為了與Spark做集成,我們不得不做接口封裝——從HDFS路徑得到local File對象。基本思路便是把HDFS目錄拷貝到本地的目標路徑,然后基於本地路徑new File對象:
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import java.io.File
implicit def path2File(hdfsPath: String): File = {
val fs = FileSystem.get(new Configuration)
val dstDir = System.getProperty("java.io.tmpdir") + "/scala-tmp"
val dstPath = new Path(dstDir)
fs.deleteOnExit(dstPath)
fs.copyToLocalFile(new Path(hdfsPath), dstPath)
new File(dstDir)
}
由HDFS路徑(作為Scala Script的package根目錄)導入規則腳本生成規則集合對象:
import com.googlecode.scalascriptengine.ScalaScriptEngine
def loadRules[T](sourceDir: String, className: String): T = {
val sse = ScalaScriptEngine.onChangeRefresh(sourceDir)
// delete all compiled classes (i.e. from previous runs)
sse.deleteAllClassesInOutputDirectory()
sse.refresh
sse.newInstance[T](className)
}
為了使得規則集合具有類型,而特地定義了RulesTrait:
// RulesTrait.scala
package rule.util
import hammurabi.Rule
trait RulesTrait {
val rules: Set[Rule]
}
// User.scala
case class User(uid: String, apps: Array[String]) {
val tags = mutable.Set.empty[String]
override def toString: String = "uid: %s, apps: %s, tags: %s".format(uid, apps.mkString, tags)
}
Scala Script所描述的業務規則集合(用於給用戶打標簽)如下:
import hammurabi.Rule
import _root_.rule.util.{RulesTrait, User}
import hammurabi.Rule._
class TagRules extends RulesTrait {
override val rules: Set[Rule] = Set(
rule("add 母嬰 Tag") let {
val u = any(kindOf[User])
when {
u.apps.mkString.matches(".*(孕|寶寶|育兒).*")
} then {
u.tags += "母嬰"
}
},
rule("add 大學生 Tag") let {
val u = any(kindOf[User])
when {
u.apps.mkString.matches(".*(四級|六級|大學).*")
} then {
u.tags += "大學生"
}
}
)
}
object TagRules {
}
在Spark程序中集成規則引擎:
import rule.util.{RulesTrait, User}
val rulesObj = loadRules[RulesTrait](params.scriptPath, "TagRules")
val ruleEngine = RuleEngine(rulesObj.rules)
val engineBC = sc.broadcast(ruleEngine)
val log: RDD[User]
val tagRdd = log.mapPartitions { iter =>
val seq = iter.toSeq
val workingMemory = WorkingMemory(seq)
engineBC.value execOn workingMemory
seq.filter(_.tags.nonEmpty).toIterator
}
3. 參考資料
[1] 李國樂, Java規則引擎與其API(JSR-94).
[2] Ricardo Olivieri, 使用 Drools 規則引擎實現業務邏輯.