From 7e69fddafd4622c7e2cdb2d36fb496f70a11f918 Mon Sep 17 00:00:00 2001 From: gaoyutao Date: Wed, 19 Nov 2025 18:42:49 +0800 Subject: [PATCH] =?UTF-8?q?mtr=E7=9B=91=E6=8E=A7=E7=AD=96=E7=95=A5?= =?UTF-8?q?=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 +- .../client/core/config/GlobalConfig.java | 1 + .../agent/client/core/enums/MsgEnum.java | 2 + .../client/core/eo/MtrPolicyConfigEO.java | 45 ++++ .../tongran/agent/client/core/vo/MtrVO.java | 16 ++ .../scheduler/service/BusinessTasks.java | 54 +++++ .../agent/client/service/MtrService.java | 10 + .../client/service/impl/AgentServiceImpl.java | 71 +++++- .../client/service/impl/MtrServiceImpl.java | 213 ++++++++++++++++++ src/main/resources/application-dev.yml | 2 +- src/main/resources/application.yml | 4 +- 11 files changed, 415 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/tongran/agent/client/core/eo/MtrPolicyConfigEO.java create mode 100644 src/main/java/com/tongran/agent/client/core/vo/MtrVO.java create mode 100644 src/main/java/com/tongran/agent/client/service/MtrService.java create mode 100644 src/main/java/com/tongran/agent/client/service/impl/MtrServiceImpl.java diff --git a/pom.xml b/pom.xml index 2e349d2..8a6387a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,9 +9,9 @@ com.tongran.agent - tr-agent-client + tr-mtragent-client 0.0.1-SNAPSHOT - tr-agent-client + tr-mtragent-client tr-agent-client diff --git a/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java b/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java index 13c5ef5..3d7b39b 100644 --- a/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java +++ b/src/main/java/com/tongran/agent/client/core/config/GlobalConfig.java @@ -56,6 +56,7 @@ public class GlobalConfig { public static long SCRIPT_TIME = 0L; public static long VERSION_TIME = 0L; public static long ROUTE_TIME = 0L; + public static String MTRPOLICYMSG = ""; /** * 采集标识 */ diff --git a/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java b/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java index 1892650..ce7cdea 100644 --- a/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java +++ b/src/main/java/com/tongran/agent/client/core/enums/MsgEnum.java @@ -67,6 +67,8 @@ public enum MsgEnum { Agent版本更新应答("AGENT_VERSION_UPDATE_RSP"), + MTR探测上报("MTR_DETECT"), + 多网IP探测上报("NETWORK_DETECT"); private String value; diff --git a/src/main/java/com/tongran/agent/client/core/eo/MtrPolicyConfigEO.java b/src/main/java/com/tongran/agent/client/core/eo/MtrPolicyConfigEO.java new file mode 100644 index 0000000..d42a4b3 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/eo/MtrPolicyConfigEO.java @@ -0,0 +1,45 @@ +package com.tongran.agent.client.core.eo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * mtr策略信息 + */ +@Data +public class MtrPolicyConfigEO { + private static final long serialVersionUID = 1L; + + /** 主键ID */ + private Long id; + + /** 策略名称 */ + private String policyName; + + /** 优先级 */ + private Long priority; + + /** MTR客户端ID */ + private String mtrClientId; + + /** 服务器集合(换行符分割) */ + private String serverGroup; + /** 探测目标ip集合 */ + private String serveripGroup; + + /** 是否探测(0-否,1-是) */ + private Long probeFlag; + + /** 开始时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startTime; + + /** 结束时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endTime; + + /** 探测频率(秒) */ + private Long probeFrequency; +} diff --git a/src/main/java/com/tongran/agent/client/core/vo/MtrVO.java b/src/main/java/com/tongran/agent/client/core/vo/MtrVO.java new file mode 100644 index 0000000..c089fae --- /dev/null +++ b/src/main/java/com/tongran/agent/client/core/vo/MtrVO.java @@ -0,0 +1,16 @@ +package com.tongran.agent.client.core.vo; + +import lombok.Data; + +import java.util.Date; + +@Data +public class MtrVO { + private String targetIp; // 目标IP + private Long policyId; // 策略ID + private double firstLossPercent; // 第一次探测丢包率 + private double finalLossPercent; // 最终丢包率 + private long timestamp; // 探测时间戳 + private boolean hasRetry; // 是否重试 + private String errorMsg; // 错误信息 +} diff --git a/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java b/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java index 7288315..f033a85 100644 --- a/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java +++ b/src/main/java/com/tongran/agent/client/scheduler/service/BusinessTasks.java @@ -7,6 +7,7 @@ import com.alibaba.fastjson2.JSONObject; import com.tongran.agent.client.core.config.ApplicationProperties; import com.tongran.agent.client.core.config.GlobalConfig; import com.tongran.agent.client.core.enums.MsgEnum; +import com.tongran.agent.client.core.eo.MtrPolicyConfigEO; import com.tongran.agent.client.core.session.SessionManager; import com.tongran.agent.client.core.vo.*; import com.tongran.agent.client.netty.MultiTargetNettyClient; @@ -59,6 +60,7 @@ public class BusinessTasks { private final AtomicInteger registerTask = new AtomicInteger(0); private final AtomicInteger connectionTask = new AtomicInteger(0); private final AtomicInteger networkDetectTask = new AtomicInteger(0); + private final AtomicInteger mtrTask = new AtomicInteger(0); @@ -108,6 +110,9 @@ public class BusinessTasks { @Resource private AgentService agentService; + @Resource + private MtrService mtrService; + /** * 任务1:心跳上报任务 */ @@ -900,6 +905,55 @@ public class BusinessTasks { AssertLog.info("发送多网IP探测定时任务执行 - task #{} completed", count); } + /** + * 任务30:MTR网络探测任务 + */ + @Async("taskExecutor") + public void mtrTask() { + long timestamp = AgentUtil.roundMinutes(); // 使用5分钟整点时间 + int count = mtrTask.incrementAndGet(); + AssertLog.info("MTR网络探测定时任务执行 - 时间: {},task #{}", LocalDateTime.now(), count); + + try { + // 检查当前是否有有效的MTR策略 + if (StringUtils.isBlank(GlobalConfig.MTRPOLICYMSG)) { + AssertLog.info("没有MTR策略配置,跳过本次执行"); + return; + } + + // 解析策略 + List mtrPolicyConfigEOList = JSON.parseArray(GlobalConfig.MTRPOLICYMSG, MtrPolicyConfigEO.class); + if (CollectionUtil.isEmpty(mtrPolicyConfigEOList)) { + AssertLog.info("MTR策略列表为空,跳过本次执行"); + return; + } + + // 判定客户端与服务端是否连接 + if (Objects.nonNull(sessionManager.getSessionById(GlobalConfig.CLIENT_ID))) { + // 执行MTR探测并获取结果 + String data = ""; + List list = mtrService.mtrList(timestamp, mtrPolicyConfigEOList); + if (CollectionUtil.isNotEmpty(list)) { + data = JSONArray.toJSONString(list); + + // 发送MTR探测结果 + Message message = Message.builder() + .clientId(GlobalConfig.CLIENT_ID) + .dataType(MsgEnum.MTR探测上报.getValue()) + .data(data) + .build(); + sessionManager.writeAndFlush(sessionManager.getSessionById(GlobalConfig.CLIENT_ID).getChannel(), message); + AssertLog.info("发送MTR探测信息包,结果数量: {}", list.size()); + } else { + AssertLog.info("本次MTR探测无有效结果"); + } + } + } catch (Exception e) { + AssertLog.error("MTR定时任务执行异常", e); + } + + AssertLog.info("MTR网络探测定时任务执行 - task #{} completed", count); + } diff --git a/src/main/java/com/tongran/agent/client/service/MtrService.java b/src/main/java/com/tongran/agent/client/service/MtrService.java new file mode 100644 index 0000000..a170268 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/MtrService.java @@ -0,0 +1,10 @@ +package com.tongran.agent.client.service; + +import com.tongran.agent.client.core.eo.MtrPolicyConfigEO; +import com.tongran.agent.client.core.vo.MtrVO; + +import java.util.List; + +public interface MtrService { + List mtrList(long timestamp, List mtrPolicyConfigEOList); +} diff --git a/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java index 1ddf808..56a7d54 100644 --- a/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java +++ b/src/main/java/com/tongran/agent/client/service/impl/AgentServiceImpl.java @@ -9,8 +9,10 @@ import com.tongran.agent.client.core.config.GlobalConfig; import com.tongran.agent.client.core.enums.MsgEnum; import com.tongran.agent.client.core.eo.AgentVersionUpdateEO; import com.tongran.agent.client.core.eo.CollectEO; +import com.tongran.agent.client.core.eo.MtrPolicyConfigEO; import com.tongran.agent.client.core.eo.ScriptPolicyEO; import com.tongran.agent.client.core.session.SessionManager; +import com.tongran.agent.client.core.vo.MtrVO; import com.tongran.agent.client.netty.MultiTargetNettyClient; import com.tongran.agent.client.netty.config.AgentNettyConfig; import com.tongran.agent.client.netty.model.Message; @@ -35,6 +37,9 @@ import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; @Service @@ -825,11 +830,73 @@ public class AgentServiceImpl implements AgentService { } } } - } + if(jsonObject.containsKey("mtrPolicys")){ + String mtrPolicys = jsonObject.getString("mtrPolicys"); + if(!mtrPolicys.equals(GlobalConfig.MTRPOLICYMSG)){ + GlobalConfig.MTRPOLICYMSG = mtrPolicys; + List mtrPolicyConfigEOList = JSON.parseArray(mtrPolicys, MtrPolicyConfigEO.class); + try { + if (CollectionUtil.isEmpty(mtrPolicyConfigEOList)) { + // 如果没有策略,取消定时任务 + dynamicTaskService.cancelTask("mtrTask"); + AssertLog.info("MTR策略为空,取消MTR定时任务"); + return; + } + // 检查是否有有效的策略(在时间范围内的策略) + boolean hasValidPolicy = false; + long currentTimestamp = System.currentTimeMillis() / 1000; + + for (MtrPolicyConfigEO policy : mtrPolicyConfigEOList) { + if (isPolicyTimeValid(policy, currentTimestamp)) { + hasValidPolicy = true; + break; + } + } + + if (!hasValidPolicy) { + // 没有有效策略,取消定时任务 + dynamicTaskService.cancelTask("mtrTask"); + AssertLog.info("没有有效的MTR策略,取消MTR定时任务"); + return; + } + + // 计算下一个5分钟整点的延迟时间 + long milli = AgentUtil.millisecondsToNext5Minute(); + + // 启动或更新定时任务 + dynamicTaskService.scheduleTask("mtrTask", + businessTasks::mtrTask, milli, 300000L); // 5分钟间隔 + + AssertLog.info("MTR定时任务已启动/更新,延迟: {}ms, 间隔: 300000ms", milli); + + } catch (Exception e) { + AssertLog.error("启动MTR定时任务异常", e); + } + } + } } + /** + * 检查策略时间是否有效 + */ + private boolean isPolicyTimeValid(MtrPolicyConfigEO policy, long currentTimestamp) { + try { + Date currentTime = new Date(currentTimestamp * 1000); + Date startTime = policy.getStartTime(); + Date endTime = policy.getEndTime(); + // 开始时间为空表示立即开始 + boolean startValid = startTime == null || !currentTime.before(startTime); + // 结束时间为空表示永不过期 + boolean endValid = endTime == null || !currentTime.after(endTime); + + return startValid && endValid; + } catch (Exception e) { + AssertLog.error("检查策略时间有效性异常", e); + return false; + } + } @Override public void checkTrAgent() { String CRON_FILE_PATH = properties.getScriptPath()+"/tr_live.cron"; @@ -904,7 +971,7 @@ public class AgentServiceImpl implements AgentService { @Override public boolean connection() { - String clientId = MachineFingerprint.getHardwareFingerprint(); + String clientId = "MTR" + MachineFingerprint.getHardwareFingerprint(); //初始化SN信息 GlobalConfig.DEVICE_SN = AgentUtil.getDeviceSN(); //检查外置目录是否存在 diff --git a/src/main/java/com/tongran/agent/client/service/impl/MtrServiceImpl.java b/src/main/java/com/tongran/agent/client/service/impl/MtrServiceImpl.java new file mode 100644 index 0000000..6b15987 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/service/impl/MtrServiceImpl.java @@ -0,0 +1,213 @@ +package com.tongran.agent.client.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.tongran.agent.client.core.eo.MtrPolicyConfigEO; +import com.tongran.agent.client.core.vo.MtrVO; +import com.tongran.agent.client.core.vo.NetVO; +import com.tongran.agent.client.service.MtrService; +import com.tongran.agent.client.utils.AgentUtil; +import com.tongran.agent.client.utils.AssertLog; +import org.springframework.stereotype.Service; +import oshi.SystemInfo; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.hardware.NetworkIF; +import oshi.util.FormatUtil; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Service +public class MtrServiceImpl implements MtrService { + /** + * 执行MTR探测列表 - 异步版本 + */ + public List mtrList(long timestamp, List mtrPolicyConfigEOList) { + List> futures = new ArrayList<>(); + List mtrResults = new ArrayList<>(); + + try { + if (CollectionUtil.isEmpty(mtrPolicyConfigEOList)) { + return mtrResults; + } + + // 为每个IP创建异步任务 + for (MtrPolicyConfigEO policy : mtrPolicyConfigEOList) { + if (!isPolicyTimeValid(policy, timestamp)) { + continue; + } + + String[] targetIps = policy.getServeripGroup().split(";"); + for (String targetIp : targetIps) { + String ip = targetIp.trim(); + if (!ip.isEmpty()) { + // 异步执行每个IP的探测 + CompletableFuture future = CompletableFuture.supplyAsync(() -> + executeSingleMtrProbe(ip, policy, timestamp) + ); + futures.add(future); + } + } + } + + // 等待所有任务完成 + CompletableFuture allFutures = CompletableFuture.allOf( + futures.toArray(new CompletableFuture[0]) + ); + + // 获取所有结果(设置超时时间,比如2分钟) + allFutures.get(2, TimeUnit.MINUTES); + + for (CompletableFuture future : futures) { + MtrVO result = future.get(); + if (result != null) { + mtrResults.add(result); + } + } + + } catch (Exception e) { + AssertLog.error("MTR异步探测异常", e); + } + + return mtrResults; + } + + + /** + * 检查策略时间是否有效 + */ + private boolean isPolicyTimeValid(MtrPolicyConfigEO policy, long currentTimestamp) { + try { + // 将时间戳转换为Date对象进行比较 + Date currentTime = new Date(currentTimestamp * 1000); + Date startTime = policy.getStartTime(); + Date endTime = policy.getEndTime(); + + // 开始时间为空表示立即开始 + boolean startValid = startTime == null || !currentTime.before(startTime); + // 结束时间为空表示永不过期 + boolean endValid = endTime == null || !currentTime.after(endTime); + + return startValid && endValid; + } catch (Exception e) { + AssertLog.error("检查策略时间有效性异常", e); + return false; + } + } + + /** + * 执行单个IP的MTR探测 + */ + private MtrVO executeSingleMtrProbe(String targetIp, MtrPolicyConfigEO policy, long timestamp) { + MtrVO mtrVO = new MtrVO(); + mtrVO.setTargetIp(targetIp); + mtrVO.setPolicyId(policy.getId()); + mtrVO.setTimestamp(timestamp); + + try { + // 第一次MTR探测 + double firstLossPercent = executeMtrCommand(targetIp); + mtrVO.setFirstLossPercent(firstLossPercent); + + // 如果丢包率超过5%,30秒后重试 + if (firstLossPercent > 5.0) { + AssertLog.info("目标IP {} 丢包率 {}% > 5%,30秒后重试探测", targetIp, firstLossPercent); + + // 等待30秒 + Thread.sleep(30000); + + // 第二次MTR探测 + double secondLossPercent = executeMtrCommand(targetIp); + mtrVO.setFinalLossPercent(secondLossPercent); + mtrVO.setHasRetry(true); + + AssertLog.info("目标IP {} 重试探测完成,最终丢包率: {}%", targetIp, secondLossPercent); + } else { + mtrVO.setFinalLossPercent(firstLossPercent); + mtrVO.setHasRetry(false); + AssertLog.info("目标IP {} 丢包率 {}%,无需重试", targetIp, firstLossPercent); + } + + return mtrVO; + + } catch (Exception e) { + AssertLog.error("MTR探测异常 - IP: {}", targetIp, e); + mtrVO.setFinalLossPercent(-1.0); // -1表示探测失败 + mtrVO.setErrorMsg(e.getMessage()); + return mtrVO; + } + } + + /** + * 执行mtr -r命令并解析结果 + */ + private double executeMtrCommand(String targetIp) { + Process process = null; + try { + // 执行mtr -r -c 10命令 + ProcessBuilder processBuilder = new ProcessBuilder("mtr", "-r", "-c", "10", targetIp); + process = processBuilder.start(); + + // 读取命令输出 + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + String lastValidLine = null; + + while ((line = reader.readLine()) != null) { + // 跳过标题行,记录最后一条有效数据行 + if (!line.trim().isEmpty() && + !line.startsWith("Start:") && + !line.startsWith("HOST:") && + Character.isDigit(line.trim().charAt(0))) { + lastValidLine = line; + } + } + + // 等待命令执行完成 + boolean finished = process.waitFor(2, TimeUnit.MINUTES); + if (!finished) { + process.destroy(); + throw new RuntimeException("MTR命令执行超时"); + } + + if (lastValidLine == null) { + throw new RuntimeException("MTR命令无有效输出"); + } + + // 解析丢包率 + return parseLossPercentFromMtrOutput(lastValidLine); + + } catch (Exception e) { + throw new RuntimeException("MTR命令执行异常: " + e.getMessage()); + } finally { + if (process != null) { + process.destroy(); + } + } + } + + /** + * 从MTR输出中解析丢包率 + */ + private double parseLossPercentFromMtrOutput(String mtrOutputLine) { + try { + // 使用正则表达式匹配Loss%列 + Pattern pattern = Pattern.compile("(\\d+\\.?\\d*)%"); + Matcher matcher = pattern.matcher(mtrOutputLine); + + if (matcher.find()) { + return Double.parseDouble(matcher.group(1)); + } + + throw new RuntimeException("未找到Loss%数据: " + mtrOutputLine); + } catch (Exception e) { + throw new RuntimeException("解析Loss%失败: " + e.getMessage()); + } + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 9efad49..596d0af 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -17,7 +17,7 @@ logging: netty: server: host: 120.211.95.173 - port: 6620 + port: 6610 client: client-id: client-001 reconnect-interval: 5 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 288a043..9fa03c2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,8 +5,8 @@ spring: pathmatch: matching-strategy: ant_path_matcher application: - name: tr-agent-client - version: 1.1.4 + name: tr-mtragent-client + version: 1.0.0 conf-path: /usr/local/tongran/conf script-path: /usr/local/tongran/sbin tmp-path: /usr/local/tongran/tmp