You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Drools规则并发执行时全局变量异常问题咨询及排查方案求助

问题分析

你的问题核心是全局集合的线程安全性问题,以及并发场景下对myList的共享访问导致的竞态条件:

  1. KieSession隔离性没问题,但全局变量共享了非线程安全集合
    你每次执行规则都创建新的KieSession,这部分是正确的——KieSession本身线程不安全,单请求单实例是标准做法。但问题出在传入的myList:如果多个并发请求共享同一个非线程安全集合(比如普通ArrayList),当多个线程同时调用add()时,会出现数据丢失、元素覆盖或集合结构损坏的情况。
  2. 规则执行的原子性未被保证
    即使每个请求用独立KieSession,但如果myList是跨请求共享的全局对象(比如Spring单例Bean中的集合),多线程对它的修改没有同步保护,add()操作非原子,并发下必然出现预期外结果。
解决建议

根据业务场景,可选择以下修复方案:

1. 每个请求使用独立集合实例(推荐)

如果myList不需要跨请求共享,每次调用executeRules时创建新集合实例,从根源避免共享问题:

// 调用方代码示例
Map<String, Object> globals = new HashMap<>();
globals.put("myList", new ArrayList<>()); // 每个请求创建独立集合
ruleExecutor.executeRules(rulesProvider, applicant, globals);

2. 替换为线程安全集合

如果必须共享myList(比如收集所有请求的规则结果),将集合替换为线程安全实现:

  • Collections.synchronizedList(new ArrayList<>()):基础同步包装列表
  • CopyOnWriteArrayList:适合读多写少场景
  • ConcurrentLinkedQueue:队列结构需求时使用

规则中可明确全局变量类型(可选):

global java.util.concurrent.CopyOnWriteArrayList myList

传入全局变量时使用线程安全实例:

globals.put("myList", new CopyOnWriteArrayList<>());

3. 对集合操作加同步锁

若不想替换集合实现,可在规则的then块或Java代码中对集合修改加锁:

rule "myRule3"
when
    Applicant(experienceInYears == 5)
then
    synchronized(myList) {
        myList.add("level3");
    }
end

注意:该方式会降低并发性能,需权衡业务需求与性能损耗。

4. 改用Fact传递结果(符合Drools设计理念)

Drools推荐用Fact传递数据而非全局变量,定义结果类作为Fact插入会话,规则修改该对象的集合属性:

// 结果类定义
public class RuleResult {
    private List<String> levels = new ArrayList<>();
    public void addLevel(String level) {
        levels.add(level);
    }
    public List<String> getLevels() {
        return levels;
    }
}

规则中使用Fact代替全局变量:

rule "myRule3"
when
    Applicant(experienceInYears == 5)
    $result: RuleResult()
then
    $result.addLevel("level3");
end

Java代码中插入结果Fact并取出:

private void setupFactAndFireAllRules(Object fact, KieSession kieSession) {
    RuleResult result = new RuleResult();
    kieSession.insert(result);
    kieSession.insert(fact);
    kieSession.fireAllRules();
    List<String> levels = result.getLevels(); // 获取规则执行结果
}
并发测试定位根因

可通过以下方法模拟并发场景,验证问题并确认修复效果:

1. JUnit + ExecutorService模拟并发请求

@Test
void testConcurrentRuleExecution() throws InterruptedException {
    int threadCount = 100;
    ExecutorService executor = Executors.newFixedThreadPool(threadCount);
    CountDownLatch latch = new CountDownLatch(threadCount);
    
    // 测试用:非线程安全集合会出现数据丢失;修复后替换为CopyOnWriteArrayList
    List<String> sharedList = new ArrayList<>();
    
    for (int i = 0; i < threadCount; i++) {
        final int experience = (i % 3) == 0 ? 5 : (i % 3 == 1 ? 10 : 15);
        executor.submit(() -> {
            try {
                Applicant applicant = new Applicant();
                applicant.setExperienceInYears(experience);
                Map<String, Object> globals = new HashMap<>();
                globals.put("myList", sharedList);
                ruleExecutor.executeRules(rulesProvider, applicant, globals);
            } finally {
                latch.countDown();
            }
        });
    }
    
    latch.await();
    executor.shutdown();
    
    // 预期100个元素,非线程安全集合结果会少于100
    System.out.println("Total elements: " + sharedList.size());
}

2. JMeter压力测试

  • 创建线程组设置并发数(如100线程)
  • 配置HTTP请求调用规则执行接口
  • 检查响应中myList的元素数量或内容是否符合预期

3. 启用Drools调试日志

添加日志配置查看规则执行顺序与集合修改细节:

logging.level.org.drools=DEBUG

通过日志可确认哪些规则被触发,以及myList的修改是否被正确执行,帮助定位竞态条件导致的数据丢失问题。


内容的提问来源于stack exchange,提问作者msdev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 05:16:33