修正 modbus 超时问题
This commit is contained in:
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user