Drools 之 KieModule 版本管理

Drools 作为一个优秀的开源规则引擎,它的功能无疑是非常强大的。与普通的规则硬编码相比,Drools 有着非常多的优点,比如:规则的动态更新、规则配置的可视化等。

1. 规则动态更新的简单描述

使用或了解过 Drools 的小伙伴们应该知道它里面定义了很多的概念,其中有一个比较重要的就是 KieModule,而 Drools 的规则动态更新也就是基于对 KieModule 地动态加载。由于一般在现实的使用场景中,规则内容都会被打包成一个 Jar 文件,然后由 KieScanner 根据 Jar 的 GAV(groupId/artifactId/version) 对其进行扫描加载,从而实现规则的动态更新。那么 Drools 是怎样根据 GAV 来控制规则的版本管理的呢?

2. KieModule 仓库

在 Drools 里面,规则包是强依赖于 Maven 的,规则管理的抽象模型也与其基本一致,与 Maven 的 jar 仓库类似,Drools 也有着自己的仓库对 KieModule 进行管理,当然,Drools 的仓库只是程序代码中一个概念。

Drools 的仓库非常简单,首先,我们来看一下仓库相关的类图:

Drools 之 KieModule 版本管理_第1张图片

类的层次结构非常简单,一个接口,一个实现类,就完成了整个对 KieModule 的管理,当然只限于 KieModule,不包含其里面的 KieBase、KieSession 之类的。

而对于 KieModule 的管理,所有的操作也只有三种:添加(addKieModule)、删除(removeKieModule)、获取(getKieModule)。而这三类操作却全部委派给 KieRepositoryImpl.KieModuleRepo 这个内部类来完成了,这个类将是我们学习的重点。

2.1. KieModuleRepo 的属性设置

KieModuleRepo 属性:用于定义 KieModule 缓存区的大小。

// GA 缓存的最大 Size 相关属性
public static final String CACHE_GA_MAX_PROPERTY = "kie.repository.project.cache.size";
static final int MAX_SIZE_GA_CACHE // made changeable for test purposes
        = Integer.parseInt(System.getProperty(CACHE_GA_MAX_PROPERTY, "100"));

// Version 缓存的最大 Size 相关属性
public static final String CACHE_VERSIONS_MAX_PROPERTY = "kie.repository.project.versions.cache.size";
static final int MAX_SIZE_GA_VERSIONS_CACHE // made changeable for test purposes
        = Integer.parseInt(System.getProperty(CACHE_VERSIONS_MAX_PROPERTY, "10"));

2.2. KieModule 缓存

KieModule 的缓存分为两类:

  • KieModule 缓存: 缓存分为两级,第一级:键为 GA(规则包的 groupId 及 artifactId),值为 NavigableMap,也称为 GA 缓存 。其中 NavigableMap 是同 GA 不同 Version 的一组 KieModule。
  • OldKieModule 缓存: 主要存储旧版本的 KieModule。键为 GAV,这里用一个实体类 ReleaseId 来表示。
// KieModules 缓存区,大小为 GA Size
final Map> kieModules
        = new LinkedHashMap>(16, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry> eldest) {
        return (size() > MAX_SIZE_GA_CACHE);
    }
};

// oldKieModules 缓存区,大小为 GA Size * Version Size
final LinkedHashMap oldKieModules = new LinkedHashMap() {
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > (MAX_SIZE_GA_CACHE * MAX_SIZE_GA_VERSIONS_CACHE);
    }
};

上述的两个缓存区都使用 LinkedHashMap 实现了 LRU(近期最少使用)算法,用来控制缓存区的大小,在缓存区无多余容量时,将最少使用的 KieModule 移出缓存区(新技能 Get)。

2.3. 添加 KieModule

整个 KieModule 的添加流程可以分成三步:
第一步:ReleaseId 的拆解,即分为 GA 与 Version。
第二步:KieModule 二级缓存 NavigableMap 的获取(创建)。
第三步:备份旧的 KieModule,并添加新的 KieModule。

