• 性能问题从发现到优化一般思路


    欢迎大家关注公众号「马小乐学技术」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我微信「sy200316x」一起交流学习

    1 文章概述

    技术系统有一个发展过程,在业务初期主要是实现业务功能和目标,由于数据和访问量都不大,所以性能问题并不作为首要考虑。

    但是随着业务发展,随着数据和访问量增大甚至激增,造成例如首页五秒钟才能展示等问题,这种不佳体验会造成用户流失,此时性能就是必须面对之问题。我们把技术系统分为早期、中期、后期三个阶段:

    • 早期:主要实现业务需求,性能非重点考虑
    • 中期:性能问题注解显现,影响业务发展
    • 后期:技术迭代性能与业务必须同时考虑

    如何发现性能问题,并且最终如何解决性能问题就是本文讨论之要点。

    2 什么是性能

    我们可以从四个维度介绍什么是性能:

    • 两个维度定义性能:
      • 速度慢
      • 压力大
    • 两个维度描述性能:
      • 定性:直观感受
      • 定量:指标分析

    3 发现性能问题

    3.1 定性 + 速度

    一个页面需要长时间打开,一个列表很慢才能加载完成,一个接口访问导致超时异常,这些显而易见之问题可以归为此类。

    3.2 定量 + 速度

    3.2.1 速度指标

    一个公司有7200名员工,每天上班打卡时间是早上8点到8点30分,每次打卡时间系统执行时长5秒,那么RT、QPS、并发量分别是多少?

    RT表示响应时间,问题已经包含答案:

    • RT = 5秒

    QPS表示每秒访问量,假设行为平均分布:

    • QPS = 7200 / (30 * 60) = 4

    并发量表示系统同时处理请求数:

    • 并发量 = QPS x RT = 4 x 5 = 20

    根据上述实例引出公式:

    • 并发量 = QPS x RT

    3.2.2 QPS VS TPS

    1. QPS(Queries Per Second):每秒查询量
    2. TPS(Transactions Per Second):每秒事务数
    3. 复制代码

    需要注意此事务并不是指数据库事务,而是包括以下三个阶段:

    • 接收请求
    • 处理业务
    • 返回结果
    1. QPS = N * TPS (N>=1)
    2. 复制代码

    N=1表示接口有一个事务:

    1. public class OrderService {
    2. public Order queryOrderById(String orderId) {
    3. return orderMapper.selectById(orderId);
    4. }
    5. }
    6. 复制代码

    N>1表示接口有多个事务:

    1. public class OrderService {
    2. public void updateOrder(Order order) {
    3. // transaction1
    4. orderMapper.update(order);
    5. // transaction2
    6. sendOrderUpdateMessage(order);
    7. }
    8. }
    9. 复制代码

    3.2.3 发现问题

    (1) 打印日志

    1. public class FastTestService {
    2. public void test01() {
    3. long start = System.currentTimeMillis();
    4. biz1();
    5. biz2();
    6. long costTime = System.currentTimeMillis() - start;
    7. System.out.println("costTime=" + costTime);
    8. }
    9. private void biz1() {
    10. try {
    11. System.out.println("biz1");
    12. Thread.sleep(500L);
    13. } catch (Exception ex) {
    14. log.error("error", ex);
    15. }
    16. }
    17. private void biz2() {
    18. try {
    19. System.out.println("biz2");
    20. Thread.sleep(1000L);
    21. } catch (Exception ex) {
    22. log.error("error", ex);
    23. }
    24. }
    25. }
    26. 复制代码

    (2) StopWatch

    1. import org.springframework.util.StopWatch;
    2. import org.springframework.util.StopWatch.TaskInfo;
    3. public class FastTestService {
    4. public void test02() {
    5. StopWatch sw = new StopWatch("testWatch");
    6. sw.start("biz1");
    7. biz1();
    8. sw.stop();
    9. sw.start("biz2");
    10. biz2();
    11. sw.stop();
    12. // 简单输出耗时
    13. System.out.println("costTime=" + sw.getTotalTimeMillis());
    14. System.out.println();
    15. // 输出任务信息
    16. TaskInfo[] taskInfos = sw.getTaskInfo();
    17. for (TaskInfo task : taskInfos) {
    18. System.out.println("taskInfo=" + JSON.toJSONString(task));
    19. }
    20. System.out.println();
    21. // 格式化任务信息
    22. System.out.println(sw.prettyPrint());
    23. }
    24. }
    25. 复制代码

    输出结果:

    1. costTime=1526
    2. taskInfo={"taskName":"biz1","timeMillis":510,"timeNanos":510811200,"timeSeconds":0.5108112}
    3. taskInfo={"taskName":"biz2","timeMillis":1015,"timeNanos":1015439700,"timeSeconds":1.0154397}
    4. StopWatch 'testWatch': running time = 1526250900 ns
    5. ---------------------------------------------
    6. ns % Task name
    7. ---------------------------------------------
    8. 510811200 033% biz1
    9. 1015439700 067% biz2
    10. 复制代码

    (3) trace

    Arthas是阿里开源Java诊断工具:

    1. Arthas是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率
    2. 复制代码

    Arthas trace命令监控链路每个节点耗时:

    1. https://arthas.aliyun.com/doc/trace.html
    2. 复制代码

    我们通过实例说明,首先编写并运行代码:

    1. package java.front.optimize;
    2. public class FastTestService {
    3. public static void main(String[] args) {
    4. FastTestService service = new FastTestService();
    5. while (true) {
    6. service.test03();
    7. }
    8. }
    9. public void test03() {
    10. biz1();
    11. biz2();
    12. }
    13. private void biz1() {
    14. try {
    15. System.out.println("biz1");
    16. Thread.sleep(500L);
    17. } catch (Exception ex) {
    18. log.error("error", ex);
    19. }
    20. }
    21. private void biz2() {
    22. try {
    23. System.out.println("biz2");
    24. Thread.sleep(1000L);
    25. } catch (Exception ex) {
    26. log.error("error", ex);
    27. }
    28. }
    29. }
    30. 复制代码

    第一步进入arthas控制台:

    1. $ java -jar arthas-boot.jar
    2. [INFO] arthas-boot version: 3.6.2
    3. [INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER
    4. * [1]: 14121
    5. [2]: 20196 java.front.optimize.FastTestService
    6. 复制代码

    第二步输入监控进程号并回车:

    1. 2
    2. 复制代码

    第三步trace命令监控相应方法:

    1. trace java.front.optimize.FastTestService test03
    2. 复制代码

    第四步查看链路耗时:

    1. `---[1518.7362ms] java.front.optimize.FastTestService:test03()
    2. +---[33.66% 511.2817ms ] java.front.optimize.FastTestService:biz1() #54
    3. `---[66.32% 1007.2962ms ] java.front.optimize.FastTestService:biz2() #55
    4. 复制代码

    3.3 定性 + 压力

    系统压力大也会表现出速度慢之特征,但是这种慢不仅仅是需要几秒后才能打开网页,而是网页一直处于加载状态最终白屏。

    3.4 定量 + 压力

    服务器常见压力指标如下:

    • 内存
    • CPU
    • 磁盘
    • 网络

    服务端开发比较容易引发内存和CPU问题,所以我们重点关注。

    3.4.1 发现CPU问题

    首先编写一段造成CPU飙高之代码并运行:

    1. public class FastTestService {
    2. public static void main(String[] args) {
    3. FastTestService service = new FastTestService();
    4. while (true) {
    5. service.test();
    6. }
    7. }
    8. public void test() {
    9. biz();
    10. }
    11. private void biz() {
    12. System.out.println("biz");
    13. }
    14. }
    15. 复制代码

    (1) dashboard + thread

    dashboard查看当前系统实时面板,发现线程ID=1 CPU占用非常高(这个ID不可以与jstack nativeID相对应):

    1. $ dashboard
    2. ID NAME GROUP PRIORI STATE %CPU DELTA TIME TIME INTERRU DAEMON
    3. 1 main main 5 RUNNA 96.06 4.812 2:41.2 false false
    4. 复制代码

    thread查看最忙前N个线程:

    1. $ thread -n 1
    2. "main" Id=1 deltaTime=203ms time=1714000ms RUNNABLE
    3. at app//java.front.optimize.FastTestService.biz(FastTestService.java:83)
    4. at app//java.front.optimize.FastTestService.test(FastTestService.java:61)
    5. at app//java.front.optimize.FastTestService.main(FastTestService.java:17)
    6. 复制代码

    3.4.2 发现内存问题

    (1) free

    1. $ free -h
    2. total used free shared buff/cache available
    3. Mem: 10G 5.5G 3.1G 28M 1.4G 4.4G
    4. Swap: 2.0G 435M 1.6G
    5. total
    6. 服务器总内存
    7. used
    8. 已使用内存
    9. free
    10. 未被任何应用使用空闲内存
    11. shared
    12. 被共享物理内存
    13. cache
    14. IO设备读缓存(Page Cache)
    15. buff
    16. IO设备写缓存(Buffer Cache)
    17. available
    18. 可以被程序应用之内存
    19. 复制代码

    (2) memory

    Arthas memory命令查看JVM内存信息:

    1. https://arthas.aliyun.com/doc/heapdump.html
    2. 复制代码
    • 查看JVM内存信息(官方实例)
    1. $ memory
    2. Memory used total max usage
    3. heap 32M 256M 4096M 0.79%
    4. g1_eden_space 11M 68M -1 16.18%
    5. g1_old_gen 17M 184M 4096M 0.43%
    6. g1_survivor_space 4M 4M -1 100.00%
    7. nonheap 35M 39M -1 89.55%
    8. codeheap_'non-nmethods' 1M 2M 5M 20.53%
    9. metaspace 26M 27M -1 96.88%
    10. codeheap_'profiled_nmethods' 4M 4M 117M 3.57%
    11. compressed_class_space 2M 3M 1024M 0.29%
    12. codeheap_'non-profiled_nmethods' 685K 2496K 120032K 0.57%
    13. mapped 0K 0K - 0.00%
    14. direct 48M 48M - 100.00%
    15. 复制代码

    (3) jmap

    • 查看JAVA程序进程号
    1. jps -l
    2. 复制代码
    • 查看实时内存占用
    1. jhsdb jmap --heap --pid 20196
    2. 复制代码
    • 导出快照文件
    1. jmap -dump:format=b,file=/home/tmp/my-dump.hprof 20196
    2. 复制代码
    • 内存溢出自动导出堆快照
    1. -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath==/home/tmp/my-dump.hprof
    2. 复制代码

    (4) heapdump

    Arthas heapdump命令支持导出堆快照:

    1. https://arthas.aliyun.com/doc/heapdump.html
    2. 复制代码
    • dump至指定文件
    1. heapdump /home/tmp/my-dump.hprof
    2. 复制代码
    • dump live对象至指定文件
    1. heapdump --live /home/tmp/my-dump.hprof
    2. 复制代码
    • dump至临时文件
    1. heapdump
    2. 复制代码

    (5) 垃圾回收

    jstat可以查看垃圾回收情况,观察程序是否频繁GC或者GC用时是否过长:

    1. jstat -gcutil <pid>
    2. 复制代码
    • 每秒查看垃圾回收情况
    1. $ jstat -gcutil 20196 1000
    2. S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
    3. 0.00 0.00 57.69 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
    4. 0.00 0.00 57.69 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
    5. 0.00 0.00 57.69 0.00 - - 0 0.000 0 0.000 0 0.000 0.000
    6. 复制代码

    各参数说明如下:

    1. S0:新生代中Survivor 0区占已使用空间比例
    2. S1:新生代中Survivor 1区占已使用空间比例
    3. E:新生代占已使用空间比例
    4. O:老年代占已使用空间比例
    5. P:永久带占已使用空间比例
    6. YGC:应用程序启动至今,发生Young GC次数
    7. YGCT:应用程序启动至今,Young GC所用时间(秒)
    8. FGC:应用程序启动至今,发生Full GC次数
    9. FGCT:应用程序启动至今,Full GC所用时间(秒)
    10. GCT:应用程序启动至今,所用垃圾回收总时间(秒)
    11. 复制代码

    3.4.3 综合发现问题

    (1) 压力测试

    对系统进行压测可以主动暴露系统问题,评估系统容量,简单常用参数如下:

    • 常用工具:JMeter
    • 阶梯发压:线程数10、20、30递增至瓶颈
    • 持续时间:持续1分钟,Ramp-Up=0
    • TPS:Throughput
    • 响应时间:重点关注95Line

    (2) 监控系统

    监控系统可以更加友好地展示相关指标,如果公司具有一定技术实力可以自研,否则可以选择使用业界通用方案。

    4 优化性能问题

    4.1 四个方法

    • 减少请求

    • 空间换时间

    • 任务并行化

    • 任务异步化

    4.2 五个层面

    • 代理层
    • 前端层
    • 服务层
    • 缓存层
    • 数据层

    4.3 优化说明

    一说到性能优化不难想到例如加索引、加缓存等方案,这也许是正确的,但是这样思考可能会造成遗漏,因为这只是缓存层和数据层的方案。

    如果可以将无效流量在最外层拒绝,那么这是对系统更好地好保护。四个方法可以应用在每一个层面,我们不妨举一些例子:

    (1) 减少请求 + 前端层

    在秒杀场景中设置前置验证码

    (2) 减少请求 + 服务层

    多次RPC是否可以转换为一次批量RPC

    (3) 空间换时间 + 服务层

    引入缓存

    (4) 空间换时间 + 缓存层

    引入多级缓存

    (5) 空间换时间 + 数据层

    新增索引

    (6) 任务并行化 + 服务层

    如果多个调用互不依赖,使用Future并行化

    (7) 任务异步化 + 服务层

    如果无需等待返回结果,可以异步执行

    5 文章总结

    第一本文讨论了系统早期、中期、后期如何看待性能问题,第二讨论了什么是性能,第三讨论了如果发现性能问题,第四讨论了如何优化性能问题,希望本文对大家有所帮助。

    欢迎大家关注公众号「马小乐学技术」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我微信「sy200316x」一起交流学习

  • 相关阅读:
    【华为OD机试真题 python】 堆栈中的剩余数字【2022 Q4 | 100分】
    图像降噪相关论文-从传统方法到深度学习
    Hive-DDL-常用操作命令(数据库操作、表操作、分区操作、show语法)
    解决 React forwardRef 与 TypeScript 泛型组件冲突的问题
    【Redis系列】缓存击穿、穿透、雪崩解决方案详解
    使用del语句删除名称对引用计数的影响
    [CISCN 2019初赛]Love Math - RCE(异或绕过)
    Inpaint Anything:一键进行多种图像修补
    软信天成:如何提高云数据仓库的数据质量?
    Unity9 路径权限、场景的加载、异步加载、场景跳转
  • 原文地址:https://blog.csdn.net/YYniannian/article/details/126221323