diff --git a/liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowAutoConfiguration.java b/liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowAutoConfiguration.java index f0e81d247..c14d4c11e 100644 --- a/liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowAutoConfiguration.java +++ b/liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowAutoConfiguration.java @@ -51,6 +51,8 @@ public class LiteflowAutoConfiguration { liteflowConfig.setGlobalThreadPoolQueueSize(property.getGlobalThreadPoolQueueSize()); liteflowConfig.setWhenThreadPoolIsolate(property.getWhenThreadPoolIsolate()); liteflowConfig.setEnableNodeInstanceId(property.isEnableNodeInstanceId()); + liteflowConfig.setEnableRuleCache(property.getRuleCache().getEnabled()); + liteflowConfig.setRuleCacheCapacity(property.getRuleCache().getCapacity()); return liteflowConfig; } diff --git a/liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowProperty.java b/liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowProperty.java index cca6b3542..1319a8d1a 100644 --- a/liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowProperty.java +++ b/liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowProperty.java @@ -101,6 +101,33 @@ public class LiteflowProperty { //是否启用节点实例ID private boolean enableNodeInstanceId; + // 规则缓存配置 + private RuleCache ruleCache; + + public static class RuleCache { + // 是否启用规则缓存 + private Boolean enabled; + + // 规则缓存容量 + private Integer capacity; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Integer getCapacity() { + return capacity; + } + + public void setCapacity(Integer capacity) { + this.capacity = capacity; + } + } + public boolean isEnable() { return enable; } @@ -341,4 +368,12 @@ public class LiteflowProperty { public void setEnableNodeInstanceId(boolean enableNodeInstanceId) { this.enableNodeInstanceId = enableNodeInstanceId; } + + public RuleCache getRuleCache() { + return ruleCache; + } + + public void setRuleCache(RuleCache ruleCache) { + this.ruleCache = ruleCache; + } } diff --git a/liteflow-solon-plugin/src/main/resources/META-INF/liteflow-default.properties b/liteflow-solon-plugin/src/main/resources/META-INF/liteflow-default.properties index c3dbf73d5..c3ef43c84 100644 --- a/liteflow-solon-plugin/src/main/resources/META-INF/liteflow-default.properties +++ b/liteflow-solon-plugin/src/main/resources/META-INF/liteflow-default.properties @@ -19,3 +19,4 @@ liteflow.global-thread-pool-size=16 liteflow.global-thread-pool-queue-size=512 liteflow.global-thread-pool-executor-class=com.yomahub.liteflow.thread.LiteFlowDefaultGlobalExecutorBuilder liteflow.enable-node-instance-id=true +liteflow.rule-cache.enabled=false diff --git a/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/RuleCacheSolonTest.java b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/RuleCacheSolonTest.java new file mode 100644 index 000000000..1defd6013 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/RuleCacheSolonTest.java @@ -0,0 +1,206 @@ +package com.yomahub.liteflow.test.ruleCache; + +import cn.hutool.core.collection.CollUtil; +import com.github.benmanes.caffeine.cache.Cache; +import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder; +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.exception.ChainNotFoundException; +import com.yomahub.liteflow.flow.FlowBus; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.flow.element.Chain; +import com.yomahub.liteflow.flow.element.Condition; +import com.yomahub.liteflow.lifecycle.LifeCycleHolder; +import com.yomahub.liteflow.lifecycle.PostProcessFlowExecuteLifeCycle; +import com.yomahub.liteflow.lifecycle.impl.RuleCacheLifeCycle; +import com.yomahub.liteflow.test.BaseTest; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.noear.solon.annotation.Import; +import org.noear.solon.annotation.Inject; +import org.noear.solon.test.SolonTest; +import org.springframework.test.context.TestPropertySource; + +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +/** + * Solon环境下规则缓存测试 + * @author DaleLee + * @since 2.13.0 + */ +@SolonTest +@Import(profiles="classpath:/ruleCache/application.properties") +public class RuleCacheSolonTest extends BaseTest { + + @Inject + private FlowExecutor flowExecutor; + + @BeforeEach + public void reload() { + flowExecutor.reloadRule(); + // 清空缓存 + Cache cache = getCache(); + cache.invalidateAll(); + cache.cleanUp(); + } + + // 测试chain被淘汰 + @Test + public void testRuleCache1() { + // 加满缓存 + loadCache(); + // 缓存快照 + HashSet<@NonNull String> strings = CollUtil.newHashSet(getCache().asMap().keySet()); + LiteflowResponse response = flowExecutor.execute2Resp("chain6", "arg"); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals("c==>b", response.getExecuteStepStr()); + // 获得被淘汰chain + String chainId = getEvictedChain(strings); + testEvicted(chainId); + // 测试被淘汰的chain仍可正常执行 + response = flowExecutor.execute2Resp(chainId, "arg"); + Assertions.assertTrue(response.isSuccess()); + } + + // 测试缓存数量 + @Test + public void testRuleCache2() { + // 确保至少执行过5个不同的chain + loadCache(); + // 随机执行chain + loadCache(100); + // 等待缓存淘汰 + getCache().cleanUp(); + // 测试只有5个chain被编译 + int count = 0; + for (Chain chain : FlowBus.getChainMap().values()) { + List conditionList = chain.getConditionList(); + if (chain.isCompiled()) { + Assertions.assertTrue(CollUtil.isNotEmpty(conditionList)); + count++; + } else { + Assertions.assertNull(conditionList); + } + } + Assertions.assertEquals(5, count); + } + + // 测试开启规则缓存后,进入缓存的chain可以正常被更新 + @Test + public void testRuleCache3() { + loadCache(); + // 缓存快照 + HashSet<@NonNull String> strings = CollUtil.newHashSet(getCache().asMap().keySet()); + LiteflowResponse response = flowExecutor.execute2Resp("chain7", "arg"); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals("x==>a==>b", response.getExecuteStepStr()); + // chain7进入缓存 + Assertions.assertTrue(getCache().asMap().containsKey("chain7")); + // 获得被淘汰chain + String chainId = getEvictedChain(strings); + testEvicted(chainId); + // 更新chain7 + LiteFlowChainELBuilder + .createChain() + .setChainId("chain7") + .setEL("THEN(a, b, c)") + .build(); + // 重新执行chain7 + response = flowExecutor.execute2Resp("chain7", "arg"); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals("a==>b==>c", response.getExecuteStepStr()); + } + + // 测试开启规则缓存后,进入缓存的chain被移除后无法执行 + @Test + public void testRuleCache4() { + loadCache(); + // 缓存快照 + HashSet<@NonNull String> strings = CollUtil.newHashSet(getCache().asMap().keySet()); + LiteflowResponse response = flowExecutor.execute2Resp("chain7", "arg"); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals("x==>a==>b", response.getExecuteStepStr()); + // chain7进入缓存 + Assertions.assertTrue(getCache().asMap().containsKey("chain7")); + // 获得被淘汰chain + String chainId = getEvictedChain(strings); + testEvicted(chainId); + // 手动移除chain7 + FlowBus.removeChain("chain7"); + response = flowExecutor.execute2Resp("chain7", "arg"); + Assertions.assertFalse(response.isSuccess()); + Assertions.assertEquals(ChainNotFoundException.class, response.getCause().getClass()); + } + + // 测试并发下,正在执行的chain被淘汰仍能执行 + @Test + public void testRuleCache5() throws InterruptedException { + // 模拟清空编译好的chain + Thread thread = new Thread(()-> { + Chain chain1 = FlowBus.getChain("chain1"); + chain1.setCompiled(true); + chain1.setConditionList(null); + }); + thread.start(); + thread.join(); + LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg"); + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals("a==>b", response.getExecuteStepStr()); + } + + + // 加载缓存, chain1~chain5 + private void loadCache() { + // 容量上限为5 + for (int i = 1; i <= 5; i++) { + flowExecutor.execute2Resp("chain" + i); + } + } + + private void loadCache(int count) { + // 随机执行chain + Random random = new Random(); + for (int i = 0; i < count; i++) { + int id = random.nextInt(10) + 1; + flowExecutor.execute2Resp("chain" + id); + } + } + + // 测试 chain 被淘汰 + private void testEvicted(String chanId) { + Chain chain = FlowBus.getChain(chanId); + getCache().cleanUp(); + // 测试缓存中不存在 + Assertions.assertFalse(getCache().asMap().containsKey(chanId)); + // 测试chain被设置为未编译 + Assertions.assertFalse(chain.isCompiled()); + Assertions.assertNull(chain.getConditionList()); + } + + public Cache getCache() { + List lifeCycleList + = LifeCycleHolder.getPostProcessFlowExecuteLifeCycleList(); + for (PostProcessFlowExecuteLifeCycle lifeCycle : lifeCycleList) { + if (lifeCycle.getClass().equals(RuleCacheLifeCycle.class)) { + RuleCacheLifeCycle ruleCacheLifeCycle = (RuleCacheLifeCycle) lifeCycle; + return ruleCacheLifeCycle.getCache(); + } + } + return null; + } + + // 获得淘汰的chain,传入淘汰前的chain集合 + // 确保只有一个被淘汰时使用 + String getEvictedChain(Set set) { + Cache cache = getCache(); + cache.cleanUp(); + Set<@NonNull String> strings = cache.asMap().keySet(); + set.removeAll(strings); + Assertions.assertEquals(1, set.size()); + return set.iterator().next(); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/ACmp.java b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/ACmp.java new file mode 100644 index 000000000..da4be1512 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/ACmp.java @@ -0,0 +1,20 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * @author Bryan.Zhang + * @email weenyc31@163.com + * @Date 2020/4/1 + */ +package com.yomahub.liteflow.test.ruleCache.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.noear.solon.annotation.Component; + +@Component("a") +public class ACmp extends NodeComponent { + + @Override + public void process() { + System.out.println("ACmp executed!"); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/BCmp.java b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/BCmp.java new file mode 100644 index 000000000..7f957f293 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/BCmp.java @@ -0,0 +1,21 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * @author Bryan.Zhang + * @email weenyc31@163.com + * @Date 2020/4/1 + */ +package com.yomahub.liteflow.test.ruleCache.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.noear.solon.annotation.Component; + +@Component("b") +public class BCmp extends NodeComponent { + + @Override + public void process() { + System.out.println("BCmp executed!"); + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/CCmp.java b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/CCmp.java new file mode 100644 index 000000000..c49a987e9 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/CCmp.java @@ -0,0 +1,20 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * @author Bryan.Zhang + * @email weenyc31@163.com + * @Date 2020/4/1 + */ +package com.yomahub.liteflow.test.ruleCache.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.noear.solon.annotation.Component; + +@Component("c") +public class CCmp extends NodeComponent { + + @Override + public void process() { + System.out.println("CCmp executed!"); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/XCmp.java b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/XCmp.java new file mode 100644 index 000000000..73b375e01 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/ruleCache/cmp/XCmp.java @@ -0,0 +1,12 @@ +package com.yomahub.liteflow.test.ruleCache.cmp; + +import com.yomahub.liteflow.core.NodeBooleanComponent; +import org.noear.solon.annotation.Component; + +@Component("x") +public class XCmp extends NodeBooleanComponent { + @Override + public boolean processBoolean() throws Exception { + return true; + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/ruleCache/application.properties b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/ruleCache/application.properties new file mode 100644 index 000000000..cb4066d3c --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/ruleCache/application.properties @@ -0,0 +1,3 @@ +liteflow.rule-source=ruleCache/flow.el.xml +liteflow.rule-cache.enabled=true +liteflow.rule-cache.capacity=5 \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/ruleCache/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/ruleCache/flow.el.xml new file mode 100644 index 000000000..11acd1cd4 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/ruleCache/flow.el.xml @@ -0,0 +1,43 @@ + + + + THEN(a, b); + + + + THEN(a, c); + + + + THEN(b, a); + + + + THEN(b, c); + + + + THEN(c, a); + + + + THEN(c, b); + + + + THEN(IF(x, a), b); + + + + THEN(IF(x, a), c); + + + + WHEN(a, b, c); + + + + FOR(5).DO(THEN(a, b, c)); + + + \ No newline at end of file