【微电平台】-高并发实战经验-奇葩问题解决之旅
字数 2276 2025-08-11 08:36:04

高并发系统问题排查与优化实战教学文档

1. 案例背景

1.1 系统概述

微电平台是一个综合智能SCRM SAAS化系统,集电销、企业微信等功能于一体,具有以下特点:

  • 60+京东业务线入驻
  • 每日处理上亿客户名单
  • 提供千人千面的配置功能
  • 基于规则引擎设计实现
  • 包含金融刷单标记、风控标记、人群画像标记等多维度复杂接口

1.2 问题场景

  • 业务场景:每日凌晨1~8点进行客户名单离线打标
  • 规模:80台机器处理,平均95万名单/分钟
  • 性能指标
    • 拒绝营销JSF服务总TPS约2万
    • TP99在100~110ms
  • 问题表现
    • 2023年2月24日发现打标未按时完成
    • JMQ消费端TP99过高
    • 吞吐量降低40%
    • 4台机器频繁出现"问题"

2. 问题排查与分析

2.1 为什么少量问题机器会导致吞吐急剧下降?

根本原因分析

  1. JMQ重试机制

    • 每条消息消费报错会导致本地sleep并重新消费拉下的所有消息(batchSize=10)
    • 每次报错增加至少1000ms耗时
  2. JSF负载均衡

    • 使用默认随机负载均衡算法
    • 80台机器中4台问题机器的命中概率为5%
    • 10条消息中只要1条命中问题机器就会显著降低吞吐

解决方案

  1. 优化JMQ重试配置:
<jmq:consumer id="cusAttributionMarkConsumer" transport="jmqTransport">
  <jmq:listener topic="${jmq.topic}" listener="jmqListener" 
    retryDelay="10" maxRetryDelay="20" maxRetrys="1"/>
</jmq:consumer>
  1. 修改JSF负载均衡算法:
<jsf:consumer loadbalance="shortestresponse"/>

负载均衡算法对比

  • Random(默认):完全随机,不考虑服务端表现
  • ShortestResponse:基于服务请求响应时间动态调整,类似熔断机制

2.2 如何判定问题实例

判断依据

  1. 时间段内耗时高或失败的请求集中在特定IP
  2. 大量IP出现类似现象则非机器硬件问题

监控指标

  • SGM监控显示特定机器持续高耗时
  • 问题机器TPS几乎为0
  • JSF线程池满错误

2.3 线程假死问题定位

排查步骤

  1. 内存分析

    • 使用行云工具dump内存对象
    • 内存占用正常,无显著问题
  2. 线程堆栈分析

    • jstack显示所有JSF线程处于WAITING状态
    • 典型线程堆栈示例:
      // 线程1
      at com.jd.jr.scg.service.common.crowd.UserCrowdHitResult.isHit(UserCrowdHitResult.java:36)
      at com.jd.jr.scg.service.impl.BlacklistTempNewServiceImpl.callTimes(...)
      
      // 线程2 
      at com.jd.jr.scg.service.biz.BlacklistBiz.isBlacklist(BlacklistBiz.java:337)
      

问题代码模式

  1. 主线程使用线程池A执行任务X
  2. 任务X内部又使用同一个线程池A执行子任务
  3. 当线程池资源不足时形成死锁等待

验证方法

  • 将业务线程池从50扩容到200
  • 观察TPS抖动情况,确认无线程等待时服务正常

2.4 同步锁性能问题

发现过程

  • 线程堆栈中存在大量"waiting to lock"信息
  • 服务链路中3个方法使用同一把锁

解决方案

  • 使用Caffeine本地缓存替代同步锁维护的手写缓存

2.5 JSF线程池满的特殊行为

意外发现

  • 当服务端报错:"[JSF-23003]Biz thread pool of provider has bean exhausted"
  • 客户端不会自动重试(与其他RpcException行为不同)

解决方案

  • 需要在客户端代码中实现自定义重试逻辑

3. 优化实施与效果

