Files
jsowell-charger-web/improve_record.md
Lemon 04bff9bc7a 解决 Netty 合并冲突并添加耗时监控
- 解决 NettyServerHandler.java 的 Git 合并冲突
- 添加消息处理耗时监控到 channelRead 方法
- 更新 improve_record.md 优化记录文档
2026-03-21 10:35:59 +08:00

8.4 KiB
Raw Permalink 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
  4. 新增 removeByPileSnAndChannelId() 安全删除方法

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


优化 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:81-127

修改内容:

@Override
public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
    long startTime = System.currentTimeMillis();

    try {
        // ... 原有代码 ...
    } finally {
        ReferenceCountUtil.release(message);

        // 性能监控:记录消息处理耗时
        long elapsed = System.currentTimeMillis() - startTime;
        String frameType = ctx.channel().attr(LAST_FRAME_TYPE).get();
        // 心跳帧(0x03)处理超过50ms警告其他帧超过200ms警告
        int warnThreshold = "0x03".equals(frameType) ? 50 : 200;
        if (elapsed > warnThreshold) {
            log.warn("【性能警告】消息处理耗时: {}ms, 帧类型: {}, channelId: {}",
                     elapsed, frameType, ctx.channel().id().asLongText());
        }
    }
}

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


额外优化(团队完成)

优化 6: IdleStateHandler 超时调整

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

修改内容:

// 修改前
pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));

// 修改后
pipeline.addLast(new IdleStateHandler(15, 0, 0, TimeUnit.SECONDS));  // 读空闲tick设置为15s连续3次空闲后再由handler执行关闭

效果: 更快检测到心跳超时,及时处理连接异常


优化 7: 新增连接管理 AttributeKey

文件: jsowell-netty/src/main/java/com/jsowell/netty/server/yunkuaichong/NettyServerHandler.java:40-48

修改内容:

public static final AttributeKey<Integer> IDLE_COUNT = AttributeKey.valueOf("ykc_idle_count");
public static final AttributeKey<String> LAST_FRAME_TYPE = AttributeKey.valueOf("ykc_last_frame_type");
public static final AttributeKey<String> LAST_RECEIVE_AT = AttributeKey.valueOf("ykc_last_receive_at");
public static final AttributeKey<String> LAST_SERIAL_NUMBER = AttributeKey.valueOf("ykc_last_serial_number");
public static final AttributeKey<String> DISCONNECT_REASON = AttributeKey.valueOf("ykc_disconnect_reason");

效果: 更精细的连接管理和断开原因追踪


修改文件清单

序号 文件路径 修改内容 状态
1 jsowell-netty/.../NettyServerChannelInitializer.java EchoServerHandler 绑定到业务线程池
IdleStateHandler 从 30s 调整为 15s
完成
2 jsowell-common/.../PileChannelEntity.java 新增反向映射,优化查询性能
新增 removeByPileSnAndChannelId 方法
完成
3 jsowell-framework/.../ThreadPoolConfig.java 修改拒绝策略,增大队列容量 完成
4 jsowell-netty/.../HeartbeatRequestHandler.java 心跳处理完全异步化 完成
5 jsowell-netty/.../NettyServerHandler.java 增加消息处理耗时监控
新增 AttributeKey 用于连接管理
完成

测试建议

1. 编译打包

mvn clean package -DskipTests

2. 部署后观察日志

关注以下指标:

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

3. 监控 Redis 和数据库性能

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

4. 持续监控

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


预期效果

指标 优化前 优化后
心跳回复延迟 可能被阻塞 < 10ms
桩频繁登录 存在 消除
连接查询性能 O(n) O(1)
线程阻塞风险
超时检测响应 30s 15s更快

相关文档

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

记录信息

项目 内容
记录人 Claude Code + 开发团队
记录时间 2026-03-21
Git 分支 dev
提交建议 建议创建新分支提交,便于回滚
状态 全部优化已完成