性能优化实践分享

背景与挑战

我们项目在上线初期面临严重的性能瓶颈:

  • 性能现状:单机播放控制场景QPS仅为15左右,远低于预期
  • 技术特点:采用流式输出、大量长连接的架构
  • 业务压力:计划将400-500万用户接入平台
  • 核心风险:性能瓶颈成为平台上线的关键阻碍

通过系统性的性能优化,最终实现了单机QPS从15提升至70,平台可稳定支撑500万用户并发访问的显著改善。

优化方法论

分析工具与手段

  • JMeter:压力测试与性能基准测试
  • JProfiler:运行时性能监控与热点分析
  • MAT (Memory Analyzer Tool) :内存泄漏检测与对象分析
  • 系统监控:GC日志分析、线程状态监控

优化策略

采用多维度系统性优化策略,包括JVM调优、中间件优化、代码重构、架构调整等。


核心优化措施

一、缓存策略优化

问题分析:项目中存在大量高频查询场景未使用缓存,导致数据库压力过大,查询性能低下。

优化方案

  • 识别高频查询接口和数据热点
  • 为核心查询场景添加多级缓存
  • 实施缓存预热和失效策略
  • 建立缓存命中率监控机制

效果:显著减少数据库查询压力,提升响应速度。

二、流式输出技术选型对比

SseEmitter vs Flux 技术对比

维度 SseEmitter Flux (WebFlux)
编程模型 Servlet阻塞式 响应式编程
线程模型 每连接占用线程 非阻塞事件驱动
并发性能 中低并发场景适用 高并发表现优秀
学习成本 低,传统MVC风格 高,需响应式编程经验
兼容性 老项目友好 最适合WebFlux+Netty

实践结论

通过构建对照实验,发现在固定文本返回场景下,WebFlux性能虽有提升但未达到数量级差异。考虑到:

  • 代码重构成本较高
  • 团队技术栈适配度
  • 项目交付时间约束

决策:继续使用SseEmitter,通过其他维度优化达成性能目标。

三、工作流引擎优化

问题识别:MAT内存分析显示工作流执行链占用大量内存。

3.1 WorkflowId生成策略优化

原始方案:使用UUID生成唯一标识

ini 复制代码
// 问题:每次生成全新ID,无法复用工作流结构
String workflowId = UUID.randomUUID().toString();
​

优化方案:基于EL表达式生成确定性ID

ini 复制代码
// 解决:相同EL表达式生成相同ID,支持工作流复用
String workflowId = DigestUtils.md5Hex(elExpression);
​

优化效果

  • 内存优化:相同工作流结构复用,避免重复创建
  • 性能提升:跳过重复解析和编译步骤
  • 缓存友好:提高工作流引擎缓存效率

3.2 异步执行线程池优化

问题:LiteFlow默认线程池配置(核心16,最大64)在高并发场景下不足。

优化方案

java 复制代码
public class LiteFlowConfig {
    // 定义自定义线程池
    @Bean(name = "customLiteFlowExecutor")
    public ExecutorService customLiteFlowExecutor() {
        // 自定义线程工厂,设置线程名称前缀
        ThreadFactory threadFactory = new ThreadFactory() {
            private final AtomicInteger threadNumber = new AtomicInteger(1);
​
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("LiteFlow-Worker-" + threadNumber.getAndIncrement());
                return thread;
            }
        };
        // 配置线程池参数
        return new ThreadPoolExecutor(
            32, // 核心线程数
            128, // 最大线程数
            60L, TimeUnit.SECONDS, // 空闲线程存活时间
            new ArrayBlockingQueue<>(200), // 有界队列,容量200
            threadFactory, // 自定义线程工厂
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:回退到调用者线程
        );
    }
    // 配置 LiteFlow 执行器,注入自定义线程池
    @Bean
    public LiteflowExecutor liteflowExecutor(ExecutorService customLiteFlowExecutor) {
        LiteflowExecutor executor = LiteFlowProxyUtil.getInstance(LiteflowExecutor.class);
        executor.setExecutorService(customLiteFlowExecutor);
        return executor;
    }
}

