drools规则引擎耗费内存问题解决

背景
公司使用drools规则引擎过程中,一个规则文件中差不多10个rule,每一次访问都需要耗费800M内存,导致频繁GC,同时classloader实例数每构建一次都会增加,导致nonHeap区内存占用量跟着递增,导致比较频繁的fullGC(此时老年代使用率还不到10%)。当并发量稍微大一点服务就会挂掉。

原因
规则文件加入drools的时候需要解析编译,每访问一次接口都导致规则解析编译一次,经过监控测试发现800M内存和classloader递增问题就是在解析过程中产生的

解决思路
解决问题的本质是缓存已经解析的规则对象,避免重复加载。

缓存以后注意点

  • 缓存淘汰策略
  • 当规则文件发生变化的时候,要及时更新缓存

用到的技术:drools+Guava
建议用Caffeine替代Guava,我这里是公司规定使用Guava,所以代码实例使用的Guava

代码

	// 建议使用Caffeine代替guava
 	private static Cache<String, Entity> guavaCache;

    @Value("${guava.drools.maximumSize:1000}")
    private Integer maximumSize;

	// 默认一周
    @Value("${guava.drools.expireAfterAccess:168}")
    private int expireAfterAccess;

	/**
	 * 构建guava
	**/
    @PostConstruct
    public void init() {
        guavaCache = CacheBuilder.newBuilder()
                .maximumSize(maximumSize)
                .expireAfterAccess(expireAfterAccess, TimeUnit.HOURS)
                .build();
    }

    /**
     * 执行规则
     * 
     * @param drl 规则字符串
     * @param fact 规则fact
     * @param agendaStr agenda
     * @param lastTime 规则最后修改时间
     */
    public void excute(String drl, String fact, String agendaStr, Long lastTime) {
        KieSession kSession = null;
        try {
            kSession = getSession(agendaStr, drl, lastTime);
            // 新增议程组
            Agenda agenda = kSession.getAgenda();
            agenda.getAgendaGroup(agendaStr).setFocus();
            kSession.insert(log);
            kSession.insert(fact);
            kSession.fireAllRules();
        } catch (Exception e) {
            log.error("drools rule excute ex.", e);
        } finally {
            if (Objects.nonNull(kSession)) {
                kSession.dispose();
            }
        }
    }

    public KieSession getSession(String agenda, String drl, Long lastTime) {
        Entity entity = guavaCache.getIfPresent(agenda);
        KnowledgeBaseImpl kieBase;
        if (Objects.isNull(entity)) {
            kieBase = buildKieBase(agenda, drl, lastTime);
        } else {
            if (lastTime.equals(entity.getLastTime())) {
                kieBase = entity.getKnowledgeBase();
            } else {
                // 此处无需删除,直接覆盖即可
                kieBase = buildKieBase(agenda, drl, lastTime);
            }
        }
        return kieBase.newKieSession();
    }

    /**
     * 构建 {@link KnowledgeBaseImpl}
     *
     * @param agenda   规则文件唯一key
     * @param drl      规则内容
     * @param lastTime 规则最后修改时间
     * @return
     */
    private KnowledgeBaseImpl buildKieBase(String agenda, String drl, Long lastTime) {
        log.info("drools create kieBase. agenda:{}", agenda);
        KieHelper helper = new KieHelper();
        helper.addContent(drl, ResourceType.DRL);
        KnowledgeBaseImpl kieBase = (KnowledgeBaseImpl) helper.build();
        // 解除严格模式
        kieBase.getConfiguration().setProperty("drools.dialect.mvel.strict", "false");
        guavaCache.put(agenda, new Entity(kieBase, lastTime));
        return kieBase;
    }

    public void removeKieBase(String agenda) {
        guavaCache.invalidate(agenda);
    }

    class Entity {
        KnowledgeBaseImpl knowledgeBase;
        /**
         * 规则最后修改时间
         */
        Long lastTime;

        Entity(KnowledgeBaseImpl base, Long lastTime) {
            this.knowledgeBase = base;
            this.lastTime = lastTime;
        }

        public KnowledgeBaseImpl getKnowledgeBase() {
            return knowledgeBase;
        }

        public Long getLastTime() {
            return lastTime;
        }
    }

备注:
1、入口方法excute,其中drl,我们的规则是以字符串的方式存储在数据库中,业务方动态查询出来并传进来
2、lastTime参数,是随drl存储在库中的,每次drl修改都会同步更新lastTime,并作为规则的版本标记
3、agenda参数,这个参数在我们业务中是能唯一确定一条规则的,所以以他作为缓存的key

重点关注
代码的关键点在于把构建的KnowledgeBaseImpl对象要做一个缓存, 实例中的代码是结合我们系统的业务,而且是兼容整合旧代码改过来的,在设计上存在缺陷,强烈建议根据这个思路进行重写

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值