synchronized void store(KieModule kieModule) {
    ReleaseId releaseId = kieModule.getReleaseId();
    // 拼接 groupId 及 artifactId 用来标识仓库里面一组同 GA 的 KieModule
    String ga = releaseId.getGroupId() + ":" + releaseId.getArtifactId();
    // 根据 KieModule 的版本号通过某种算法计算出一个可比较的版本
    ComparableVersion comparableVersion = new ComparableVersion(releaseId.getVersion());

    // 获取相同 GA 的 KieModule 集合
    NavigableMap artifactMap = kieModules.get(ga);
    if( artifactMap == null ) {
        artifactMap = createNewArtifactMap();
        kieModules.put(ga, artifactMap);
    }

    // 正式添加 KieModule 之前,备份旧的 KieModule
    KieModule oldReleaseIdKieModule = oldKieModules.get(releaseId);
    // variable used in order to test race condition
    if (oldReleaseIdKieModule == null) {
        KieModule oldKieModule = artifactMap.get(comparableVersion);
        if (oldKieModule != null) {
            oldKieModules.put( releaseId, oldKieModule );
        }
    }

    // 正式添加 KieModule
    artifactMap.put( comparableVersion, kieModule );
}

在这三步操作中,第一步里面有一个 ComparableVersion 的计算,这里暂时不做赘述,详情请参考 ComparableVersion 的实现。重点看一下第二步中的 NavigableMap 的初始化,也是版本管理中的重中之重。

private NavigableMap createNewArtifactMap() {
    // 使用实现 TreeMap 匿名内部类,作用:保证 KieModule 缓存中元素按照 ComparableVersion 进行排序
    NavigableMap newArtifactMap = new TreeMap() {

        // 此处定义一个 Map 指向 TreeMap 本身的实例,是因为需要在索引队列中,对真实的 KieModule 缓存进行了删除操作
        // 而匿名内部类无法访问到外部类的实例,所以增加一个 final 的引用指向外部类的一个实例,即真实的缓存容器
        private final Map artifactMap = this;

        // 使用 LinkedHashMap 实现一个 LRU 队列,存储了真实缓存 artifactMap 里面的 Key(相当于索引),下面称索引队列
        // 主要作用:限制 KieModule 缓存容器的无限增长
        LinkedHashMap backingLRUMap = new LinkedHashMap(16, 0.75f, true) {

            @Override
            protected boolean removeEldestEntry( Map.Entry eldest ) {
                // 当集合中元素数量超过缓存允许的最大限制时,移除最老(根据访问顺序或插入顺序判断)的元素
                boolean remove = (size() > MAX_SIZE_GA_VERSIONS_CACHE);

                //  根据 backingLRUMap 存储的索引删除真实缓存 artifactMap 里面的 KieModule
                if( remove ) {
                    artifactMap.remove(eldest.getKey());
                }
                return remove;
            }
        };

        @Override
        public KieModule put( ComparableVersion key, KieModule value ) {
            // 向索引队列里面插入一个最新的索引
            backingLRUMap.put(key, PRESENT);
            // 向真实缓存 artifactMap 中插入最新的 KieModule,此处不可以使用 artifactMap 直接调用,会造成死循环
            return super.put(key, value);
        }

    };
    return newArtifactMap;
}

可以看到注释比代码还要多,由此可见这段代码的逻辑较之前面的代码复杂了很多。此段代码我们从变量入手来剖析它:

  1. newArtifactMap:这是此方法的最终结果,用来存储同 GA 的 KieModule 的集合,每个 KieModule 需要按照版本号进行排序,所以将其类型定义为 TreeMap。由于同一个集合不能同时为 TreeMap 与 LinkedHashMap,所以它借用了 backingLRUMap 来帮它完成 LRU 的功能。
  2. backingLRUMap:存储 KieModule 在 newArtifactMap 中的索引,即 ComparableVersion,对其进行 LRU 排序,协助 newArtifactMap 完成限制缓存大小的功能。
  3. artifactMap:backingLRUMap 为一个内部类实现,但它需要获取对其外部类实例的一个操作权限,所以它必须持有外部类实例的引用,因此,artifactMap 的诞生完美地解决掉了这个问题。

程序虽然短小,但设计十分精巧,令人叹服。

你可能感兴趣的:(drools)