Hippo4j 动态线程池监控优化改造
一、本次优化点
- 「线程池监控」查询范围由原先的 30 分钟改为 60分钟且可配置化;
- 「线程池监控」增加时间选择器,可选范围 10 天;
- 「线程池监控」数据库数据持久化 10 天且可配置;
二、优化步骤
前端部分:增加时间选择器组件、设置默认时间、中英文转译、传递至后端
-
修改页面路径:/hippo4j-ui/src/views/hippo4j/monitor/index.vue,在 class 为「
filter-container
」的大块后面接着写<div> <el-date-picker v-model="listQuery.entTime" type="datetime" placeholder="请选择截止时间" :picker-options="pickerOptions"> </el-date-picker> </div>
-
选择地址及端口的选择器代码修改
<el-select v-model="listQuery.identify" :placeholder="$t('threadPoolMonitor.ipPortRequired')" style="width: 220px" filterable class="filter-item" @change="identifyChange"> <el-option v-for="item in identifyOptions" :key="item.key" :label="item.display_name" :value="item.key" /> </el-select>
-
「data」块更改
data() { return { pickerOptions: { disabledDate(time) { let curDateMillisecond = (new Date()).getTime(); let tenDaysMillisecond = 10 * 24 * 3600 * 1000; let tenDaysTime = curDateMillisecond - tenDaysMillisecond; return time.getTime() > Date.now() || time.getTime() < tenDaysTime;; } }, listQuery: { endTime: null }, endTime: null }; }
-
「methods」方法更改
identifyChange(identify) { if (identify) { this.endTime = new Date(); } }, fetchData() { if (!this.listQuery.tenantId) { this.$message.warning(this.$t('message.emptyWarning', { name: this.$t('tenantManage.tenant') })); return; } if (!this.listQuery.itemId) { this.$message.warning(this.$t('message.emptyWarning', { name: this.$t('projectManage.item') })); return; } if (!this.listQuery.tpId) { this.$message.warning(this.$t('message.emptyWarning', { name: this.$t('threadPool.threadPool') })); return; } if (!this.listQuery.identify) { this.$message.warning(this.$t('message.emptyWarning', { name: this.$t('threadPoolMonitor.ipPort') })); return; } if (this.endTime) { let time = this.endTime; time.setHours(time.getHours() + 8); this.listQuery.endTime = time.toISOString(); time.setHours(time.getHours() - 8); } this.listQuery.instanceId = this.listQuery.identify; threadPoolApi.info(this.listQuery).then((res) => { this.temp = res; }); // monitorApi.lastTaskCountFun(this.listQuery).then((res) => { // this.rejectCount = res.rejectCount; // this.lastTaskCount = res.completedTaskCount; // }); this.initChart(); }, refreshData() { this.listQuery.tenantId = null; this.listQuery.itemId = null; this.listQuery.tpId = null; this.listQuery.identify = null; this.itemOptions = []; this.threadPoolOptions = []; this.identifyOptions = []; this.listQuery.endTime = null; this.endTime = null; }
-
中英文转译,js 路径 /hippo4j-ui/src/locale/lang/zh.js、/hippo4j-ui/src/locale/lang/en.js,分别在 js 页面下找到如下代码块,增加
chooseDeadline
以及对应翻译//线程池监控 threadPoolMonitor: { ipPort: 'IP : Port', ipPortRequired: 'IP : Port(必填)', noResultsYet: '暂无结果', chooseDeadline: '请选择截止时间', }, //线程池监控 threadPoolMonitor: { ipPort: 'IP : Port', ipPortRequired: 'IP : Port(Required)', noResultsYet: 'No results yet', chooseDeadline: 'Choose deadline', },
-
编译前端项目,执行「package.json」中 scritps 的第二个命令
vue-cli-service build
,会生成或重新替换 dist 目录下的文件,将该目录下的所有文件替换 /hippo4j//hippo4j-server/hippo4j-console/src/main/resources/static 该目录下的静态文件,前端即完成;也可单独将前端代码拉出来部署,后端不依赖静态文件,需要改动前端访问后端接口的 ip+port,在 vue.config.js 中 查找http://127.0.0.1:6691/hippo4j/v1/cs
后替换即可;
后端部分:接口动态查询,数据持久化配置、查询范围配置
-
我这边是将原本的内置配置文件,改为了读取 nacos 中的配置文件,有个一层注册中心的概念,大家可参考下;
-
在 hippo4j-config 的 pom.xml 中引入如下依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> <version>3.0.2</version> </dependency>
-
修改
ServerBootstrapProperties
类/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.hippo4j.config.config; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Configuration; /** * Server bootstrap properties. */ @Slf4j @Getter @Setter @RefreshScope @Configuration @ConfigurationProperties(prefix = ServerBootstrapProperties.PREFIX) public class ServerBootstrapProperties { public final static String PREFIX = "hippo4j.core"; /** * Whether to start the background task of cleaning up thread pool history data. */ private Boolean cleanHistoryDataEnable = Boolean.TRUE; /** * Regularly clean up the historical running data of thread pool. unit: minute. */ private Integer cleanHistoryDataPeriod = 30; /** * query up the historical running data of thread pool. unit: minute. */ private Integer queryHistoryDataPeriod = 60; /** * Netty server port. */ private String nettyServerPort = "8899"; }
-
修改
MonitorQueryReqDTO
类/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.hippo4j.config.model.biz.monitor; import lombok.Data; import java.time.LocalDateTime; /** * Monitor query req DTO. */ @Data public class MonitorQueryReqDTO { /** * Tenant id */ private String tenantId; /** * Item id */ private String itemId; /** * Thread-pool id */ private String tpId; /** * Instance id */ private String instanceId; /** * end time * 截止时间 */ private LocalDateTime endTime; }
-
修改
HisRunDataServiceImpl
类下的query
、queryInfoThreadPoolMonitor
、queryThreadPoolLastTaskCount
方法@Override public List<MonitorRespDTO> query(MonitorQueryReqDTO reqDTO) { LocalDateTime currentDate = LocalDateTime.now(); LocalDateTime dateTime = currentDate.plusMinutes(-properties.getQueryHistoryDataPeriod()); long startTime = DateUtil.getTime(dateTime); List<HisRunDataInfo> hisRunDataInfos = this.lambdaQuery() .eq(HisRunDataInfo::getTenantId, reqDTO.getTenantId()) .eq(HisRunDataInfo::getItemId, reqDTO.getItemId()) .eq(HisRunDataInfo::getTpId, reqDTO.getTpId()) .eq(HisRunDataInfo::getInstanceId, reqDTO.getInstanceId()) .between(HisRunDataInfo::getTimestamp, startTime, DateUtil.getTime(currentDate)) .orderByAsc(HisRunDataInfo::getTimestamp) .list(); return BeanUtil.convert(hisRunDataInfos, MonitorRespDTO.class); } @Override public MonitorActiveRespDTO queryInfoThreadPoolMonitor(MonitorQueryReqDTO reqDTO) { LocalDateTime currentDate; if (Objects.isNull(reqDTO.getEndTime())) { currentDate = LocalDateTime.now(); } else { currentDate = reqDTO.getEndTime(); } LocalDateTime dateTime = currentDate.plusMinutes(-properties.getQueryHistoryDataPeriod()); long startTime = DateUtil.getTime(dateTime); List<HisRunDataInfo> hisRunDataInfos = this.lambdaQuery() .eq(HisRunDataInfo::getTenantId, reqDTO.getTenantId()) .eq(HisRunDataInfo::getItemId, reqDTO.getItemId()) .eq(HisRunDataInfo::getTpId, reqDTO.getTpId()) .eq(HisRunDataInfo::getInstanceId, reqDTO.getInstanceId()) .between(HisRunDataInfo::getTimestamp, startTime, DateUtil.getTime(currentDate)) .orderByAsc(HisRunDataInfo::getTimestamp) .list(); List<String> times = new ArrayList<>(); List<Long> poolSizeList = new ArrayList<>(); List<Long> activeSizeList = new ArrayList<>(); List<Long> queueCapacityList = new ArrayList<>(); List<Long> queueSizeList = new ArrayList<>(); List<Long> completedTaskCountList = new ArrayList<>(); List<Long> rejectCountList = new ArrayList<>(); List<Long> queueRemainingCapacityList = new ArrayList<>(); List<Long> currentLoadList = new ArrayList<>(); long countTemp = 0L; AtomicBoolean firstFlag = new AtomicBoolean(Boolean.TRUE); for (HisRunDataInfo each : hisRunDataInfos) { String time = DateUtil.format(new Date(each.getTimestamp()), NORM_TIME_PATTERN); times.add(time); poolSizeList.add(each.getPoolSize()); activeSizeList.add(each.getActiveSize()); queueSizeList.add(each.getQueueSize()); rejectCountList.add(each.getRejectCount()); queueRemainingCapacityList.add(each.getQueueRemainingCapacity()); currentLoadList.add(each.getCurrentLoad()); queueCapacityList.add(each.getQueueCapacity()); if (firstFlag.get()) { completedTaskCountList.add(0L); firstFlag.set(Boolean.FALSE); countTemp = each.getCompletedTaskCount(); continue; } long completedTaskCount = each.getCompletedTaskCount(); long countTask = completedTaskCount - countTemp; completedTaskCountList.add(countTask); countTemp = each.getCompletedTaskCount(); } return new MonitorActiveRespDTO(times, poolSizeList, activeSizeList, queueSizeList, completedTaskCountList, rejectCountList, queueRemainingCapacityList, currentLoadList, queueCapacityList); } @Override public MonitorRespDTO queryThreadPoolLastTaskCount(MonitorQueryReqDTO reqDTO) { LocalDateTime currentDate = LocalDateTime.now(); LocalDateTime dateTime = currentDate.plusMinutes(-properties.getQueryHistoryDataPeriod()); long startTime = DateUtil.getTime(dateTime); HisRunDataInfo hisRunDataInfo = this.lambdaQuery() .eq(HisRunDataInfo::getTenantId, reqDTO.getTenantId()) .eq(HisRunDataInfo::getItemId, reqDTO.getItemId()) .eq(HisRunDataInfo::getTpId, reqDTO.getTpId()) .eq(HisRunDataInfo::getInstanceId, reqDTO.getInstanceId()) .orderByDesc(HisRunDataInfo::getTimestamp) .between(HisRunDataInfo::getTimestamp, startTime, DateUtil.getTime(currentDate)) .last("LIMIT 1") .one(); return BeanUtil.convert(hisRunDataInfo, MonitorRespDTO.class); }
-
在 nacos 中可以自定义查询时间以及数据持久化时间配置,由于加了
@RefreshScope
注解,修改后会立即生效# 是否开启线程池历史数据清洗 false 不开启 hippo4j: core: clean-history-data-enable: true clean-history-data-period: 14400 query-history-data-period: 60
三、注意事项
- 客户端在每次启动时,都会根据一些信息动态的生成实例标识,这也是查询线程池监控数据的查询参数,这就导致了,本次启动后会查询不到之前的线程池监控数据;