自动装配
基于 SpringBoot 的实现方式自然使用到了 starter 机制,核心代码在
liteflow-spring-boot-starter
包下
首先来看 spring.factories
文件
1 2 3 org.springframework.boot.autoconfigure.EnableAutoConfiguration =\ com.yomahub.liteflow.springboot.config.LiteflowPropertyAutoConfiguration,\ com.yomahub.liteflow.springboot.config.LiteflowMainAutoConfiguration
可以看到一共有两个重要的自动装配的配置类:
LiteflowPropertyAutoConfiguration :配置属性的解析,封装为
LiteflowConfig
实例
LiteflowMainAutoConfiguration :LiteFlow
的核心功能对象在该配置类下实例化
SpringAware :Spring ApplicationContext
的获取与封装,便于后续业务中获取 bean 的操作
ComponentScanner :组件扫描器,用于扫描与封装
NodeComponent
节点的实现类
FlowExecutor :规则执行器
LiteflowExecutorInit :设置服务启动时初始化配置生效,用于启动时主动进行初始化操作
MonitorBus :监控类元数据
LiteflowPropertyAutoConfiguration
总的来说就是对配置属性的合并和封装,实例化
LiteflowConfig
,便于后续业务的使用
合并了 liteflow
配置和 liteflow.monitor
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Configuration @EnableConfigurationProperties({LiteflowProperty.class, LiteflowMonitorProperty.class}) @PropertySource( name = "Liteflow Default Properties", value = "classpath:/META-INF/liteflow-default.properties") public class LiteflowPropertyAutoConfiguration { @Bean public LiteflowConfig liteflowConfig (LiteflowProperty property, LiteflowMonitorProperty liteflowMonitorProperty) { LiteflowConfig liteflowConfig = new LiteflowConfig (); liteflowConfig.setPrintExecutionLog(property.isPrintExecutionLog()); liteflowConfig.setSubstituteCmpClass(property.getSubstituteCmpClass()); return liteflowConfig; } }
依赖前置 LiteflowProperty
和
LiteflowMonitorProperty
bean,即流程配置参数和监控器的配置参数类
LiteflowMainAutoConfiguration
配置核心的各种实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Configuration @AutoConfigureAfter({LiteflowPropertyAutoConfiguration.class}) @ConditionalOnBean(LiteflowConfig.class) @ConditionalOnProperty(prefix = "liteflow", name = "enable", havingValue = "true") @Import(SpringAware.class) public class LiteflowMainAutoConfiguration { @Bean public ComponentScanner componentScanner (LiteflowConfig liteflowConfig, SpringAware springAware) { return new ComponentScanner (liteflowConfig); } @Bean public FlowExecutor flowExecutor (LiteflowConfig liteflowConfig, SpringAware springAware) { FlowExecutor flowExecutor = new FlowExecutor (); flowExecutor.setLiteflowConfig(liteflowConfig); return flowExecutor; } @Bean @ConditionalOnProperty(prefix = "liteflow", name = "parse-on-start", havingValue = "true") public LiteflowExecutorInit liteflowExecutorInit (FlowExecutor flowExecutor) { return new LiteflowExecutorInit (flowExecutor); } @Bean("monitorBus") @ConditionalOnProperty(prefix = "liteflow", name = "monitor.enable-log", havingValue = "true") public MonitorBus monitorBus (LiteflowConfig liteflowConfig, SpringAware springAware) { return new MonitorBus (liteflowConfig); } }
可以看出 bean 的加载顺序:LiteflowPropertyAutoConfiguration ->
LiteflowConfig -> ComponentScanner & FlowExecutor &
MonitorBus -> LiteflowExecutorInit
LiteflowExecutorInit 和 MonitorBus 依赖不同的配置条件,其中
liteflow.parse-on-start
默认是
true,liteflow.monitor.enable-log
默认是 false
ComponentScanner
ComponentScanner
在项目中也被缩写为
cmp
,主要是依赖 Spring 的 BeanPostProcessor
来对 NodeComponent
的各种实现进行发现、保存、扩展等一系列操作
构造方法
配置类中使用了其构造方法进行实例化
1 2 3 4 5 6 7 public ComponentScanner (LiteflowConfig liteflowConfig) { this .liteflowConfig = liteflowConfig; if (liteflowConfig.getPrintBanner()) { LOGOPrinter.print(); } }
构造方法中主要进行了两个操作:
填充属性 liteflowConfig
打印 LOGO(可以看出这个是启动的第一个 bean);LOGOPrinter
就是一个打印 LOGO 的静态方法工具类
BeanPostProcessor
既然实现了 BeanPostProcessor
接口,那么重点就需要关注其对于 before 和 after 钩子方法的实现
1 2 3 4 5 6 7 8 9 public class ComponentScanner implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { return bean; } }
before 方法是一个空实现,重点关注 after 方法
声明式组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 if (LiteFlowProxyUtil.isDeclareCmp(bean.getClass())) { LOG.info("proxy component[{}] has been found" , beanName); List<NodeComponent> nodeComponents = LiteFlowProxyUtil.proxy2NodeComponent(bean, beanName); nodeComponents.forEach( nodeComponent -> { String nodeId = nodeComponent.getNodeId(); nodeId = StrUtil.isEmpty(nodeId) ? beanName : nodeId; nodeComponentMap.put(nodeId, nodeComponent); } ); if (nodeComponents.size() == 1 ) { return nodeComponents.get(0 ); } return bean; }
如何判断是否是一个声明式组件,实现方法是
LiteFlowProxyUtil.isDeclareCmp
1 2 3 4 5 6 7 8 9 10 11 public static boolean isDeclareCmp (Class<?> clazz) { Class<?> targetClass = getUserClass(clazz); return Arrays.stream(targetClass.getMethods()) .anyMatch(method -> method.getAnnotation(LiteflowMethod.class) != null ); }
获取真实的用户定义类
判断类中的方法是否标记了 @LiteflowMethod
实现类组件
实现类组件有两种:
继承自 NodeComponent
的逻辑组件
实现自 ICmpAroundAspect
的切面组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (NodeComponent.class.isAssignableFrom(clazz)) { LOG.info("component[{}] has been found" , beanName); NodeComponent nodeComponent = (NodeComponent) bean; nodeComponentMap.put(beanName, nodeComponent); return nodeComponent; } if (ICmpAroundAspect.class.isAssignableFrom(clazz)) { LOG.info("component aspect implement[{}] has been found" , beanName); cmpAroundAspect = (ICmpAroundAspect) bean; return cmpAroundAspect; }
扫描组件的逻辑都是这类方式:
判断是否是其实现
转型
保存到对应引用或对象
可以看到对于逻辑组件,是一个 map
结构,存在多个组件;而对于切面组件,只能存在一个(事实上这段逻辑没有对实现进行判断,也就是说如果有多个实现的
bean 会进行覆盖,测试中也发现是这样)
脚本组件
脚本组件也有两种:
@ScriptBean
修饰的脚本 bean
@ScriptMethod
修饰的脚本方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ScriptBean scriptBean = AnnoUtil.getAnnotation(clazz, ScriptBean.class);if (ObjectUtil.isNotNull(scriptBean)) { ScriptBeanProxy proxy = new ScriptBeanProxy (bean, clazz, scriptBean); ScriptBeanManager.addScriptBean(scriptBean.value(), proxy.getProxyScriptBean()); return bean; } List<Method> scriptMethods = Arrays.stream(clazz.getMethods()).filter(method -> { ScriptMethod scriptMethod = AnnoUtil.getAnnotation(method, ScriptMethod.class); return ObjectUtil.isNotNull(scriptMethod) && StrUtil.isNotEmpty(scriptMethod.value()); }).collect(Collectors.toList()); if (CollUtil.isNotEmpty(scriptMethods)) { Map<String, List<Method>> scriptMethodsGroupByValue = CollStreamUtil.groupBy(scriptMethods, method -> { ScriptMethod scriptMethod = AnnoUtil.getAnnotation(method, ScriptMethod.class); return scriptMethod.value(); }, Collectors.toList()); for (Map.Entry<String, List<Method>> entry : scriptMethodsGroupByValue.entrySet()) { String key = entry.getKey(); List<Method> methods = entry.getValue(); ScriptMethodProxy proxy = new ScriptMethodProxy (bean, clazz, methods); ScriptBeanManager.addScriptBean(key, proxy.getProxyScriptMethod()); } return bean; }
主要是将不同的脚本形式包装成代理类,转换成 bean 后放入
ScriptBeanManager
进行管理
转换的核心在 getProxyScriptMethod
方法,这个会在脚本相关源码中进一步分析
FlowExecutor
流程规则主要执行器,也是一个规则表执行的主要入口
这里主要关注其构造流程,初始化在后面的
LiteflowExecutorInit
中讲解
构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public FlowExecutor () { FlowExecutorHolder.setHolder(this ); DataBus.init(); } public FlowExecutor (LiteflowConfig liteflowConfig) { this .liteflowConfig = liteflowConfig; LiteflowConfigGetter.setLiteflowConfig(liteflowConfig); FlowExecutorHolder.setHolder(this ); if (BooleanUtil.isTrue(liteflowConfig.isParseOnStart())) { this .init(true ); } DataBus.init(); }
对于 Spring 的启动方式,使用了无参构造,随后将 liteflowConfig 用 set
方法进行赋值,带有参数的构造方法应该是为了非 Spring 环境准备的,因为在
Spring 环境中 liteflowConfig 使用了 properties 的机制,而 FlowExecutor
通过 @Bean
进行实例化
可以看到无参构造中主要进行了 DataBus.init()
操作
DataBus
数据 BUS,主要用来管理 Slot,用以分配和回收
Slot
即整个规则流程调用时的管理者,保存了整个规则进行中的数据、组件步骤等信息,每一次调用会生成新的
Slot,并且互相隔离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private static ConcurrentHashMap<Integer, Slot> SLOTS;private static ConcurrentLinkedQueue<Integer> QUEUE;public static void init () { if (MapUtil.isEmpty(SLOTS)) { LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); currentIndexMaxValue = liteflowConfig.getSlotSize(); SLOTS = new ConcurrentHashMap <>(); QUEUE = IntStream.range(0 , currentIndexMaxValue) .boxed() .collect(Collectors.toCollection(ConcurrentLinkedQueue::new )); } }
这里 SLOTS 用来保存分配的序号和 Slot 对象,QUEUE 则是用来保存
index
即在并发的情况下,能够分配的运行资格由 QUEUE 控制,QUEUE
会根据并发情况进行扩容,具体的资格由 0 ~ n 的 int 值来表示
LiteflowExecutorInit
执行器初始化类,主要用于在启动时执行执行器的初始化方法,避免在运行执行器时第一次初始化而耗费时间
该类实例化的意义就是控制上面对象 FlowExecutor
的初始化操作,如果不在配置中设置启动初始化,则 FlowExecutor
的初始化工作会放在调用时进行
@ConditionalOnProperty(prefix = "liteflow", name = "parse-on-start", havingValue = "true")
构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class LiteflowExecutorInit implements InitializingBean { private final FlowExecutor flowExecutor; public LiteflowExecutorInit (FlowExecutor flowExecutor) { this .flowExecutor = flowExecutor; } @Override public void afterPropertiesSet () throws Exception { flowExecutor.init(true ); } }
该类实现了 Spring 钩子 InitializingBean,afterPropertiesSet 进行
FlowExecutor
的初始化操作
FlowExecutor 的 init
LiteflowExecutorInit
的实例化是为了 init
FlowExecutor
,而 FlowExecutor
的初始化主要目的是 parse 规则文件
ID 生成器
1 2 IdGeneratorHolder.init();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public static void init () { try { INSTANCE = new IdGeneratorHolder (); LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); String requestIdGeneratorClass = liteflowConfig.getRequestIdGeneratorClass(); RequestIdGenerator requestIdGenerator; if (StrUtil.isBlank(requestIdGeneratorClass)) { requestIdGenerator = new DefaultRequestIdGenerator (); } else { Class<RequestIdGenerator> idGenerateClass = (Class<RequestIdGenerator>) Class .forName(requestIdGeneratorClass); requestIdGenerator = ContextAwareHolder.loadContextAware().registerBean(idGenerateClass); } INSTANCE.setRequestIdGenerator(requestIdGenerator); } catch (Exception e) { throw new RequestIdGeneratorException (e.getMessage()); } }
IdGeneratorHolder
用于保存实例化后的 ID 生成器
RequestIdGenerator
,是一个单例
基本流程就是获取配置项上的生成器类路径,然后通过反射进行实例化,保存到
IdGeneratorHolder
这个角色中,如果没有进行配置则使用默认实现
DefaultRequestIdGenerator
默认实现 DefaultRequestIdGenerator
1 2 3 4 5 6 public class DefaultRequestIdGenerator implements RequestIdGenerator { @Override public String generate () { return IdUtil.fastSimpleUUID(); } }
hutool 下的简单 UUID 生成
规则文件源路径
在上面流程中加载了逻辑组件,接下来就是解析规则表
规则路径为空
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 String ruleSource = liteflowConfig.getRuleSource();if (StrUtil.isBlank(ruleSource)) { ServiceLoader<ParserClassNameSpi> loader = ServiceLoader.load(ParserClassNameSpi.class); Iterator<ParserClassNameSpi> it = loader.iterator(); if (it.hasNext()) { ParserClassNameSpi parserClassNameSpi = it.next(); ruleSource = "el_xml:" + parserClassNameSpi.getSpiClassName(); liteflowConfig.setRuleSource(ruleSource); } else { return ; } }
这一块逻辑是在判断当配置文件的规则源路径为空的情况,此时会使用 SPI
工具 ServiceLoader
去加载自定义实现的规则加载器去放置新的规则路径
项目文件和本地文件
1 2 3 4 5 6 7 8 9 10 List<String> sourceRulePathList; if (ReUtil.contains(PREFIX_FORMAT_CONFIG_REGEX, ruleSource)) { sourceRulePathList = ListUtil.toList(ruleSource); } else { String afterHandleRuleSource = ruleSource.replace(StrUtil.SPACE, StrUtil.EMPTY); sourceRulePathList = ListUtil.toList(afterHandleRuleSource.split(",|;" )); }
LiteFlow
的特性可以支持从本地文件加载规则,所以在此处判断规则是来源于项目文件还是本地文件
根据规则前缀
String PREFIX_FORMAT_CONFIG_REGEX = "xml:|json:|yml:|el_xml:|el_json:|el_yml:"
的正则来匹配;如果是本地文件则根据 ,|;
来进行分割
将规则文件的路径都放置在 sourceRulePathList
这个集合中
规则文件解析
获取规则源后要对规则文件进行解析
复合规则配置
这里应该是实现不同格式规则加载的特性,需要进行相关配置
supportMultipleType
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 FlowParser parser = null ;Set<String> parserNameSet = new HashSet <>(); List<String> rulePathList = new ArrayList <>(); for (String path : sourceRulePathList) { try { parser = FlowParserProvider.lookup(path); parserNameSet.add(parser.getClass().getName()); path = ReUtil.replaceAll(path, PREFIX_FORMAT_CONFIG_REGEX, "" ); rulePathList.add(path); if (BooleanUtil.isTrue(liteflowConfig.isSupportMultipleType())) { parser.parseMain(ListUtil.toList(path)); } } catch (CyclicDependencyException e) { LOG.error(e.getMessage()); throw e; } catch (Exception e) { String errorMsg = StrUtil.format("init flow executor cause error for path {},reason:{}" , path, e.getMessage()); LOG.error(e.getMessage(), e); throw new FlowExecutorNotInitException (errorMsg); } }
流程如下:
遍历规则路径,查找对应的解析器
记录解析器类名(主要是用于后续非多类型解析器判断使用)
路径放入规则路径集合
如果支持多类型配置文件解析,则直接进行解析
parser.parseMain(ListUtil.toList(path))
catch 异常,打印日志
单类型配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 if (BooleanUtil.isFalse(liteflowConfig.isSupportMultipleType())) { if (parserNameSet.size() > 1 ) { String errorMsg = "cannot have multiple different parsers" ; LOG.error(errorMsg); throw new MultipleParsersException (errorMsg); } try { if (parser != null ) { parser.parseMain(rulePathList); } else { throw new ConfigErrorException ("parse error, please check liteflow config property" ); } } catch (CyclicDependencyException e) { LOG.error(e.getMessage(), e); LOG.error(e.getMessage()); throw e; } catch (ChainDuplicateException e) { LOG.error(e.getMessage(), e); throw e; } catch (Exception e) { String errorMsg = StrUtil.format("init flow executor cause error for path {},reason: {}" , rulePathList, e.getMessage()); LOG.error(e.getMessage(), e); throw new FlowExecutorNotInitException (errorMsg); } }
单类型的流程主要针对解析器数量进行了判断,也是使用
parser.parseMain(ListUtil.toList(path))
进入解析流程
解析器
这里主要先来看 LocalXmlFlowELParser
的实现
1 2 3 4 5 6 7 8 9 public class LocalXmlFlowELParser extends XmlFlowELParser { @Override public void parseMain (List<String> pathList) throws Exception { List<String> contentList = PathContentParserHolder.loadContextAware().parseContent(pathList); parse(contentList); } }
PathContentParserHolder.loadContextAware().parseContent(pathList)
的操作用于获取文件中的内容,即规则的 EL
表达式等(就是读文件,不过会根据不同环境选择不同的实现)
最后执行 BaseXmlFlowParser
抽象类提供的
parse
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public void parse (List<String> contentList) throws Exception { if (CollectionUtil.isEmpty(contentList)) { return ; } List<Document> documentList = ListUtil.toList(); for (String content : contentList) { Document document = DocumentHelper.parseText(content); documentList.add(document); } ParserHelper.parseNodeDocument(documentList); ParserHelper.parseChainDocument(documentList, CHAIN_NAME_SET, this ::parseOneChain); }
ParserHelper
以 XML
格式的规则表达为例
解析 node
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public static void parseNodeDocument (List<Document> documentList) { for (Document document : documentList) { Element rootElement = document.getRootElement(); Element nodesElement = rootElement.element(NODES); if (ObjectUtil.isNotNull(nodesElement)) { List<Element> nodeList = nodesElement.elements(NODE); String id, name, clazz, type, script, file, language; for (Element e : nodeList) { id = e.attributeValue(ID); name = e.attributeValue(NAME); clazz = e.attributeValue(_CLASS); type = e.attributeValue(TYPE); script = e.getText(); file = e.attributeValue(FILE); language = e.attributeValue(LANGUAGE); NodePropBean nodePropBean = new NodePropBean ().setId(id) .setName(name) .setClazz(clazz) .setScript(script) .setType(type) .setFile(file) .setLanguage(language); ParserHelper.buildNode(nodePropBean); } } } }
从配置的 XML
规则中解析标签
找到 nodes
的标签;name 常量定义在
ChainConstant.NODES
遍历 nodes
下面的每一个 node
标签,构建
NodePropBean
node 对象
ParserHelper.buildNode(nodePropBean)
开始解析节点,解析完成后会放置在 FlowBus
中(解析过程中还在分辨是普通节点还是脚本节点,这个放到脚本相关源码中再看)
解析 chain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public static void parseChainDocument (List<Document> documentList, Set<String> chainNameSet, Consumer<Element> parseOneChainConsumer) { documentList.forEach(document -> { List<Element> chainList = document.getRootElement().elements(CHAIN); chainList.forEach(e -> { String chainName = Optional.ofNullable(e.attributeValue(ID)).orElse(e.attributeValue(NAME)); if (!chainNameSet.add(chainName)) { throw new ChainDuplicateException (String.format("[chain name duplicate] chainName=%s" , chainName)); } FlowBus.addChain(chainName); }); }); chainNameSet.clear(); for (Document document : documentList) { Element rootElement = document.getRootElement(); List<Element> chainList = rootElement.elements(CHAIN); chainList.forEach(parseOneChainConsumer); } }
从 XML
规则中解析 chain
标签
遍历标签处理 chain 数据
获取 chainName,规则为如果没有配置独立的 id 就使用 name
加入 chainNameSet,如果 name
重复则抛出异常(这种方式无法处理多文件的重复问题,我理解带来的影响就是会导致解析的
chain 被覆盖)
先将 name 加入元数据管理类
FlowBus
;这个流程是预装载,其元数据管理中的 map value
是占位对象
遍历 chain 标签,调用传入的 Consumer
方法开始解析;Consumer
方法为 Parser
实现的
parseOneChain
方法
EL 规则组装器
1 2 3 4 5 6 7 public static void parseOneChainEl (JsonNode chainNode) { String chainId = Optional.ofNullable(chainNode.get(ID)).orElse(chainNode.get(NAME)).textValue(); String el = chainNode.get(VALUE).textValue(); LiteFlowChainELBuilder chainELBuilder = LiteFlowChainELBuilder.createChain().setChainId(chainId); chainELBuilder.setEL(el).build(); }
调用 parseOneChainEl
后主要是获取 el 规则,创建
LiteFlowChainELBuilder
对象后在 setEL
的步骤中会进行 EL 规则的解析,最终返回
List<Condition> conditionList
规则的解析使用了阿里开源的 QLExpress,后面会单独一篇来讲解
基本上到这里,相关的执行组件就已经初始化完毕了
后续具体的执行就是获取 Chain
对象的
conditionList
,遍历 list 进行执行
MonitorBus
监控类元数据,打印执行器类
需要配置 condition 参数 monitor.enable-log
构造方法
1 2 3 4 5 6 7 8 9 10 11 private final ScheduledExecutorService printLogScheduler = Executors.newScheduledThreadPool(1 );public MonitorBus (LiteflowConfig liteflowConfig) { this .liteflowConfig = liteflowConfig; if (BooleanUtil.isTrue(liteflowConfig.getEnableLog())) { this .printLogScheduler.scheduleAtFixedRate(new MonitorTimeTask (this ), liteflowConfig.getDelay(), liteflowConfig.getPeriod(), TimeUnit.MILLISECONDS); } }
向定时线程池设置任务,任务为
new MonitorTimeTask(this)
,是一个
TimerTask
1 2 3 4 5 6 7 8 9 10 11 12 13 public class MonitorTimeTask extends TimerTask { private MonitorBus monitorBus; public MonitorTimeTask (MonitorBus monitorBus) { this .monitorBus = monitorBus; } @Override public void run () { monitorBus.printStatistics(); } }
run
方法为调用
MonitorBus.printStatistics
打印统计数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 private final ConcurrentHashMap<String, BoundedPriorityBlockingQueue<CompStatistics>> statisticsMap = new ConcurrentHashMap <>();public void printStatistics () { try { Map<String, BigDecimal> compAverageTimeSpent = new HashMap <String, BigDecimal>(); for (Entry<String, BoundedPriorityBlockingQueue<CompStatistics>> entry : statisticsMap.entrySet()) { long totalTimeSpent = 0 ; for (CompStatistics statistics : entry.getValue()) { totalTimeSpent += statistics.getTimeSpent(); } compAverageTimeSpent.put(entry.getKey(), new BigDecimal (totalTimeSpent) .divide(new BigDecimal (entry.getValue().size()), 2 , RoundingMode.HALF_UP)); } List<Entry<String, BigDecimal>> compAverageTimeSpentEntryList = new ArrayList <>( compAverageTimeSpent.entrySet()); Collections.sort(compAverageTimeSpentEntryList, (o1, o2) -> o2.getValue().compareTo(o1.getValue())); StringBuilder logStr = new StringBuilder (); logStr.append("以下为LiteFlow中间件统计信息:\n" ); logStr.append("======================================================================================\n" ); logStr.append("===================================SLOT INFO==========================================\n" ); logStr.append(MessageFormat.format("SLOT TOTAL SIZE : {0}\n" , liteflowConfig.getSlotSize())); logStr.append(MessageFormat.format("SLOT OCCUPY COUNT : {0}\n" , DataBus.OCCUPY_COUNT)); logStr.append("===============================TIME AVERAGE SPENT=====================================\n" ); for (Entry<String, BigDecimal> entry : compAverageTimeSpentEntryList) { logStr.append(MessageFormat.format("COMPONENT[{0}] AVERAGE TIME SPENT : {1}\n" , entry.getKey(), entry.getValue())); } logStr.append("======================================================================================\n" ); LOG.info(logStr.toString()); } catch (Exception e) { LOG.error("print statistics cause error" , e); } }
statisticsMap
是用来保存组件执行时上报的数据,在
printStatistics
方法中对数据进行整理和打印
主要数据:
slot 槽位现在的大小(用来衡量最大并发度)
slot 当前的占用数量(展示当前并发度)
各个组件的平均耗时