3.1 优化措施汇总

  1. 解决线程等待问题:

    • 新增独立线程池,避免线程池嵌套使用
  2. 性能优化:

    • 使用Caffeine替换同步锁缓存实现
  3. 配置调优:

    • 调整JMQ重试参数
    • 修改JSF负载均衡算法

3.2 优化效果

指标 优化前 优化后 提升幅度
TPS 2万 3万 +50%
TP99 100-110ms 65ms -35%~-40%
JMQ重试TP99 1100ms 300ms -72%
任务完成时间 8点 5点前 时间缩短57%

4. 经验总结与最佳实践

4.1 高并发系统设计原则

  1. 线程池使用规范

    • 避免线程池嵌套使用
    • 为不同层级任务分配独立线程池
  2. 锁优化

    • 减少同步锁范围
    • 优先考虑无锁设计或并发容器
  3. 中间件配置

    • 根据业务特点调整重试策略
    • 选择合适的负载均衡算法

4.2 问题排查方法论

  1. 监控先行

    • 建立完善的监控体系(SGM、JMQ、JSF等)
  2. 工具使用

    • 内存分析:内存dump
    • 线程分析:jstack
  3. 推理验证

    • 提出假设 → 设计验证 → 确认结论
  4. 根因定位

    • 从表象到本质层层深入
    • 区分症状与病因

4.3 性能优化checklist

  1. [ ] 线程池配置合理性检查
  2. [ ] 锁竞争分析
  3. [ ] 中间件参数调优
  4. [ ] 负载均衡策略评估
  5. [ ] 异常处理机制审查

5. 附录:关键技术点

5.1 JMQ关键配置参数

参数 说明 建议值
retryDelay 重试延迟时间(ms) 10-50
maxRetryDelay 最大重试延迟时间(ms) 20-100
maxRetrys 最大重试次数 1-3

5.2 JSF负载均衡算法对比

算法 原理 适用场景
random 完全随机 服务端性能均匀
roundrobin 轮询 服务端性能均匀
shortestresponse 基于响应时间 服务端性能不均
consistenthash 一致性哈希 需要会话保持

5.3 线程池嵌套问题代码示例

// 错误示例:线程池嵌套使用
public void process() {
    executorService.submit(() -> {
        // 业务逻辑
        executorService.submit(() -> {  // 使用同一个线程池
            // 子任务
        });
    });
}

// 正确做法:使用独立线程池
public void process() {
    executorService.submit(() -> {
        // 业务逻辑
        subTaskExecutor.submit(() -> {  // 使用不同线程池
            // 子任务
        });
    });
}

通过本案例的系统性分析和解决方案,我们不仅解决了眼前的问题,还建立了预防类似问题的长效机制,为高并发系统的稳定运行提供了可靠保障。

