diff --git a/jsowell-common/src/main/java/com/jsowell/common/enums/ykc/PileChannelEntity.java b/jsowell-common/src/main/java/com/jsowell/common/enums/ykc/PileChannelEntity.java index 34daca52f..163a81a33 100644 --- a/jsowell-common/src/main/java/com/jsowell/common/enums/ykc/PileChannelEntity.java +++ b/jsowell-common/src/main/java/com/jsowell/common/enums/ykc/PileChannelEntity.java @@ -165,7 +165,11 @@ public class PileChannelEntity { if (!StringUtils.equals(currentChannelId, expectedChannelId)) { return false; } - return manager.remove(pileSn, currentCtx); + boolean removed = manager.remove(pileSn, currentCtx); + if (removed) { + channelIdToPileSnMap.remove(expectedChannelId); + } + return removed; } public static List getPileSnListSnapshot() { diff --git a/jsowell-framework/src/main/java/com/jsowell/framework/config/ThreadPoolConfig.java b/jsowell-framework/src/main/java/com/jsowell/framework/config/ThreadPoolConfig.java index 5b0d276df..8d868d6f8 100644 --- a/jsowell-framework/src/main/java/com/jsowell/framework/config/ThreadPoolConfig.java +++ b/jsowell-framework/src/main/java/com/jsowell/framework/config/ThreadPoolConfig.java @@ -27,7 +27,7 @@ public class ThreadPoolConfig { // 最大可创建的线程数 private final int maxPoolSize = 128; // 突发时翻倍,避免过多上下文切换 - // 队列最大长度 (优化:增大队列容量以应对桩端消息高峰) + // 队列最大长度 private final int queueCapacity = 5000; // 桩消息短时堆积时提供缓冲,防止直接拒绝 // 线程池维护线程所允许的空闲时间 @@ -51,7 +51,6 @@ public class ThreadPoolConfig { /** * 线程池 - * 优化:使用 DiscardOldestPolicy 避免阻塞调用者线程(如 Netty IO 线程) */ @Bean(name = "threadPoolTaskExecutor") public ThreadPoolTaskExecutor threadPoolTaskExecutor() { @@ -61,8 +60,8 @@ public class ThreadPoolConfig { executor.setQueueCapacity(queueCapacity); executor.setKeepAliveSeconds(keepAliveSeconds); // 线程池对拒绝任务(无线程可用)的处理策略 - // 优化:DiscardOldestPolicy 丢弃最老的任务,避免阻塞调用者线程 - executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); + // 关键链路任务不能静默丢弃,回退为调用方执行 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setThreadNamePrefix(threadNamePrefix); executor.setWaitForTasksToCompleteOnShutdown(true); // log.info("threadPoolTaskExecutor创建成功"); diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/HeartbeatRequestHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/HeartbeatRequestHandler.java index f91c9b34d..3480536e0 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/HeartbeatRequestHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/HeartbeatRequestHandler.java @@ -64,19 +64,16 @@ public class HeartbeatRequestHandler extends AbstractYkcHandler { String connectorStatus = BytesUtil.binary(connectorStatusByte, 16); // log.info("桩号:{}, 枪号:{}, 枪状态:{}", pileSn, pileConnectorNum, connectorStatus); - // 优化:先构建并返回心跳应答,不阻塞心跳回复 + // 先构建心跳应答,再同步刷新最后通信时间和连接映射,保证离线确认链路及时收敛 byte[] flag = Constants.zeroByteArray; byte[] messageBody = Bytes.concat(pileSnByte, pileConnectorNumByte, flag); byte[] response = getResult(ykcDataProtocol, messageBody); - // 优化:所有其他操作(Redis、数据库)完全异步化,不阻塞心跳回复 - // 将 saveLastTimeAndCheckChannel 和 updateStatus 都改为异步执行 + saveLastTimeAndCheckChannel(pileSn, channel); + + // 数据库状态更新继续异步,避免把心跳回包链路拖长 CompletableFuture.runAsync(() -> { try { - // 异步保存连接时间并检查通道(Redis 操作) - saveLastTimeAndCheckChannel(pileSn, channel); - - // 异步更新状态(数据库操作) String frameType = BytesUtil.bcd2Str(ykcDataProtocol.getFrameType()); pileBasicInfoService.updateStatus(frameType, pileSn, pileConnectorNum, connectorStatus, null); } catch (Exception e) { diff --git a/plan/2026-03-21_08-48-24-ykc-midnight-offline-fix-tracker.md b/plan/2026-03-21_08-48-24-ykc-midnight-offline-fix-tracker.md index f4a537762..2d85fbeb1 100644 --- a/plan/2026-03-21_08-48-24-ykc-midnight-offline-fix-tracker.md +++ b/plan/2026-03-21_08-48-24-ykc-midnight-offline-fix-tracker.md @@ -88,8 +88,11 @@ created_at: 2026-03-21T08:48:24+08:00 ### YKC-001 - 优先级:P0 -- 状态:todo +- 状态:done - 目标:将云快充连接保活从“30 秒空闲直接断开”改为“允许短时抖动,按连续丢心跳或宽限期判定断链”。 +- 当前进展: + - 已按第一批止血方案落地为 `15s tick + 连续 3 次 READER_IDLE 才关闭`。 + - 已补 `idleCount / lastFrameType / lastReceiveAt / lastSerialNumber / disconnectReason` 连接属性。 - 修改文件: - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerChannelInitializer.java` - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerHandler.java` @@ -106,8 +109,11 @@ created_at: 2026-03-21T08:48:24+08:00 ### YKC-002 - 优先级:P0 -- 状态:todo +- 状态:done - 目标:增加“断链宽限期”,避免瞬断立刻将枪口置离线、订单置异常。 +- 当前进展: + - 已落地 `60s` 离线确认宽限期。 + - 已引入 `pile_pending_disconnect` 和 `pile_offline_confirmed` 标记,并在恢复通信后自动清理。 - 修改文件: - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/service/yunkuaichong/impl/YKCBusinessServiceImpl.java` - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java` @@ -124,8 +130,11 @@ created_at: 2026-03-21T08:48:24+08:00 ### YKC-003 - 优先级:P0 -- 状态:todo +- 状态:in_progress - 目标:补齐连接生命周期日志,支持“凌晨离线”专项排查。 +- 当前进展: + - 已补空闲告警、连续空闲关闭、异常断链、宽限恢复、正式离线确认等主链路日志。 + - `LoginRequestHandler` / `UploadRealTimeMonitorHandler` 维度的专项日志尚未单独补齐。 - 修改文件: - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerHandler.java` - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/HeartbeatRequestHandler.java` @@ -142,8 +151,11 @@ created_at: 2026-03-21T08:48:24+08:00 ### YKC-004 - 优先级:P0 -- 状态:todo +- 状态:done - 目标:修复 channel 映射清理不完整的问题,避免旧连接残留。 +- 当前进展: + - 已补 `removeByPileSnAndChannelId()` 安全删除。 + - 已补 `channelId -> pileSn` 反向映射维护,并修复删除时的脏映射残留问题。 - 修改文件: - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/service/yunkuaichong/impl/YKCBusinessServiceImpl.java` - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-common/src/main/java/com/jsowell/common/enums/ykc/PileChannelEntity.java` @@ -159,8 +171,11 @@ created_at: 2026-03-21T08:48:24+08:00 ### YKC-005 - 优先级:P1 -- 状态:todo +- 状态:in_progress - 目标:将桩离线判定从“任意枪状态为离线即整桩离线”改为“最近通信时间 + 枪状态 + 宽限期”的综合判定。 +- 当前进展: + - 已完成 `checkPileOffLine()` 优先识别 `PILE_OFFLINE_CONFIRMED` 标记。 + - 仍未重写 `getPileStatus/getPileStatusV2` 的整桩聚合规则,暂按第一批止血范围保留现状。 - 修改文件: - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileConnectorInfoServiceImpl.java` - 设计要点: @@ -195,8 +210,12 @@ created_at: 2026-03-21T08:48:24+08:00 ### YKC-007 - 优先级:P1 -- 状态:todo +- 状态:done - 目标:补每日自动对时,降低跨天时钟漂移的影响。 +- 当前进展: + - 已新增 `jsowellTask.dailyProofreadTimeForYkcV160()`。 + - 已补 `0x56` 下发记录和 `0x55` 应答记录。 + - 待上线时补 Quartz 任务配置。 - 修改文件: - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/YKCPushCommandServiceImpl.java` - `/Users/guoqiusi/Workspace/jsowell-charger-web/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/TimeCheckSettingResponseHandler.java` @@ -319,6 +338,23 @@ created_at: 2026-03-21T08:48:24+08:00 - 订单异常处理若改为延迟确认,需防止漏处理真正断电或断网导致的异常订单。 - 双实现收敛前,所有改动应明确打在老 Handler 链路上,避免误改新 Strategy 无法生效。 +## 当前进度快照 + +- 更新时间:2026-03-21 11:14:17 +0800 +- `done`: + - `YKC-001` + - `YKC-002` + - `YKC-004` + - `YKC-007` +- `in_progress`: + - `YKC-003` + - `YKC-005` +- `todo`: + - `YKC-006` + - `YKC-008` + - `YKC-009` + - `YKC-010` + ## 进度记录模板 ```md @@ -338,6 +374,58 @@ created_at: 2026-03-21T08:48:24+08:00 - 备注/风险: ``` +## 最终上线注意项 + +- 本次建议作为一个整体上线,包含: + - 第一批凌晨离线止血。 + - `YKC-007` 云快充 1.6 每日自动对时。 +- 当前最终基线以“离线判定正确性优先”为准,同时保留必要性能优化: + - 保留 `EchoServerHandler` 绑定 `businessGroup`。 + - 保留 `PileChannelEntity` 的 `channelId -> pileSn` 反向映射。 + - 保留 `NettyServerHandler` 的消息处理耗时监控。 + - 不采用“心跳全异步 + 公共线程池 DiscardOldestPolicy”组合。 +- 已确认的最终收口原则: + - `HeartbeatRequestHandler` 中 `saveLastTimeAndCheckChannel()` 必须同步执行,保证任意上行立即刷新 `PILE_LAST_CONNECTION`,并立即清除 `PILE_PENDING_DISCONNECT / PILE_OFFLINE_CONFIRMED`。 + - `HeartbeatRequestHandler` 中 `updateStatus()` 继续异步执行,避免数据库操作拖长心跳回包链路。 + - `ThreadPoolConfig.threadPoolTaskExecutor` 保持 `CallerRunsPolicy`,避免关键任务被静默丢弃。 + - `PileChannelEntity.removeByPileSnAndChannelId()` 必须同步清理反向映射,避免旧连接脏数据残留。 + +### Quartz 配置建议 + +- 新增每日对时任务: + - `invokeTarget`: `jsowellTask.dailyProofreadTimeForYkcV160()` +- 不建议把任务压在 `00:00` 整点,建议放到低峰时段,例如 `03:15`。 +- 每日对时任务默认只扫描“当前有活连接的云快充 1.6 桩”,并按小间隔逐台发送,避免凌晨集中打满链路。 + +### 上线后重点观察 + +- 是否还出现“凌晨批量掉线后立刻落离线”的情况。 +- 是否能看到 `pile_pending_disconnect` 在恢复通信后于宽限期内被清除。 +- 是否出现“正式离线确认”日志明显下降。 +- 是否能看到 `0x56` 下发记录与 `0x55` 应答记录。 +- 是否持续出现 `【性能警告】` 日志堆积。 + +### 建议回归项 + +- 连续触发 1 次、2 次、3 次 `READER_IDLE`: + - 预期仅第 3 次关闭连接。 +- 第 3 次空闲关闭后 60 秒内重连: + - 预期不落 `OFF_NETWORK`,不改订单异常。 +- 第 3 次空闲关闭后 60 秒内未恢复: + - 预期才正式落离线。 +- 手工执行一次 `jsowellTask.dailyProofreadTimeForYkcV160()`: + - 预期仅对在线云快充 1.6 桩发对时。 +- 检查 `TimeCheckSettingResponseHandler`: + - 预期能记录 `0x55` 对时应答及桩端回传时间。 + +### 编译验证记录 + +- 已执行: + - `mvn -pl jsowell-quartz,jsowell-netty -am -DskipTests compile` + - `mvn -pl jsowell-netty,jsowell-framework,jsowell-common -am -DskipTests compile` +- 结果: + - 通过。 + ## 当前建议 - 先以第一批任务为本周主线,尽快止住凌晨离线误判。