Files
jsowell-charger-web/improve_record.md

6.2 KiB
Raw Blame History

充电桩频繁登录问题优化记录

问题背景

发生时间: 2026-03-21 06:00 左右

问题描述: 充电桩出现频繁登录的情况

初步原因: 项目没有及时回复桩端的心跳消息,未及时回复超过 3 次后桩端会发起重连


问题分析

问题 1: Pipeline 配置存在潜在阻塞点(最关键)

文件: jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerChannelInitializer.java:41

问题描述:

  • nettyServerHandler 绑定到 businessGroup 线程池32 线程)
  • echoServerHandler 没有绑定到任何 EventExecutorGroup,它会在 IO 线程Worker 线程)中执行
  • 如果 IO 线程被其他任务阻塞,心跳回复就无法及时发送

影响: 这是导致心跳回复不及时的最主要原因!


问题 2: 心跳处理中存在阻塞的数据库操作

文件: jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/HeartbeatRequestHandler.java:54

问题描述:

  • saveLastTimeAndCheckChannel()同步执行的,包含 Redis 写操作
  • 如果 Redis 响应慢,会阻塞心跳响应

问题 3: PileChannelEntity.checkChannel() 使用同步阻塞遍历

文件: jsowell-common/src/main/java/com/jsowell/common/enums/ykc/PileChannelEntity.java:108-115

问题描述:

  • getPileSnByChannelId() 使用线性遍历整个 manager.entrySet()
  • 当连接数很多时,这个遍历操作会很慢,并且会阻塞执行线程

问题 4: 线程池配置可能导致任务堆积

文件: jsowell-framework/src/main/java/com/jsowell/framework/config/ThreadPoolConfig.java:63

问题描述:

  • 使用 CallerRunsPolicy 拒绝策略,当队列满时,任务会在调用者线程中执行
  • 如果 Netty 的 IO 线程或 businessGroup 线程作为调用者,会阻塞这些关键线程

优化方案

优化 1: 修复 Pipeline 配置优先级P0

文件: jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerChannelInitializer.java:41

修改内容:

// 修改前
pipeline.addLast(businessGroup, nettyServerHandler);
pipeline.addLast(echoServerHandler);

// 修改后
pipeline.addLast(businessGroup, nettyServerHandler);
pipeline.addLast(businessGroup, echoServerHandler);  // 回复Handler也绑定到业务线程池

效果: 避免心跳回复被 IO 线程阻塞


优化 2: 优化 PileChannelEntity 使用双向映射优先级P0

文件: jsowell-common/src/main/java/com/jsowell/common/enums/ykc/PileChannelEntity.java

修改内容:

  1. 新增反向映射 channelIdToPileSnMap
  2. getPileSnByChannelId() 从 O(n) 优化到 O(1)
  3. 同步维护反向映射checkChannel、removeByPileSn、removeByChannelId

效果: 当连接数多时,查询性能大幅提升


优化 3: 修改线程池拒绝策略优先级P1

文件: jsowell-framework/src/main/java/com/jsowell/framework/config/ThreadPoolConfig.java

修改内容:

// 修改前
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
private final int queueCapacity = 2000;

// 修改后
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
private final int queueCapacity = 5000;  // 增大队列容量

效果: 避免阻塞关键线程Netty IO 线程、业务线程池)


优化 4: 心跳处理完全异步化优先级P1

文件: jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/HeartbeatRequestHandler.java:67-85

修改内容:

// 修改前saveLastTimeAndCheckChannel 同步执行
saveLastTimeAndCheckChannel(pileSn, channel);
CompletableFuture.runAsync(() -> updateStatus(...), executor);

// 修改后:先返回心跳应答,再异步处理
byte[] response = getResult(ykcDataProtocol, messageBody);
CompletableFuture.runAsync(() -> {
    saveLastTimeAndCheckChannel(pileSn, channel);
    pileBasicInfoService.updateStatus(...);
}, executor);
return response;

效果: 心跳回复不受 Redis/数据库操作影响


优化 5: 增加心跳处理耗时监控优先级P2

文件: jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerHandler.java:174-180

修改内容:

long elapsed = System.currentTimeMillis() - startTime;
int warnThreshold = "0x03".equals(frameTypeStr) ? 50 : 200;
if (elapsed > warnThreshold) {
    log.error("【性能警告】消息处理耗时过长: {}ms, 帧类型: {}, pileSn: {}", elapsed, frameTypeStr, pileSn);
}

效果: 便于后续排查性能问题


修改文件清单

序号 文件路径 修改内容
1 jsowell-netty/.../NettyServerChannelInitializer.java EchoServerHandler 绑定到业务线程池
2 jsowell-common/.../PileChannelEntity.java 新增反向映射,优化查询性能
3 jsowell-framework/.../ThreadPoolConfig.java 修改拒绝策略,增大队列容量
4 jsowell-netty/.../HeartbeatRequestHandler.java 心跳处理完全异步化
5 jsowell-netty/.../NettyServerHandler.java 增加消息处理耗时监控

测试建议

1. 编译打包

mvn clean package -DskipTests

2. 部署后观察日志

关注以下指标:

  • 是否还有桩频繁登录的情况
  • 心跳处理耗时是否降低
  • 是否出现 【性能警告】 日志

3. 监控 Redis 和数据库性能

  • Redis 响应时间应 < 5ms
  • 数据库连接池应充足
  • 检查 Redis 服务性能

4. 持续监控

部署后持续观察 24 小时,确认问题是否彻底解决


预期效果

指标 优化前 优化后
心跳回复延迟 可能被阻塞 < 10ms
桩频繁登录 存在 消除
连接查询性能 O(n) O(1)
线程阻塞风险

相关文档

  • CLAUDE.md - 项目架构文档
  • jsowell-netty/CLAUDE.md - Netty 模块文档
  • jsowell-common/CLAUDE.md - 通用模块文档

记录信息

项目 内容
记录人 Claude Code
记录时间 2026-03-21
Git 分支 dev
提交建议 建议创建新分支提交,便于回滚