1. 项目背景与核心需求
工业自动化领域的数据采集一直是系统集成中的关键环节。最近在为一个智能制造项目搭建数据中台时,我需要实现从分布在车间不同位置的12台PLC设备中定时采集生产数据。这些设备采用Modbus TCP协议通信,要求每5秒采集一次数据并实时存储到Redis中,供后续的数据分析和可视化平台调用。
传统做法是通过OPC服务器中转,但考虑到成本和技术栈统一性,我决定采用SpringBoot+modbus4j的方案直接与PLC通信。这种方案有三大优势:一是Java技术栈与现有系统无缝集成;二是modbus4j库成熟稳定;三是避免了OPC中间件的授权费用。
2. 技术选型与架构设计
2.1 核心组件选型
SpringBoot 2.7.x:作为基础框架,提供依赖注入、定时任务等企业级特性。选择2.7.x版本是因为它在长期支持(LTS)周期内,比3.x版本对老项目更友好。
modbus4j 3.0:Modbus协议栈实现库。相比jLibModbus等替代方案,它的优势在于:
- 支持同步/异步两种通信模式
- 提供完整的Modbus功能码实现
- 社区活跃度高,bug修复及时
Lettuce 6.2:Redis客户端。相比Jedis,它在SpringBoot生态中集成度更高,支持响应式编程模型,连接池管理更高效。
2.2 系统架构设计
整体采用生产者-消费者模式:
code复制[Modbus Master] → [Data Processor] → [Redis Writer]
↑ ↑ ↑
[Schedule] [Data Transform] [Pipeline]
关键设计要点:
- 连接池管理:每个PLC设备维护独立的TCP连接池
- 数据分片:不同设备的数据写入Redis不同db分区
- 异常隔离:单个设备通信失败不影响其他设备采集
3. 核心实现细节
3.1 Modbus连接配置
java复制@Configuration
public class ModbusConfig {
@Value("${plc.devices}")
private List<String> plcIps;
@Bean
public Map<String, IpMaster> ipMasterMap() {
Map<String, IpMaster> masters = new ConcurrentHashMap<>();
plcIps.forEach(ip -> {
IpParameters params = new IpParameters();
params.setHost(ip);
params.setPort(502);
params.setEncapsulated(false);
ModbusMaster master = new TcpMaster(params, true);
master.setTimeout(2000);
master.setRetries(1);
masters.put(ip, master);
});
return masters;
}
}
关键参数说明:
- timeout设为2000ms:根据车间网络质量实测得出
- retries=1:避免因重试导致采集周期紊乱
- encapsulated=false:标准Modbus TCP协议格式
3.2 定时采集任务实现
java复制@Scheduled(fixedRate = 5000)
public void pollPlcData() {
plcIps.parallelStream().forEach(ip -> {
try {
IpMaster master = ipMasterMap.get(ip);
BatchRead<String> batch = new BatchRead<>();
// 读取保持寄存器40001-40010
batch.addLocator(ip+"_temp",
new SimpleLocator(1, 0, 10, DataType.TWO_BYTE_INT_SIGNED));
BatchResults<String> results = master.send(batch);
processResults(ip, results);
} catch (Exception e) {
log.error("PLC[{}]采集异常: {}", ip, e.getMessage());
}
});
}
优化技巧:
- 使用parallelStream实现多设备并行采集
- BatchRead批量读取减少通信次数
- 设备IP作为key前缀实现数据隔离
3.3 Redis存储设计
采用Hash结构存储设备数据:
code复制Key: plc:data:{deviceIp}
Field: {registerAddress}
Value: {registerValue}+timestamp
写入示例:
java复制public void saveToRedis(String ip, BatchResults<String> results) {
String key = "plc:data:" + ip;
Map<String, String> fieldValues = new HashMap<>();
results.getValues().forEach((k,v) -> {
fieldValues.put(k, v+"|"+System.currentTimeMillis());
});
redisTemplate.opsForHash().putAll(key, fieldValues);
redisTemplate.expire(key, 24, TimeUnit.HOURS);
}
数据结构优势:
- 单次hmset操作原子性写入
- 时间戳便于后续数据分析
- 24小时过期避免内存堆积
4. 性能优化与异常处理
4.1 连接池调优参数
yaml复制modbus:
pool:
max-active: 8
max-idle: 4
min-idle: 2
max-wait: 1000
实测数据对比:
| 参数配置 | 平均采集耗时 | 错误率 |
|---|---|---|
| 默认配置 | 3200ms | 5.2% |
| 调优后 | 1800ms | 1.1% |
4.2 常见异常处理方案
-
ConnectionTimeoutException
- 检查物理网络连接
- 适当增加timeout值
- 添加自动重连机制
-
ErrorResponseException
- 确认PLC从站地址正确
- 检查寄存器地址范围
- 验证功能码支持情况
-
CRC校验失败
- 检查网络干扰情况
- 尝试降低通信波特率
- 启用TCP协议校验
5. 监控与扩展方案
5.1 监控指标设计
通过Spring Actuator暴露关键指标:
java复制@Bean
public MeterRegistryCustomizer<MeterRegistry> metrics() {
return registry -> {
Gauge.builder("plc.connection.active",
() -> ipMasterMap.values().stream()
.filter(m -> ((TcpMaster)m).isConnected())
.count())
.register(registry);
Counter.builder("plc.read.errors")
.tag("type", "timeout")
.register(registry);
};
}
5.2 扩展方向
- 协议扩展:增加Modbus RTU串口支持
- 数据持久化:添加MySQL批量落盘
- 边缘计算:在采集端实现简单数据处理
实际部署中发现,当PLC设备超过20台时,建议采用分布式采集方案,将设备分组部署到多个采集节点上。我在某汽车生产线项目中采用Nacos服务发现+SpringCloud Stream的方案,实现了50+PLC设备的稳定采集。