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

267 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 充电桩频繁登录问题优化记录
## 问题背景
**发生时间**: 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`
**修改内容**:
```java
// 修改前
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`
**修改内容**:
```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`
**修改内容**:
```java
// 修改前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`
**修改内容**:
```java
@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`
**修改内容**:
```java
// 修改前
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`
**修改内容**:
```java
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 绑定到业务线程池<br>IdleStateHandler 从 30s 调整为 15s | ✅ 完成 |
| 2 | `jsowell-common/.../PileChannelEntity.java` | 新增反向映射,优化查询性能<br>新增 removeByPileSnAndChannelId 方法 | ✅ 完成 |
| 3 | `jsowell-framework/.../ThreadPoolConfig.java` | 修改拒绝策略,增大队列容量 | ✅ 完成 |
| 4 | `jsowell-netty/.../HeartbeatRequestHandler.java` | 心跳处理完全异步化 | ✅ 完成 |
| 5 | `jsowell-netty/.../NettyServerHandler.java` | 增加消息处理耗时监控<br>新增 AttributeKey 用于连接管理 | ✅ 完成 |
---
## 测试建议
### 1. 编译打包
```bash
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 |
| 提交建议 | 建议创建新分支提交,便于回滚 |
| 状态 | ✅ 全部优化已完成 |