高并发系统问题排查与优化实战教学文档 1. 案例背景 1.1 系统概述 微电平台是一个综合智能SCRM SAAS化系统,集电销、企业微信等功能于一体,具有以下特点: 60+京东业务线入驻 每日处理上亿客户名单 提供千人千面的配置功能 基于规则引擎设计实现 包含金融刷单标记、风控标记、人群画像标记等多维度复杂接口 1.2 问题场景 业务场景 :每日凌晨1~8点进行客户名单离线打标 规模 :80台机器处理,平均95万名单/分钟 性能指标 : 拒绝营销JSF服务总TPS约2万 TP99在100~110ms 问题表现 : 2023年2月24日发现打标未按时完成 JMQ消费端TP99过高 吞吐量降低40% 4台机器频繁出现"问题" 2. 问题排查与分析 2.1 为什么少量问题机器会导致吞吐急剧下降? 根本原因分析 : JMQ重试机制 : 每条消息消费报错会导致本地sleep并重新消费拉下的所有消息(batchSize=10) 每次报错增加至少1000ms耗时 JSF负载均衡 : 使用默认随机负载均衡算法 80台机器中4台问题机器的命中概率为5% 10条消息中只要1条命中问题机器就会显著降低吞吐 解决方案 : 优化JMQ重试配置: 修改JSF负载均衡算法: 负载均衡算法对比 : Random(默认) :完全随机,不考虑服务端表现 ShortestResponse :基于服务请求响应时间动态调整,类似熔断机制 2.2 如何判定问题实例 判断依据 : 时间段内耗时高或失败的请求集中在特定IP 大量IP出现类似现象则非机器硬件问题 监控指标 : SGM监控显示特定机器持续高耗时 问题机器TPS几乎为0 JSF线程池满错误 2.3 线程假死问题定位 排查步骤 : 内存分析 : 使用行云工具dump内存对象 内存占用正常,无显著问题 线程堆栈分析 : jstack显示所有JSF线程处于WAITING状态 典型线程堆栈示例: 问题代码模式 : 主线程使用线程池A执行任务X 任务X内部又使用同一个线程池A执行子任务 当线程池资源不足时形成死锁等待 验证方法 : 将业务线程池从50扩容到200 观察TPS抖动情况,确认无线程等待时服务正常 2.4 同步锁性能问题 发现过程 : 线程堆栈中存在大量"waiting to lock"信息 服务链路中3个方法使用同一把锁 解决方案 : 使用Caffeine本地缓存替代同步锁维护的手写缓存 2.5 JSF线程池满的特殊行为 意外发现 : 当服务端报错:"[ JSF-23003 ]Biz thread pool of provider has bean exhausted" 客户端不会自动重试(与其他RpcException行为不同) 解决方案 : 需要在客户端代码中实现自定义重试逻辑 3. 优化实施与效果 3.1 优化措施汇总 解决线程等待问题: 新增独立线程池,避免线程池嵌套使用 性能优化: 使用Caffeine替换同步锁缓存实现 配置调优: 调整JMQ重试参数 修改JSF负载均衡算法 3.2 优化效果 | 指标 | 优化前 | 优化后 | 提升幅度 | |------|--------|--------|----------| | TPS | 2万 | 3万 | +50% | | TP99 | 100-110ms | 65ms | -35%~-40% | | JMQ重试TP99 | 1100ms | 300ms | -72% | | 任务完成时间 | 8点 | 5点前 | 时间缩短57% | 4. 经验总结与最佳实践 4.1 高并发系统设计原则 线程池使用规范 : 避免线程池嵌套使用 为不同层级任务分配独立线程池 锁优化 : 减少同步锁范围 优先考虑无锁设计或并发容器 中间件配置 : 根据业务特点调整重试策略 选择合适的负载均衡算法 4.2 问题排查方法论 监控先行 : 建立完善的监控体系(SGM、JMQ、JSF等) 工具使用 : 内存分析:内存dump 线程分析:jstack 推理验证 : 提出假设 → 设计验证 → 确认结论 根因定位 : 从表象到本质层层深入 区分症状与病因 4.3 性能优化checklist [ ] 线程池配置合理性检查 [ ] 锁竞争分析 [ ] 中间件参数调优 [ ] 负载均衡策略评估 [ ] 异常处理机制审查 5. 附录:关键技术点 5.1 JMQ关键配置参数 | 参数 | 说明 | 建议值 | |------|------|--------| | retryDelay | 重试延迟时间(ms) | 10-50 | | maxRetryDelay | 最大重试延迟时间(ms) | 20-100 | | maxRetrys | 最大重试次数 | 1-3 | 5.2 JSF负载均衡算法对比 | 算法 | 原理 | 适用场景 | |------|------|----------| | random | 完全随机 | 服务端性能均匀 | | roundrobin | 轮询 | 服务端性能均匀 | | shortestresponse | 基于响应时间 | 服务端性能不均 | | consistenthash | 一致性哈希 | 需要会话保持 | 5.3 线程池嵌套问题代码示例 通过本案例的系统性分析和解决方案,我们不仅解决了眼前的问题,还建立了预防类似问题的长效机制,为高并发系统的稳定运行提供了可靠保障。