修正 modbus 超时问题

This commit is contained in:
2026-01-20 17:19:35 +08:00
parent 8716d43879
commit a31a1a1caa
4 changed files with 256 additions and 190 deletions

View File

@ -31,9 +31,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import java.util.stream.Collectors;
import org.eclipse.paho.client.mqttv3.MqttException;
@ -56,10 +58,14 @@ public class ModbusPoller {
private final ObjectMapper objectMapper = new ObjectMapper();
private final Map<String, Integer> deviceFailureCounts = new ConcurrentHashMap<>();
@Resource(name = "modbusExecutor")
private ExecutorService modbusExecutor;
@Autowired
private ModbusProcessor modbusProcessor;
@Autowired
private IEmsAlarmRecordsService iEmsAlarmRecordsService;
@Autowired
private ISysJobService iSysJobService;
@Autowired
@ -99,8 +105,8 @@ public class ModbusPoller {
return;
}
// 按 host:port 分组
Map<String, List<DeviceConfig>> groupedConfigs = new HashMap<>();
// 按主机IP分组同一网关的不同端口也归为一组避免并发访问导致Connection Reset
Map<String, List<DeviceConfig>> groupedByHost = new HashMap<>();
for (Path filePath : jsonFiles) {
DeviceConfig config = null;
try {
@ -110,51 +116,44 @@ public class ModbusPoller {
continue;
}
if (config.isEnabled()) {
String key = config.getHost() + ":" + config.getPort();
groupedConfigs.computeIfAbsent(key, k -> new ArrayList<>()).add(config);
// 只按主机IP分组确保同一网关的所有端口串行访问
String hostKey = config.getHost();
groupedByHost.computeIfAbsent(hostKey, k -> new ArrayList<>()).add(config);
}
}
int interval = getScheduledTaskInterval();
// 为每个 host:port 启动一个任务
for (Map.Entry<String, List<DeviceConfig>> entry : groupedConfigs.entrySet()) {
String groupKey = entry.getKey();
List<DeviceConfig> configs = entry.getValue();
try {
CompletableFuture.runAsync(() -> {
for (DeviceConfig config : configs) {
try {
scheduledStart(config);
} catch (Exception e) {
log.error("采集设备数据异常: {}", config.getDeviceName(), e);
}
}
})
.exceptionally(e -> {
log.error("采集设备数据{}轮询异常", groupKey, e);
return null;
})
.thenRun(() -> log.info("采集设备数据{}轮询任务执行完成", groupKey));
} catch (Exception e) {
log.error("采集设备数据{}任务失败", groupKey, e);
// 使用单线程 executor 串行执行所有主机的 Modbus 操作
// 将所有主机的设备按顺序串行处理,避免任何并发访问
modbusExecutor.submit(() -> {
for (Map.Entry<String, List<DeviceConfig>> entry : groupedByHost.entrySet()) {
String hostKey = entry.getKey();
List<DeviceConfig> configs = entry.getValue();
for (DeviceConfig config : configs) {
try {
scheduledStart(config);
// 每次读取后等待200ms给Modbus网关足够的处理时间
Thread.sleep(200);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
log.warn("Modbus轮询被中断");
return;
} catch (Exception e) {
log.error("采集设备数据异常: {}", config.getDeviceName(), e);
}
}
log.info("采集设备数据{}轮询任务执行完成", hostKey);
}
}
});
}
public void scheduledStart(DeviceConfig config) {
if (config.isEnabled()) {
log.info("Reading data from devices: {}", config.getDeviceName());
ModbusMaster master = null;
try {
master = modbusProcessor.borrowMaster(config);
} catch (Exception e) {
log.error("Failed to borrow connection '{}'", config.getDeviceName(), e);
// 处理设备连接失败的情况,更新设备状态为离线,添加报警记录
addDeviceOfflineRecord(siteId, config.getDeviceNumber());
return;
}
// 带重试的读取最多重试2次
Map<String, Object> data = readWithRetry(config, 2);
List<String> rawValuEmptyList = new ArrayList<>();
Map<String, Object> data = modbusProcessor.readDataFromDevice(config, master);
// 在这里处理采集到的数据空
config.getTags().forEach(tag -> {
Object rawValue = data.get(tag.getKey());
@ -191,6 +190,51 @@ public class ModbusPoller {
}
}
/**
* 带重试的读取方法
*/
private Map<String, Object> readWithRetry(DeviceConfig config, int maxRetries) {
Map<String, Object> data = new HashMap<>();
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
ModbusMaster master = modbusProcessor.borrowMaster(config);
data = modbusProcessor.readDataFromDevice(config, master);
// 如果读取成功(有数据),直接返回
if (!data.isEmpty()) {
if (attempt > 0) {
log.info("设备 {} 第 {} 次重试成功", config.getDeviceName(), attempt);
}
return data;
}
// 读取返回空数据,等待后重试
if (attempt < maxRetries) {
log.warn("设备 {} 读取返回空数据等待1秒后重试 ({}/{})",
config.getDeviceName(), attempt + 1, maxRetries);
Thread.sleep(1000);
}
} catch (Exception e) {
log.error("设备 {} 读取异常 ({}/{}): {}",
config.getDeviceName(), attempt + 1, maxRetries, e.getMessage());
if (attempt < maxRetries) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
// 所有重试都失败
log.error("设备 {} 读取失败,已重试 {} 次", config.getDeviceName(), maxRetries);
return data;
}
private void processingData(Map<String, Object> data, String deviceNumber) {
if (CollectionUtils.isEmpty(data)) {
// 增加失败计数
@ -270,4 +314,4 @@ public class ModbusPoller {
return Math.toIntExact(CronUtils.getNextExecutionIntervalMillis(sysJobs.get(0).getCronExpression()));
}
}
}

View File

@ -39,8 +39,10 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import javax.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
@ -85,28 +87,26 @@ public class StrategyPoller {
@Autowired
private ModbusProcessor modbusProcessor;
@Resource(name = "modbusExecutor")
private ExecutorService modbusExecutor;
public void pollAllDevices() {
logger.info("开始执行运行策略数据轮询...");
List<StrategyRunningVo> strategyRunningVoList = emsStrategyRunningMapper.getPendingPollerStrategy(null);
strategyRunningVoList.forEach(strategyVo -> {
Long strategyId = strategyVo.getId();
if (strategyLocks.putIfAbsent(strategyId, true) == null) {
try {
CompletableFuture.runAsync(() -> {
processData(strategyVo);
})
.exceptionally(e -> {
logger.error("运行策略{}轮询异常", strategyVo.getId(), e);
return null;
})
.thenRun(() -> {
logger.info("运行策略{}轮询任务执行完成,释放锁", strategyVo.getId());
strategyLocks.remove(strategyId);
});
} catch (Exception e) {
logger.error("运行策略{}任务失败", strategyVo.getId(), e);
strategyLocks.remove(strategyId);
}
// 使用共享的modbusExecutor串行执行避免与ModbusPoller并发访问导致通讯故障
modbusExecutor.submit(() -> {
try {
processData(strategyVo);
} catch (Exception e) {
logger.error("运行策略{}轮询异常", strategyVo.getId(), e);
} finally {
logger.info("运行策略{}轮询任务执行完成,释放锁", strategyVo.getId());
strategyLocks.remove(strategyId);
}
});
} else {
logger.info("策略{}已在处理中,跳过重复执行", strategyId);
}