Java Drools - REST 调用超时 - 内存不足

Java Drools - REST Call Timing Out - Out Of Memory

我正在 Java 项目中实施 Drools,以下依赖项的版本 6:

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-core</artifactId>
    <version>6.0.1.Final</version>
</dependency>

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>6.0.1.Final</version>
</dependency>

我打算通过调用使用 spring 4.3.0.RELEASE 构建的 REST API 来调用规则。在我的配置 class 中,我正在从数据库加载 drools 脚本:

@Configuration
@EnableWebMvc
@EnableAsync
@ComponentScan(basePackages = "com.bla.bla.api.app")
@ImportResource({ "classpath:datasources-context.xml" })
public class AppConfig {
 private static final Log log = LogFactory.getLog(AppConfig.class);  
 public @Autowired IServiceDao serviceDao;

规则随后被存储在一个全局变量中,作为 @Repository 注释中的列表 class:

@Repository
public class ServiceDao implements IServiceDao

在上面的ServiceDao中有一个方法,在post构造阶段调用:

@PostConstruct
    private void init() throws IOException {
        log.debug("Initializing ruleevaluation API Context");

        // Initializing rules:
        serviceDao.initializeRules();
    }

,从数据库重新加载规则并初始化 drools 环境:

public void initializeRules() {
        

        // 1. Load Rules:
        log.debug("Initiating Rules...");
        rules.clear();
        rules = loadRules();

        //  2. Initialize Drools Environment:
        try {

            initializeDrools();

        } catch (DroolsParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }

方法'initializeDrools()'实现如下:

private void initializeDrools() throws DroolsParserException, IOException {

        PackageBuilder packageBuilder = new PackageBuilder();
        RuleBase ruleBase = RuleBaseFactory.newRuleBase();
        
        // This script will contain all the rules combined in one Drools script:
        String completeRulesDroolsScript = "";
        // Initialize script with object imports:
        completeRulesDroolsScript = ""
                + "import com.bla.model.svc.RuleRequest             \r\n"
                + "import com.bla.model.svc.RuleResponse        \r\n\n\n";

        if (rules != null) {

            if (rules.size() > 0) {
                for (RuleObject ruleObject : rules) {

                    // Add each individual Drools rule expression to the complete Drools script:
                    completeRulesDroolsScript += ruleObject.getExpression();
                    completeRulesDroolsScript = completeRulesDroolsScript .concat("\n\n\n");
                }
            }

        }

        try {
            packageBuilder.addPackageFromDrl(new 
            StringReader(completeRulesDroolsScript));
            org.drools.core.rule.Package rulesPackage = packageBuilder.getPackage();            
            ruleBase.addPackage(rulesPackage);
            
            this.setWorkingMemory(ruleBase.newStatefulSession());
        }
        catch(Exception e) {
            log.error("!!! Could not Initialize Drools Engine !!!");
        }
        
    }

接下来是 REST 方法,在单独的控制器中 class:

@RestController
public class RuleEvaluationController

它只是从 ServiceDao 调用后端服务 class:

    @PostMapping(value = "/evaluateRule")
    public ResponseEntity<RuleEvaluationResponse> evaluateRuleData(
            @RequestBody RuleEvaluationRequest ruleEvaluationRequest) {     

        RuleEvaluationResponse ruleEvaluationResponse = new RuleEvaluationResponse();
        try {

            // 1. get the parameters list:
            log.debug(ruleEvaluationRequest.toString());
            
            RuleEvaluationResponse response = serviceDao
                    .evaluateRule(ruleEvaluationRequest);

            log.debug("response = " + response.getEntry());

            return new ResponseEntity<RuleEvaluationResponse>(response,
                    HttpStatus.OK);

        } catch (Exception e) {

            log.error("!!!!! Exception Occurred:" + e.getMessage());

            // set request id, and date in millis:
            ruleevaluationResponse.setRequestId(UUID.randomUUID().toString());
            ruleevaluationResponse.setDate(System.currentTimeMillis());

            ruleevaluationResponse.setErrorCode(Constants.FAILURE);
            ruleevaluationResponse.setErrorDescription(e.getMessage());

            return new ResponseEntity<RuleEvaluationResponse>(ruleevaluationResponse,
                    HttpStatus.OK);
        }
    }

其中 serviceDao.evaluateRule 只是插入请求和响应对象,然后触发规则:

public RuleEvaluationResponse evaluateRule(RuleEvaluationRequest request) {

        RuleEvaluationResponse response = new RuleEvaluationResponse();

        this.getWorkingMemory().insert(response);
        this.getWorkingMemory().insert(request);
        this.getWorkingMemory().fireAllRules();

        log.debug("Response = " + response.getEntry());

        return response;
    }

起初一切正常,我正在成功评估规则并获得预期的响应。然而,在 4 或 5 次 REST 调用之后,下一次休息调用永远不会 returns 响应,一段时间后我收到内存不足错误。

谁能告诉我我在这里错过了什么?非常感谢任何提示和建议。如果以上信息不足,请随时询问更多详情。

谢谢

您没有提供所有代码,所以您可能在其他地方进行了清理 但看起来您正在插入和调用 .fireAllRules() 而不是清理。我的代码与 KieSession 一起工作,看起来或多或少像这样:

kSession = container.newKieSession("name");
kSession.insert(...);
kSession.insert(...);
kSession.insert(...);

kSession.fireAllRules();
Object[] objects = kSession.getObjects();
kSession.dispose();

您可能需要更改代码。 https://docs.jboss.org/drools/release/6.0.1.Final/drools-docs/html_single/#d0e3555 文档中描述的有状态会话需要在第一次 fireAllRules() 调用后删除对象。因此,您要么创建无状态会话并每次都处理它,要么使用有状态会话并在插入新对象之前删除旧对象。

我可能会选择无状态(因为我已经在这样做了;)

[...]
try {
    packageBuilder.addPackageFromDrl(new 
        StringReader(completeRulesDroolsScript));
    org.drools.core.rule.Package rulesPackage = 
        packageBuilder.getPackage();            
    ruleBase.addPackage(rulesPackage);
        
    this.setWorkingMemory(ruleBase.newStatefulSession());
}
[...]

我假设所有繁重的工作(解析 DRL 等)都在 newStatefulSession() 调用和创建之前发生 会话不是 'expensive'.

感谢您提供的提示和建议,它们有助于找到解决方案。

我解决问题如下:

首先我使用 KieSession 迁移到版本 6.. 的新语法。

首先我初始化了 Kie 组件如下:

// Define Kie Service from the Drools factory:
KieServices ks = KieServices.Factory.get();

// Define Drools Kie Repository to house the rules:
KieRepository kr = ks.getRepository();

// By default, a Kie File System is defined. In this case rules are NOT being
// read from the *.drl file, but instead fetched from Database.
KieFileSystem kfs = ks.newKieFileSystem();

// Write rules into the Kie file system. Specified file is irrelevant and can be
// empty, since we are not reading from *.drl file.
kfs.write("src/main/resources/com/rule/tempRules.drl", completeAccountingRulesDroolsScript);

// Define rule builder of the rules in file system object:
KieBuilder kb = ks.newKieBuilder(kfs);

// Build all rules in the script.kieModule is automatically deployed to
// KieRepository if successfully built.
kb.buildAll();

// Define new session container. This container is global and will be available
// for all subsequent rule invocations:
kContainer = ks.newKieContainer(kr.getDefaultReleaseId());

在我的 REST 调用中,我用以下代码替换了代码:

kSession = kContainer.newKieSession();
kSession.insert(response);
kSession.insert(request);
kSession.fireAllRules();

Collection<? extends Object> objects = kSession.getObjects();
kSession.dispose();

现在变量 kContainer 是一个只在启动时初始化的全局变量。在每次 REST 调用结束时,我都会处理会话。未来的通话将为每个通话创建一个 kie 会话。如果这是最佳方法,可以使用您的意见。

@user3075118 @ 冷冻豌豆的罗迪

感谢您的提示,正是它解决了问题。