四、线程池与并发优化

4.1 系统架构与资源配置

  • 硬件配置:4核8G云服务器
  • 部署架构:EA(入口服务) + SA(核心处理服务),容器化部署
  • 并发模式:大量SSE长连接 + 流式输出

4.2 串行处理改并行优化

问题:EA与SA任务串行执行,响应时间过长

优化方案:引入专用线程池实现任务并行

less 复制代码
// 并行处理,提升响应速度
            eaExecutor.execute(() ->{
             ......
            });
​
            saExecutor.execute(() ->{
             ......
            }
​

4.3 关键线程池参数调优

组件 原始配置 优化配置 调优理由
Tomcat最大线程 200 500 SSE长连接需要更多线程支撑
任务队列大小 10000 100 减少任务堆积,避免超时(>10s)
EA线程池 默认 核心100/最大200 匹配请求入口压力
SA线程池 默认 核心200/最大400 承载核心业务处理
HttpClient连接池 DefaultMaxPerRoute=100 400 避免EA->SA调用瓶颈

五、内存泄漏排查与修复

5.1 ShutdownHook内存泄漏

问题发现:压测结束后内存持续占用4G+,MAT分析发现大量shutdownHooks对象堆积。

根因分析

less 复制代码
// 问题代码:每次请求都注册新的ShutdownHook
Runtime.getRuntime().addShutdownHook(new Thread(eventSource::cancel));
​

问题机制

  • 高并发下每次执行都创建新Thread对象
  • Hook持有eventSource引用,阻止GC回收
  • JVM关闭前不会清理,造成内存泄漏

解决方案

less 复制代码
                // 删除ShutdownHook,在连接关闭时手动清理资源
                try {
                    // 业务逻辑处理
                } finally {
                    eventSource.cancel(); // 手动清理资源
                }
                @Override
                public void onFailure(EventSource eventSource, @Nullable Throwable t, @Nullable Response response) {
                    eventSource.cancel();
                }
​

5.2 OkHttpClient线程泄漏

问题:JProfiler显示压测期间线程数达到2000+,其中1000+由OkHttpClient创建。

根因

scss 复制代码
// 问题:每次调用都创建新的OkHttpClient实例
OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(timeout, TimeUnit.MILLISECONDS)
    .readTimeout(timeout, TimeUnit.MILLISECONDS)
    .writeTimeout(timeout, TimeUnit.MILLISECONDS)
    .build();
​

解决方案

scss 复制代码
// 复用现有OkHttpClient实例,避免重复创建
OkHttpClient client = okHttpClient.newBuilder()
    .connectTimeout(timeout, TimeUnit.MILLISECONDS)
    .build();
​

优化效果:峰值线程数从2000+降至600。

六、中间件性能调优

6.1 Kafka参数优化

问题:JProfiler显示大量Kafka阻塞线程,影响整体性能。

优化配置

ini 复制代码
# 提升吞吐量,降低延迟
kafka.producer.acks=0          # 原值:-1,降低可靠性要求
kafka.producer.linger.ms=1000  # 原值:1,批量发送提升效率
​

拒绝策略调整

  • 原策略:CallerRunsPolicy(主线程执行)
  • 新策略:DiscardPolicy(直接丢弃)
  • 理由:避免影响主线程性能,数据重要性相对较低

6.2 ID生成策略优化

问题:MongoDB串行获取ID导致加锁排队,效率低下。

解决方案:引入雪花算法

  • 避免数据库IO操作
  • 消除串行生成ID的阻塞
  • 保证ID全局唯一性
  • 提升高并发场景下的ID生成效率

七、连接管理优化

7.1 连接泄漏修复

问题发现:使用netstat命令检查发现大量连接未正常关闭

perl 复制代码
for %i in (ESTABLISHED LISTENING TIME_WAIT CLOSE_WAIT SYN_SENT) do @echo %i: & netstat -ano | findstr %i | find /c /v ""
​

根因:异常情况下SseEmitterUTF8连接未正确关闭。

解决方案:完善异常处理机制,确保连接在异常状态下也能正确释放。


压测实践经验

一、JMeter配置要点

关键参数设置

  • 线程数设置:从50开始逐步调整,避免设置过大(如400)
  • 原因分析:流式长连接项目响应较慢,过高并发可能导致测试不准确
  • 建议策略:渐进式压力测试,找到系统真实瓶颈点

环境隔离优化

单机压测问题:JMeter本身资源消耗可能影响被测服务性能

解决方案

  • 测试机与服务机分离部署
  • 独立的网络环境避免带宽竞争
  • 监控JMeter自身资源使用情况

二、熔断机制处理

压测期间熔断策略

临时关闭熔断的必要性

  1. 压测环境特殊性

    • 请求量远超正常负载
    • 服务响应时间自然增长
    • 熔断机制可能误判服务不可用
  2. 熔断对测试的影响

    • 拒绝部分请求,无法反映真实性能
    • 无法测试系统极限承载能力
    • 影响性能指标的准确性
  3. 临时关闭的好处

    • 确保请求完整到达目标服务
    • 避免测试结果偏差
    • 准确测量系统真实性能表现

线程池使用规范

核心原则

  1. 优先自定义ThreadPoolExecutor:避免Executors默认(如newFixedThreadPool的无界队列易OOM)。

理由:灵活控制队列/拒绝策略。

  1. 设置有界队列

理由:防积压,早拒绝。

  1. 自定义ThreadFactory:线程名前缀(如"EA-Task-")。

理由:便于JProfiler追踪/调试。

  1. 拒绝策略选择:非关键任务用DiscardPolicy;关键用CallerRunsPolicy。

理由:平衡性能与可靠性。

  1. 指定线程池: 在使用 @Async 或其他异步方式(如 CompletableFuture、LiteFlow 等)时,最好显式指定线程池,而不是依赖默认线程池。

理由:显式指定线程池能精确控制资源、优化性能、便于监控,避免默认配置的"黑盒"风险。


总结与展望

优化成果

  • 线程
  • 性能指标:单机QPS从15提升至70,提升366%
  • 用户支撑:平台稳定支撑500万用户并发访问
  • 系统稳定性:解决内存泄漏、连接泄漏等关键问题
  • 资源利用率:通过参数调优充分利用硬件资源

优化方法总结

  1. 系统性分析:结合多种工具进行全面性能诊断
  2. 分层优化:从JVM、框架、应用代码多层面优化
  3. 数据驱动:基于压测数据和监控指标进行决策
  4. 风险控制:保持系统稳定性的前提下进行优化
相关推荐
大学生资源网几秒前
基于springboot的万亩助农网站的设计与实现源代码(源码+文档)
java·spring boot·后端·mysql·毕业设计·源码
苏三的开发日记9 分钟前
linux端进行kafka集群服务的搭建
后端
苏三的开发日记28 分钟前
windows系统搭建kafka环境
后端
爬山算法38 分钟前
Netty(19)Netty的性能优化手段有哪些?
java·后端
Tony Bai38 分钟前
Cloudflare 2025 年度报告发布——Go 语言再次“屠榜”API 领域,AI 流量激增!
开发语言·人工智能·后端·golang
想用offer打牌1 小时前
虚拟内存与寻址方式解析(面试版)
java·后端·面试·系统架构
無量1 小时前
AQS抽象队列同步器原理与应用
后端
9号达人2 小时前
支付成功订单却没了?MyBatis连接池的坑我踩了
java·后端·面试
用户497357337982 小时前
【轻松掌握通信协议】C#的通信过程与协议实操 | 2024全新
后端
草莓熊Lotso2 小时前
C++11 核心精髓:类新功能、lambda与包装器实战
开发语言·c++·人工智能·经验分享·后端·nginx·asp.net