Drools中KieContainer与KieBase的newKieSession()差异及性能优化
Drools 批量消息处理场景下KieSession性能与结果异常问题
问题背景
需要逐条向Drools规则引擎传入7万条消息执行规则计算,核心诉求是降低整体处理耗时。
- 第一种实现方案:每次循环调用自定义
DroolsBeanFactory的getKieSession()方法创建KieSession实例,单次实例化耗时约0.8秒,循环累计耗时过高,但规则评估结果完全准确,对应实现代码如下:
public KieSession getKieSession() { getKieRepository(); KieFileSystem kieFileSystem = kieServices.newKieFileSystem(); kieFileSystem.write(ResourceFactory.newClassPathResource("com/example/demo/Rule1.drl")); kieFileSystem.write(ResourceFactory.newClassPathResource("com/example/demo/Rule2.drl")); kieFileSystem.write(ResourceFactory.newClassPathResource("com/example/demo/Rule3.drl")); kieFileSystem.write(ResourceFactory.newClassPathResource("com/example/demo/Rule4.drl")); kieFileSystem.write(ResourceFactory.newClassPathResource("com/example/demo/Rule5.drl")); kieFileSystem.write(ResourceFactory.newClassPathResource("com/example/demo/Rule6.drl")); KieBuilder kb = kieServices.newKieBuilder(kieFileSystem); kb.buildAll(); KieModule kieModule = kb.getKieModule(); KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId()); return kContainer.newKieSession(); }
- 第二种实现方案:通过
@Autowired注入KieBase实例,每次循环调用KieBase的newKieSession()方法创建会话,该方案下KieSession实例化耗时大幅降低,但规则评估结果异常,通常循环执行36-37次后就无法正确完成消息的规则评估。对应配置与业务代码如下:kmodule.xml配置:
<?xml version="1.0" encoding="UTF-8"?> <kmodule xmlns="http://www.drools.org/xsd/kmodule"> <kbase name="rules-kb" packages="com.example.demo"> </kbase>
业务实现代码:
@Autowired KieBase kBase for(String message: messages) { //KieSession kieSession = new DroolsBeanFactory.getKieSession(); // 耗时长,结果准确 KieSession kieSession = kBase.newKieSession(); // 实例化快,结果异常 kieSession.insert(...); kieSession.fireAllRules(); KieSession.dispose(); }
问题答复
1. 两种方式创建的KieSession存在行为差异的原因
两种方式创建的KieSession本身在规则匹配逻辑上没有任何本质差异,出现结果偏差完全是实现代码的bug导致的:
- 第一种方案每次处理消息都会完整执行规则编译、KieModule构建、KieContainer新建全流程,所有规则上下文、会话状态都是全新生成的,没有任何历史残留,所以结果准确,但每次0.8秒的开销几乎全是重复的规则编译、资源加载IO操作,属于完全不必要的性能浪费。
- 第二种方案结果异常有两个直接原因:
- 配置文件不完整:贴出的
kmodule.xml中<kbase>标签没有正确闭合,Spring注入的KieBase实例可能存在规则加载不全、配置初始化异常的问题。 - 资源未正确释放:循环中最后调用的是
KieSession.dispose(),属于尝试调用类的静态方法,根本没有释放当前循环生成的kieSession实例。前30多次循环生成的会话全部驻留内存,累积的事实对象、规则触发状态、全局变量脏数据达到内存阈值后,就会导致后续新建会话的规则匹配逻辑失效。
补充说明:KieBase是存储编译后规则产物的线程安全对象,正常初始化完成后,同一个KieBase生成的所有KieSession,规则匹配逻辑和全新构建KieContainer生成的会话完全一致,不存在逻辑差异。
- 配置文件不完整:贴出的
2. 保准确前提下降低耗时的落地方案
按优先级依次落地即可,改完后7万条消息的总处理耗时可以控制在数秒内:
- 第一步:修复现有代码低级错误
补全kmodule.xml的闭合标签,保证KieBase正确加载所有规则;把循环内的资源释放逻辑改成实例方法调用,用try-finally包裹保证异常场景下会话也能被正常释放:
@Autowired KieBase kBase; for(String message: messages) { KieSession kieSession = null; try { kieSession = kBase.newKieSession(); // 插入事实、设置全局变量的业务逻辑 kieSession.insert(message); kieSession.fireAllRules(); } finally { if (kieSession != null) { kieSession.dispose(); } } }
改完后单次KieSession实例化耗时会降到毫秒级,且不会再出现30次后结果异常的问题。
- 第二步:排查规则内全局变量污染
如果DRL规则中定义了global全局变量,禁止将全局变量指向JVM静态可变对象(比如静态集合、静态计数器),每个KieSession单独设置全局变量的实例,避免跨会话状态污染。 - 第三步:用会话池进一步压减实例化开销
Drools原生提供KieSession池能力,应用启动时基于KieBase初始化一次会话池,后续处理直接从池中借用会话,用完归还即可,能把单会话的实例化开销降到微秒级以下:
// 应用启动时初始化一次即可,池大小和业务处理线程数保持一致,单线程场景设为1足够 KieSessionsPool sessionPool = kBase.newKieSessionsPool(Runtime.getRuntime().availableProcessors()); for(String message: messages) { KieSession kieSession = sessionPool.newKieSession(); try { kieSession.insert(message); kieSession.fireAllRules(); } finally { kieSession.dispose(); // 此处dispose不是销毁会话,是将会话归还到池 } }
- 禁止在消息处理循环中执行规则编译、KieFileSystem/KieBuilder/KieContainer构建流程,这类重型操作仅需在应用启动时执行一次,生成的KieBase作为全局单例复用即可,规则匹配逻辑和每次重新编译完全一致。
内容的提问来源于stack exchange,提问作者definepi314
相关产品推荐
相关产品推荐

