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

## 问题背景

**发生时间**: 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 |
| 提交建议 | 建议创建新分支提交，便于回滚 |
| 状态 | ✅ 全部优化已完成 |
