新增remnanode定时任务状态上报
This commit is contained in:
parent
8e18aa54bd
commit
d8ff88ab66
|
|
@ -0,0 +1,138 @@
|
|||
package org.dromara.job.snailjob;
|
||||
|
||||
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
|
||||
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
|
||||
import com.aizuda.snailjob.common.core.util.JsonUtil;
|
||||
import com.aizuda.snailjob.common.log.SnailJobLog;
|
||||
import com.aizuda.snailjob.model.dto.ExecuteResult;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.enums.NetNodeSoftTypeEnum;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
import org.dromara.net.domain.NetNode;
|
||||
import org.dromara.net.domain.NetNodeConfig;
|
||||
import org.dromara.net.dto.NodeSystemStatsResponse;
|
||||
import org.dromara.net.mapper.NetNodeConfigMapper;
|
||||
import org.dromara.net.mapper.NetNodeMapper;
|
||||
import org.dromara.net.util.RemnaNodeHttpClient;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Remna节点系统状态采集定时任务
|
||||
* 定时采集所有RemnaWave类型节点的系统状态信息
|
||||
* 更新节点的内存信息和运行时间
|
||||
*
|
||||
* @author lys
|
||||
* @date 2025-03-19
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@JobExecutor(name = "remnaNodeStatsJob")
|
||||
public class RemnaNodeStatsJob {
|
||||
|
||||
private final NetNodeMapper netNodeMapper;
|
||||
private final NetNodeConfigMapper netNodeConfigMapper;
|
||||
|
||||
/**
|
||||
* 请求超时时间(毫秒)
|
||||
*/
|
||||
private static final int REQUEST_TIMEOUT = 3000;
|
||||
|
||||
/**
|
||||
* 系统状态接口路径
|
||||
*/
|
||||
private static final String SYSTEM_STATS_PATH = "/node/stats/get-system-stats";
|
||||
|
||||
public ExecuteResult jobExecute(JobArgs jobArgs) {
|
||||
SnailJobLog.LOCAL.info("remnaNodeStatsJob 开始执行. jobArgs:{}", JsonUtil.toJsonString(jobArgs));
|
||||
SnailJobLog.REMOTE.info("remnaNodeStatsJob 开始执行. jobArgs:{}", JsonUtil.toJsonString(jobArgs));
|
||||
|
||||
// 查询所有RemnaWave类型的启用节点
|
||||
LambdaQueryWrapper<NetNode> nodeQueryWrapper = new LambdaQueryWrapper<>();
|
||||
nodeQueryWrapper.eq(NetNode::getNodeSoftType, NetNodeSoftTypeEnum.REMNAWAVE.getNodeValue());
|
||||
List<NetNode> nodes = TenantHelper.ignore(() -> netNodeMapper.selectList(nodeQueryWrapper));
|
||||
|
||||
SnailJobLog.LOCAL.info("remnaNodeStatsJob 找到 {} 个RemnaWave节点", nodes.size());
|
||||
SnailJobLog.REMOTE.info("remnaNodeStatsJob 找到 {} 个RemnaWave节点", nodes.size());
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (NetNode node : nodes) {
|
||||
try {
|
||||
// 获取节点配置
|
||||
NetNodeConfig config = TenantHelper.ignore(() -> {
|
||||
LambdaQueryWrapper<NetNodeConfig> configQueryWrapper = new LambdaQueryWrapper<>();
|
||||
configQueryWrapper.eq(NetNodeConfig::getNodeId, node.getId());
|
||||
return netNodeConfigMapper.selectOne(configQueryWrapper);
|
||||
});
|
||||
|
||||
if (config == null) {
|
||||
SnailJobLog.LOCAL.warn("remnaNodeStatsJob 节点 {} 配置不存在,跳过", node.getId());
|
||||
SnailJobLog.REMOTE.warn("remnaNodeStatsJob 节点 {} 配置不存在,跳过", node.getId());
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 构建完整的系统状态URL
|
||||
String nodeUrl = RemnaNodeHttpClient.buildNodeUrl(node.getNodeIp(), node.getConnectionPort());
|
||||
String statsUrl = nodeUrl + SYSTEM_STATS_PATH;
|
||||
|
||||
// 使用封装的HTTP客户端调用系统状态接口
|
||||
NodeSystemStatsResponse statsResponse = RemnaNodeHttpClient.getAndParse(
|
||||
config, statsUrl, NodeSystemStatsResponse.class, REQUEST_TIMEOUT);
|
||||
|
||||
if (statsResponse != null && statsResponse.getResponse() != null) {
|
||||
// 更新节点系统状态信息
|
||||
updateNodeStats(node.getId(), statsResponse.getResponse());
|
||||
successCount++;
|
||||
SnailJobLog.LOCAL.info("remnaNodeStatsJob 节点 {} ({}) 系统状态采集成功, alloc={}MB, sys={}MB, uptime={}s",
|
||||
node.getId(), node.getNodeName(),
|
||||
statsResponse.getResponse().getAlloc() / 1024 / 1024,
|
||||
statsResponse.getResponse().getSys() / 1024 / 1024,
|
||||
statsResponse.getResponse().getUptime());
|
||||
} else {
|
||||
failCount++;
|
||||
SnailJobLog.LOCAL.warn("remnaNodeStatsJob 节点 {} ({}) 系统状态采集失败:响应为空",
|
||||
node.getId(), node.getNodeName());
|
||||
SnailJobLog.REMOTE.warn("remnaNodeStatsJob 节点 {} ({}) 系统状态采集失败:响应为空",
|
||||
node.getId(), node.getNodeName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failCount++;
|
||||
SnailJobLog.LOCAL.error("remnaNodeStatsJob 节点 {} ({}) 系统状态采集异常: {}",
|
||||
node.getId(), node.getNodeName(), e.getMessage());
|
||||
SnailJobLog.REMOTE.error("remnaNodeStatsJob 节点 {} ({}) 系统状态采集异常: {}",
|
||||
node.getId(), node.getNodeName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
String result = String.format("执行完成, 成功: %d, 失败: %d", successCount, failCount);
|
||||
SnailJobLog.LOCAL.info("remnaNodeStatsJob {}", result);
|
||||
SnailJobLog.REMOTE.info("remnaNodeStatsJob {}", result);
|
||||
|
||||
return ExecuteResult.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新节点系统状态信息
|
||||
* 更新内存使用量和运行时间
|
||||
*
|
||||
* @param nodeId 节点ID
|
||||
* @param responseData 响应数据
|
||||
*/
|
||||
private void updateNodeStats(Long nodeId, NodeSystemStatsResponse.ResponseData responseData) {
|
||||
LambdaUpdateWrapper<NetNode> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(NetNode::getId, nodeId)
|
||||
.set(NetNode::getMemUsed, responseData.getAlloc())
|
||||
.set(NetNode::getUptime, responseData.getUptime().intValue());
|
||||
|
||||
TenantHelper.ignore(() -> netNodeMapper.update(null, updateWrapper));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package org.dromara.net.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 节点系统状态响应DTO
|
||||
* 对应 GET /node/stats/get-system-stats 接口的响应
|
||||
*
|
||||
* @author lys
|
||||
* @date 2025-03-19
|
||||
*/
|
||||
@Data
|
||||
public class NodeSystemStatsResponse {
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private ResponseData response;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
@Data
|
||||
public static class ResponseData {
|
||||
/**
|
||||
* goroutine数量
|
||||
*/
|
||||
private Long numGoroutine;
|
||||
|
||||
/**
|
||||
* GC次数
|
||||
*/
|
||||
private Long numGC;
|
||||
|
||||
/**
|
||||
* 已分配内存(字节)
|
||||
*/
|
||||
private Long alloc;
|
||||
|
||||
/**
|
||||
* 总分配内存(字节)
|
||||
*/
|
||||
private Long totalAlloc;
|
||||
|
||||
/**
|
||||
* 系统内存(字节)
|
||||
*/
|
||||
private Long sys;
|
||||
|
||||
/**
|
||||
* malloc次数
|
||||
*/
|
||||
private Long mallocs;
|
||||
|
||||
/**
|
||||
* free次数
|
||||
*/
|
||||
private Long frees;
|
||||
|
||||
/**
|
||||
* 存活对象数
|
||||
*/
|
||||
private Long liveObjects;
|
||||
|
||||
/**
|
||||
* GC暂停总时间(纳秒)
|
||||
*/
|
||||
private Long pauseTotalNs;
|
||||
|
||||
/**
|
||||
* 运行时间(秒)
|
||||
*/
|
||||
private Long uptime;
|
||||
|
||||
/**
|
||||
* 报告数量(可选)
|
||||
*/
|
||||
private Long reportsCount;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import org.dromara.net.domain.NetNode;
|
||||
import org.dromara.net.domain.NetNodeConfig;
|
||||
import org.dromara.net.domain.vo.NetNodeConfigVo;
|
||||
import org.dromara.net.domain.vo.NetNodeVo;
|
||||
|
|
@ -13,6 +15,7 @@ import org.dromara.net.dto.XrayConfig;
|
|||
import org.dromara.net.dto.XrayStartRequest;
|
||||
import org.dromara.net.dto.XrayStartResponse;
|
||||
import org.dromara.net.mapper.NetNodeConfigMapper;
|
||||
import org.dromara.net.mapper.NetNodeMapper;
|
||||
import org.dromara.net.service.INetNodeConfigService;
|
||||
import org.dromara.net.service.INetNodeService;
|
||||
import org.dromara.net.util.RemnaNodeHttpClient;
|
||||
|
|
@ -33,6 +36,7 @@ import java.util.*;
|
|||
public class NetNodeConfigServiceImpl implements INetNodeConfigService {
|
||||
|
||||
private final NetNodeConfigMapper baseMapper;
|
||||
private final NetNodeMapper netNodeMapper;
|
||||
private final INetNodeService netNodeService;
|
||||
|
||||
/**
|
||||
|
|
@ -354,6 +358,9 @@ public class NetNodeConfigServiceImpl implements INetNodeConfigService {
|
|||
throw new ServiceException("启动节点Xray失败");
|
||||
}
|
||||
|
||||
// 更新节点的内存信息和版本信息
|
||||
updateNodeSystemInfo(nodeId, response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
|
@ -443,4 +450,99 @@ public class NetNodeConfigServiceImpl implements INetNodeConfigService {
|
|||
String content = pem.replace("\r\n", "\n");
|
||||
return Arrays.asList(content.split("\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新节点的系统信息(内存、版本等)
|
||||
*
|
||||
* @param nodeId 节点ID
|
||||
* @param response Xray启动响应
|
||||
*/
|
||||
private void updateNodeSystemInfo(Long nodeId, XrayStartResponse response) {
|
||||
if (response == null || response.getResponse() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
XrayStartResponse.ResponseData data = response.getResponse();
|
||||
|
||||
LambdaUpdateWrapper<NetNode> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(NetNode::getId, nodeId);
|
||||
|
||||
// 更新内存总量
|
||||
if (data.getSystemInformation() != null
|
||||
&& StringUtils.isNotBlank(data.getSystemInformation().getMemoryTotal())) {
|
||||
Long memTotal = parseMemoryToBytes(data.getSystemInformation().getMemoryTotal());
|
||||
if (memTotal != null) {
|
||||
updateWrapper.set(NetNode::getMemTotal, memTotal);
|
||||
log.info("更新节点 {} 内存总量: {} bytes ({} MB)", nodeId, memTotal, memTotal / 1024 / 1024);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新Xray版本
|
||||
if (StringUtils.isNotBlank(data.getVersion())) {
|
||||
updateWrapper.set(NetNode::getXrayVersion, data.getVersion());
|
||||
}
|
||||
|
||||
// 更新Node版本
|
||||
if (data.getNodeInformation() != null && StringUtils.isNotBlank(data.getNodeInformation().getVersion())) {
|
||||
updateWrapper.set(NetNode::getNodeVersion, data.getNodeInformation().getVersion());
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
netNodeMapper.update(null, updateWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析内存字符串为字节数
|
||||
* 支持格式: "1GB", "512MB", "1024KB", "1024B" 等
|
||||
*
|
||||
* @param memoryStr 内存字符串
|
||||
* @return 字节数,解析失败返回null
|
||||
*/
|
||||
private Long parseMemoryToBytes(String memoryStr) {
|
||||
if (StringUtils.isBlank(memoryStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 去除空格并转大写
|
||||
String str = memoryStr.trim().toUpperCase();
|
||||
|
||||
try {
|
||||
// 提取数字部分和单位
|
||||
long multiplier = 1;
|
||||
String numStr;
|
||||
|
||||
if (str.endsWith("GB")) {
|
||||
multiplier = 1024L * 1024L * 1024L;
|
||||
numStr = str.substring(0, str.length() - 2);
|
||||
} else if (str.endsWith("MB")) {
|
||||
multiplier = 1024L * 1024L;
|
||||
numStr = str.substring(0, str.length() - 2);
|
||||
} else if (str.endsWith("KB")) {
|
||||
multiplier = 1024L;
|
||||
numStr = str.substring(0, str.length() - 2);
|
||||
} else if (str.endsWith("B")) {
|
||||
multiplier = 1L;
|
||||
numStr = str.substring(0, str.length() - 1);
|
||||
} else if (str.endsWith("G")) {
|
||||
multiplier = 1024L * 1024L * 1024L;
|
||||
numStr = str.substring(0, str.length() - 1);
|
||||
} else if (str.endsWith("M")) {
|
||||
multiplier = 1024L * 1024L;
|
||||
numStr = str.substring(0, str.length() - 1);
|
||||
} else if (str.endsWith("K")) {
|
||||
multiplier = 1024L;
|
||||
numStr = str.substring(0, str.length() - 1);
|
||||
} else {
|
||||
// 没有单位,假设是字节
|
||||
numStr = str;
|
||||
}
|
||||
|
||||
// 解析数字(支持小数)
|
||||
double num = Double.parseDouble(numStr.trim());
|
||||
return (long) (num * multiplier);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("解析内存字符串失败: {}", memoryStr);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue