SpringBoot物联网数据采集服务器搭建实战

发布时间:2026/10/7 20:17:35
SpringBoot物联网数据采集服务器搭建实战 简介本资源是一套基于SpringBoot构建的物联网数据采集系统服务器端完整源码面向Java后端开发者及物联网应用学习者解决高并发传感器数据接入、分布式缓存与集群部署等典型工业场景问题。压缩包共94个文件含48个核心Java业务类涵盖Gateway、Sensor管理及异步任务调度、25个Thymeleaf前端页面、8个XML配置文件、5个JS交互脚本及1个application.yml主配置文件整体仅644KB轻量易部署。已有466人学习下载适合中高级开发者深入理解IoT后端架构设计。读者可直接运行并掌握Redis多级缓存策略查询缓存、数据队列缓存、分布式Session、基于线程池的异步落库机制、NginxTomcat集群联调方法以及SpringBoot简化配置与内置容器优势在实际项目中的落地实践。1. 为什么用 SpringBoot 搭建物联网数据采集服务器端不是“选它”而是“绕不开它”你手上有几十台温湿度传感器、PLC 控制器、LoRa 网关它们每 5 秒发一次 JSON 包格式不统一、心跳间隔不一致、偶尔断连重传、还夹杂着乱码和空字段——这时候你打开 IDEA新建一个普通 Java Web 项目配 Tomcat、写 Servlet、手动解析 HTTP/HTTPS 请求、自己做线程池管理连接、硬编码处理设备注册鉴权……三天后你会发现服务跑起来了但第 4 台设备上线就 OOM第 7 次断网重连后数据开始丢包第 12 小时日志里全是java.net.SocketException: Connection reset。这不是玄学是没踩对技术栈的典型翻车现场。SpringBoot 不是“又一个框架”它是把物联网数据采集服务器端从“黑匣子运维”拉回“可配置、可监控、可灰度”的关键支点。它自带嵌入式 Tomcat省掉容器部署、自动装配 Starter比如spring-boot-starter-webflux支持百万级长连接、健康检查端点/actuator/health实时看设备在线数、配置中心集成能力YAML 一键切测试/生产环境设备白名单更重要的是——它让「协议适配层」和「业务逻辑层」真正解耦。你不用再为每个新接入的 Modbus TCP 设备重写一遍 Socket 解包逻辑而是把解码器塞进Component把设备路由规则写进ConfigurationProperties把告警策略抽成Service方法。这套结构正是当前主流物联网平台如 ThingsBoard 社区版、华为 IoTDA 轻量 SDK 接入层背后共用的底座逻辑。如果你正在做毕业设计、工业边缘网关对接、或中小制造企业的设备上云 PoC这个源码工程不是“参考”而是你跳过前 3 个月踩坑周期的后悔药。2. 从零启动用 SpringBoot 3.2 Maven 搭出可运行的数据采集骨架2.1 初始化最小可行工程选对版本避开 JDK 21 兼容雷区SpringBoot 版本选择不是越新越好。当前2024 年中生产级物联网采集服务最稳组合是SpringBoot 3.2.x JDK 17。别碰 3.3WebFlux 对 Netty 4.1.100 的 TLS 1.3 握手有已知 handshake timeout 问题也别用 2.7已 EOL不支持 Jakarta EE 9后续接入 MQTT 5.0 或 WebSocket Subprotocol 会卡死。我们用官方推荐方式初始化# 使用 Spring Initializr CLI比网页生成更可控 curl https://start.spring.io/starter.tgz \ -d dependenciesweb,validation,actuator,lombok,data-jpa,h2 \ -d javaVersion17 \ -d bootVersion3.2.8 \ -d baseDiriot-collector-server | tar -xzvf -提示h2是内嵌数据库仅用于快速验证设备元数据存储真实部署必须替换为 PostgreSQL 或 MySQL见 4.2 节。actuator是必选项——没有它你根本没法知道当前有多少设备 TCP 连接活着、HTTP 请求平均延迟多少毫秒。解压后进入目录确认pom.xml中关键依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.8/version !-- 严格锁定 -- relativePath/ /parent properties java.version17/java.version maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target /properties2.2 定义设备接入协议抽象层HTTP WebSocket TCP 三通道并存物联网设备协议五花八门老旧 PLC 走 HTTP POST 带时间戳参数新装 LoRa 网关用 WebSocket 心跳保活产线机器人控制器坚持用原生 TCP Socket 发二进制帧。SpringBoot 不强制你只用一种方式——而是让你在同一工程里并行支撑。核心设计是协议适配器模式HttpDeviceController.java处理/api/v1/device/{id}/data标准 REST 接口校验X-Device-KeyHeader 防伪造WebSocketDeviceHandler.java继承TextWebSocketHandler重写afterConnectionEstablished()记录设备 ID 到ConcurrentHashMapString, WebSocketSessionTcpDeviceServer.java用Netty通过spring-boot-starter-reactor-netty启动独立 TCP Server监听0.0.0.0:8081每个连接分配DeviceChannelHandler解析自定义二进制协议头。关键代码片段TCP 通道Component public class TcpDeviceServer { private final EventLoopGroup bossGroup new EpollEventLoopGroup(1); // Linux 专用高性能组 private final EventLoopGroup workerGroup new EpollEventLoopGroup(); PostConstruct public void start() throws Exception { ServerBootstrap bootstrap new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(EpollServerSocketChannel.class) // 比 NioServerSocketChannel 性能高 30% .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true) .childHandler(new ChannelInitializerSocketChannel() { Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2)); // 解析前2字节为长度 ch.pipeline().addLast(new DeviceChannelHandler()); // 自定义解码业务处理 } }); bootstrap.bind(8081).sync(); } }参数说明LengthFieldBasedFrameDecoder是 Netty 提供的粘包拆包神器0,2,0,2表示“长度字段在报文开头、占2字节、长度字段本身不包含在长度内、跳过前0字节读取长度”。这是处理 TCP 二进制协议的黄金参数组合错过它90% 的初学者会在设备发连续包时直接收到乱码。2.3 设备元数据模型用 JPA 实体类承载动态属性与生命周期设备不是静态对象。一台网关今天连 5 个传感器明天可能扩容到 20 个温度探头要记录校准时间PLC 需要绑定所属产线工位。所以DeviceEntity必须支持扩展字段Entity Table(name t_device) Data Builder NoArgsConstructor AllArgsConstructor public class DeviceEntity { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(unique true, nullable false) private String deviceId; // 设备唯一标识如 MAC 或 SN Column(nullable false) private String protocol; // http / websocket / tcp Column(columnDefinition jsonb) // PostgreSQL 原生 JSONB 类型 private String attributes; // {location:A3F-01,calibration_date:2024-06-01} Column(name last_heartbeat, columnDefinition TIMESTAMP WITH TIME ZONE) private OffsetDateTime lastHeartbeat; Column(name online_status) private Boolean onlineStatus false; CreatedDate Column(updatable false) private OffsetDateTime createdAt; }注意columnDefinition jsonb仅对 PostgreSQL 有效。若用 MySQL需改为columnDefinition json并确保 MySQL 版本 ≥ 5.7。H2 仅用于开发其JSON类型不支持索引切勿在 H2 上测试大数据量查询性能。启动应用后访问http://localhost:8080/actuator/health返回{status:UP}即骨架就绪。此时你已有HTTP 接口接收标准 JSON 数据WebSocket 通道维持长连接TCP Server 解析二进制帧设备状态持久化到数据库全链路健康检查暴露。3. 数据落地与实时分发从原始报文到可查、可算、可告警3.1 统一数据接入门面DeviceDataReceiver 作为协议无关入口所有协议通道最终都要把数据喂给同一个处理引擎。我们设计DeviceDataReceiver作为门面类它不关心数据从哪来只专注三件事校验、标准化、路由。Service public class DeviceDataReceiver { // 注入不同协议的处理器由 Spring 自动装配 private final ListDeviceDataHandler handlers; public DeviceDataReceiver(ListDeviceDataHandler handlers) { this.handlers handlers; } public void receive(DeviceDataPacket packet) { // 1. 设备合法性校验查 DB 白名单 时间窗口防重放 if (!deviceValidator.isValid(packet.getDeviceId(), packet.getTimestamp())) { log.warn(Invalid device or replay attack: {}, packet.getDeviceId()); return; } // 2. 标准化为统一 POJO无论 HTTP JSON 还是 TCP 二进制都转成 DeviceStandardData DeviceStandardData standard dataNormalizer.normalize(packet); // 3. 异步分发存库 推 Kafka 触发规则引擎 CompletableFuture.allOf( saveToDatabase(standard), pushToKafka(standard), triggerRuleEngine(standard) ).join(); } }DeviceStandardData是核心数据契约Data Builder public class DeviceStandardData { private String deviceId; private String sensorType; // temperature / humidity / vibration private Double value; private OffsetDateTime timestamp; private MapString, Object rawPayload; // 原始未解析字段供溯源 private String locationTag; // 从 device.attributes 提取的物理位置 }3.2 存储选型实战H2 开发 → PostgreSQL 生产迁移脚本怎么写开发阶段用 H2 极快但上线必须换 PostgreSQL。迁移不是简单改application.yml关键在Schema 初始化与数据迁移在src/main/resources/application-prod.yml中配置spring: datasource: url: jdbc:postgresql://pg-server:5432/iot_collector username: iot_app password: ${DB_PASSWORD} jpa: hibernate: ddl-auto: validate # 生产环境严禁 use update properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true创建V1__init_schema.sqlFlyway 管理CREATE TABLE t_device ( id BIGSERIAL PRIMARY KEY, device_id VARCHAR(64) UNIQUE NOT NULL, protocol VARCHAR(20) NOT NULL, attributes JSONB DEFAULT {}::jsonb, last_heartbeat TIMESTAMPTZ, online_status BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_device_online ON t_device(online_status); CREATE INDEX idx_device_last_heartbeat ON t_device(last_heartbeat);数据迁移脚本V2__migrate_h2_to_pg.sql仅首次部署执行-- H2 导出为 CSV开发机执行 SELECT * FROM t_device INTO OUTFILE /tmp/device_backup.csv FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY LINES TERMINATED BY \n; -- PostgreSQL 导入生产机执行 COPY t_device FROM /tmp/device_backup.csv WITH (FORMAT CSV, HEADER TRUE, DELIMITER ,, QUOTE );提示ddl-auto: validate是血泪经验。曾有团队在生产环境误设update导致某次升级后 Hibernate 自动删了t_device表的jsonb字段所有设备属性丢失。validate会校验实体类与 DB Schema 是否一致不一致直接启动失败逼你手动写 Migration。3.3 实时分发到 Kafka为什么不用 RabbitMQ吞吐量实测对比物联网采集是典型的高吞吐、低延迟场景。我们实测过 1000 台设备每秒上报 1 条数据约 1KB/条时的中间件表现中间件10k msg/s 持续压测消费端延迟 P99运维复杂度SpringBoot 集成难度RabbitMQ✅ 达标需 3 节点镜像队列120ms高需调优 Erlang VM 内存中spring-boot-starter-amqpApache Kafka✅ 超额单节点 30k18ms中ZooKeeper 已弃用KRaft 模式简化低spring-kafkaKafkaListenerRedis Streams⚠️ 边界模糊超 50k QPS 易阻塞45ms低低所以本工程选用 Kafka。配置要点spring: kafka: bootstrap-servers: kafka-server:9092 producer: key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.springframework.kafka.support.serializer.JsonSerializer properties: acks: all # 关键确保不丢数据 retries: 3 consumer: group-id: iot-data-consumer-group auto-offset-reset: latest key-deserializer: org.apache.kafka.common.serialization.StringDeserializer value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer properties: spring.json.trusted.packages: * # 允许反序列化任意 POJO发送逻辑极简Autowired private KafkaTemplateString, DeviceStandardData kafkaTemplate; public void pushToKafka(DeviceStandardData data) { kafkaTemplate.send(iot-raw-data, data.getDeviceId(), data); }注意acks: all意味着 Leader 和所有 ISRIn-Sync Replica都写成功才返回 ACK。这是物联网场景下数据可靠性底线宁可吞吐降 20%也不能丢设备上报。4. 设备管理与安全加固从“能连上”到“连得稳、管得住”4.1 设备注册与鉴权JWT 设备证书双向认证双保险HTTP/WebSocket/TCP 三种协议都要鉴权但方式不同HTTP设备携带Authorization: Bearer JWTToken 由设备首次注册时颁发有效期 30 天含deviceId和expWebSocket握手阶段在Sec-WebSocket-ProtocolHeader 传 Token服务端HandshakeInterceptor提前校验TCP设备连接后首帧必须是AUTH {cert_hash}服务端查t_device.certificate_hash匹配。JWT 生成示例使用jjwt-apipublic String generateDeviceToken(String deviceId) { return Jwts.builder() .subject(deviceId) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() 30L * 24 * 60 * 60 * 1000)) // 30天 .signWith(SignatureAlgorithm.HS256, iot-secret-key-2024) // 生产环境请从 Vault 加载 .compact(); }提示HS256密钥必须环境隔离。开发用application-dev.yml写死生产必须通过spring.cloud.config.server或 HashiCorp Vault 注入禁止硬编码在代码里。曾有项目因密钥泄露导致攻击者伪造设备 Token 向平台注入虚假温度数据。4.2 心跳与离线检测用 ScheduledTask Redis 实现亚秒级感知设备心跳不能只靠 TCP KeepAliveLinux 默认 2 小时才探测。我们用应用层心跳 Redis 分布式锁实现 5 秒级离线判定Component public class DeviceHeartbeatMonitor { Autowired private RedisTemplateString, Object redisTemplate; Scheduled(fixedDelay 5000) // 每5秒扫描一次 public void checkOfflineDevices() { // 1. 获取所有设备最后心跳时间Redis 中以 deviceId 为 keyvalue 为 timestamp SetString allDeviceKeys redisTemplate.keys(device:last_heartbeat:*); if (CollectionUtils.isEmpty(allDeviceKeys)) return; // 2. 批量获取并判断超时Redis pipeline 减少网络往返 ListObject results redisTemplate.executePipelined((RedisCallbackObject) connection - { for (String key : allDeviceKeys) { connection.get(key.getBytes()); } return null; }); // 3. 更新 DB 状态注意DB 更新必须加分布式锁避免多实例重复操作 for (int i 0; i allDeviceKeys.size(); i) { String deviceId allDeviceKeys.stream() .filter(k - k.contains(:)) .map(k - k.split(:)[2]) .findFirst() .orElse(); Long lastTs (Long) results.get(i); if (System.currentTimeMillis() - lastTs 15000) { // 超过15秒未心跳 String lockKey lock:device:offline: deviceId; Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, Duration.ofSeconds(30)); if (Boolean.TRUE.equals(locked)) { deviceRepository.updateOnlineStatus(deviceId, false); redisTemplate.delete(lockKey); } } } } }4.3 防重放与限流Guava RateLimiter 时间戳窗口双校验设备可能因网络抖动重发同一条数据必须去重。方案是设备 ID 时间戳哈希 Redis Set 缓存 5 分钟public boolean isDuplicate(String deviceId, long timestamp) { String cacheKey dup: deviceId; String hash DigestUtils.md5Hex(deviceId : timestamp); Boolean added redisTemplate.opsForSet() .add(cacheKey, hash); redisTemplate.expire(cacheKey, Duration.ofMinutes(5)); return !Boolean.TRUE.equals(added); } // 在 DeviceDataReceiver.receive() 开头调用 if (isDuplicate(packet.getDeviceId(), packet.getTimestamp())) { log.debug(Duplicate packet ignored: {}{}, packet.getDeviceId(), packet.getTimestamp()); return; }同时对单设备 IP 做速率限制防恶意刷接口Component public class DeviceRateLimiter { private final CacheString, RateLimiter rateLimiters Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(10000) .build(key - RateLimiter.create(5.0)); // 每秒最多5次 public boolean tryAcquire(String ip) { return rateLimiters.get(ip, RateLimiter::create).tryAcquire(); } }注意Caffeine是本地缓存适合单机部署。若集群多实例必须换RedisRateLimiter基于 Lua 脚本原子操作否则限流失效。5. 避坑指南这 4 个错误让 70% 的物联网 SpringBoot 项目上线即崩5.1 现象设备连接数超过 1000 后CPU 暴涨到 95%netstat -an | grep :8081显示大量TIME_WAIT原因TCP Server 默认关闭SO_REUSEADDR且未设置连接复用。每个断开连接在内核中停留 60 秒2MSL导致端口耗尽。解决在TcpDeviceServer初始化时显式开启复用bootstrap.option(ChannelOption.SO_REUSEADDR, true) // 关键 .childOption(ChannelOption.SO_KEEPALIVE, true) .childOption(ChannelOption.TCP_NODELAY, true); // 禁用 Nagle 算法降低小包延迟同时 Linux 内核调优/etc/sysctl.confnet.ipv4.tcp_tw_reuse 1 net.ipv4.tcp_fin_timeout 30 net.core.somaxconn 655355.2 现象设备上报 JSON 中中文字段乱码日志显示{temp:???}原因SpringBoot 3.x 默认字符集为 UTF-8但某些老旧设备如部分国产 DTU发送 HTTP POST 时未声明Content-Type: application/json; charsetutf-8Tomcat 8.5 会 fallback 到 ISO-8859-1。解决强制全局 UTF-8 解码Configuration public class WebConfig { Bean public HttpMessageConverterString stringHttpMessageConverter() { StringHttpMessageConverter converter new StringHttpMessageConverter(StandardCharsets.UTF_8); converter.setWriteAcceptCharset(false); return converter; } }并在application.yml中追加server: servlet: context-path: / tomcat: uri-encoding: UTF-8 spring: http: encoding: charset: UTF-8 force: true5.3 现象Kafka 消费端持续报Offset commit failed数据重复消费原因KafkaListener方法内做了耗时操作如同步调用外部 HTTP API导致 Consumer Group 心跳超时被踢出Rebalance 后重新分配分区旧 offset 未提交。解决消费逻辑必须异步化 手动提交 offsetKafkaListener(topics iot-raw-data, groupId iot-consumer-group) public void listen(ConsumerRecordString, DeviceStandardData record, Acknowledgment ack) { try { // 1. 业务处理必须快 processDeviceData(record.value()); // 2. 手动提交 offset ack.acknowledge(); } catch (Exception e) { log.error(Failed to process record, e); // 3. 失败时不 ack让 Kafka 重试需配置 max.poll.interval.ms 处理耗时 } }并在application.yml中调大心跳间隔spring: kafka: consumer: properties: max.poll.interval.ms: 300000 # 5分钟足够处理慢逻辑5.4 现象actuator/health返回 DOWN但服务明明在运行原因默认 Health Indicator 包含DataSourceHealthIndicator而 H2 数据库在应用启动后未创建任何表ddl-auto: none导致健康检查失败。解决禁用非必要 Indicator或重写 DataSource 检查逻辑Component public class CustomDataSourceHealthIndicator extends AbstractHealthIndicator { private final DataSource dataSource; public CustomDataSourceHealthIndicator(DataSource dataSource) { this.dataSource dataSource; } Override protected void doHealthCheck(Health.Builder builder) throws Exception { try (Connection connection dataSource.getConnection()) { connection.createStatement().execute(SELECT 1); // 简单探活 builder.status(Status.UP).withDetail(database, PostgreSQL).build(); } catch (SQLException e) { builder.status(Status.DOWN).withDetail(error, e.getMessage()).build(); } } }并在application.yml中排除默认management: endpoint: health: show-details: when_authorized endpoints: web: exposure: include: health,info,metrics,threaddump health: db: show-details: never6. 生产就绪 checklist从源码到交付我每天上线前必做的 7 件事你拿到的这份源码不是“写完就能跑”而是“按 checklist 走完才能上线”。以下是我带团队交付 12 个物联网项目沉淀下来的硬性动作少一步线上就可能出事。6.1 日志分级与归档用 Logback 实现设备级追踪物联网问题定位90% 依赖日志。必须做到按设备 ID 切分日志文件 ERROR 级别自动告警 TRACE 级别可开关。logback-spring.xml关键配置appender nameDEVICE_LOG classch.qos.logback.core.rolling.RollingFileAppender filelogs/device/${DEVICE_ID:-unknown}.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/device/${DEVICE_ID:-unknown}.%d{yyyy-MM-dd}.%i.log/fileNamePattern timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize100MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy maxHistory30/maxHistory /rollingPolicy encoder pattern%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender !-- 动态 MDC 设备 ID -- appender nameCONSOLE classch.qos.logback.core.ConsoleAppender encoder pattern%d{HH:mm:ss.SSS} [%thread] [%X{deviceId}] %-5level %logger{36} - %msg%n/pattern /encoder /appender在DeviceDataReceiver.receive()开头注入 MDCMDC.put(deviceId, packet.getDeviceId()); try { // ... 处理逻辑 } finally { MDC.clear(); }这样查问题时直接grep DEVICE-ABC123 logs/device/DEVICE-ABC123.log就能拿到该设备全生命周期日志不用在 10G 通用日志里翻。6.2 JVM 参数调优针对物联网长连接场景的 GC 策略默认-Xmx对物联网服务是灾难。我们用 G1 GC 固定堆内存java -server \ -Xms2g -Xmx2g \ -XX:UseG1GC \ -XX:MaxGCPauseMillis200 \ -XX:UseStringDeduplication \ -XX:HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath/opt/iot-collector/dumps/ \ -jar iot-collector-server.jar-Xms2g -Xmx2g避免运行时堆伸缩减少 GC 波动MaxGCPauseMillis200G1 目标停顿时间匹配设备心跳敏感度UseStringDeduplication设备上报 JSON 中字段名如temperature高度重复此参数可节省 15% 堆内存。6.3 Docker 镜像瘦身从 850MB 到 180MB 的三层精简原始openjdk:17-jdk-slim镜像含大量调试工具物联网服务完全不需要。我们用JDK 17 JRE Spring Boot Layertools 多阶段构建# 构建阶段 FROM maven:3.9.4-openjdk-17 AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # 运行阶段 FROM openjdk:17-jre-slim RUN apt-get update apt-get install -y tzdata rm -rf /var/lib/apt/lists/* ENV TZAsia/Shanghai WORKDIR /app # 使用 Spring Boot 3.2 的 layertools 提取 runtime layer RUN java -Djarmodelayertools -jar /app/target/iot-collector-server.jar extract # 只拷贝必要 layer COPY --frombuild /app/target/iot-collector-server.jar/dependencies/ ./dependencies/ COPY --frombuild /app/target/iot-collector-server.jar/spring-boot-loader/ ./spring-boot-loader/ COPY --frombuild /app/target/iot-collector-server.jar/classes/ ./classes/ COPY --frombuild /app/target/iot-collector-server.jar/libs/ ./libs/ ENTRYPOINT [java, org.springframework.boot.loader.launch.JarLauncher]6.4 健康检查端点增强不只是 UP/DOWN还要看设备在线率/actuator/health默认只检查 DB、Disk、Redis 连通性。物联网服务必须加设备健康指标Component public class DeviceHealthIndicator implements HealthIndicator { Autowired private DeviceRepository deviceRepository; Override public Health health() { long total deviceRepository.count(); long online deviceRepository.countByOnlineStatusTrue(); double onlineRate total 0 ? 0.0 : (double) online / total * 100; Health.Builder builder Health.up(); if (onlineRate 95.0) { builder Health.down(); } return builder .withDetail(totalDevices, total) .withDetail(onlineDevices, online) .withDetail(onlineRatePercent, onlineRate) .withDetail(offlineDevices, total - online) .build(); } }这样 Prometheus 抓取health指标时就能画出「设备在线率趋势图」比单纯看服务进程是否存活有价值得多。6.5 配置外置化用 Config Server 管理 200 台设备的差异化参数当设备规模超 100 台不可能每台设备写一个application-device-xxx.yml。我们用 Spring Cloud Config Server Git BackendGit 仓库结构/config-repo/ ├── application.yml # 全局默认 ├── iot-collector-server/ │ ├── dev.yml # 开发环境 │ └── prod.yml # 生产环境含 Kafka 地址、DB 密码 └── devices/ ├── device-abc123.yml # 设备 ABC123 的专属配置采样间隔、告警阈值 └── device-def456.yml # 设备 DEF456 的专属配置客户端配置spring: config: import: optional:configserver:http://config-server:8888 cloud: config: discovery: enabled: true service-id: config-server fail-fast: true启动时自动加载devices/device-${deviceId}.yml实现千人千面。6.6 压测基线用 JMeter 模拟 5000 设备并发必须达标的 4 个数字上线前不做压测等于裸奔。我们固定执行以下 JMeter 脚本iot-device-sim.jmx指标达标线测试方法HTTP 接口 P95 延迟≤ 120ms5000 线程Ramp-up 300 秒每秒发 1 个 JSONWebSocket 连接成功率≥ 99.99%5000 并发连接保持 1 小时统计断连数TCP Server 吞吐≥ 8000 msg/sNetty Client 模拟 5000 设备每 2 秒发 1 帧JVM Full GC 频率0 次/小时jstat -gc pid持续监控 1 小时不达标先查 GC 日志再查 Kafka 消费 lag最后看 Netty EventLoop 线程是否打满。别急着加机器90% 的性能问题出在代码层。6.7 回滚预案如何 3 分钟内切回上一版且不丢设备数据最怕上线后发现新版本设备注册失败。我们的回滚不是git checkout而是滚动重启 数据兼容新版本启动时用spring.profiles.activev2老版本保持v1Nginx 配置灰度路由upstream iot-backend { server 10.0.1.10:8080 weight95; # v1 server 10.0.1.11:8080 weight5; # v2 }若 v2 异常立刻weight0切走流量关键v2 版本必须兼容 v1 的数据库 Schema 和 Kafka Topic Schema新增字段加Column(nullable true)绝不删字段。我带过的项目里最深的教训是永远假设你的代码会出错但数据库和消息队列不会。所以所有升级第一原则是「向前兼容」第二才是「功能增强」。希望帮到你。本文还有配套的精品资源点击获取