考试压测及瓶颈排查

引入

对整个考试系统再次进行压测,并给出排查瓶颈以及不断优化的过程

可能影响性能因素

  • 代码(设计缓存是否命中)
  • exam pod数量,pod的Xmx
  • gateway 、usercenter 数量
  • redis
  • mysql
  • 索引
  • 本地网速

最简单查看是否是瓶颈的方式:cpu利用率

压测参数

  • 人数x=6k~10k

  • warm-up=100s

  • 题目延时5~195s,平均delay = 100s

  • 只测exam 1pod 1核1G,并对比两次优化前后的提升

QPS:

  1. 进入峰值QPS = 在进入考试warm结尾:x/warm * 3(notices\startExam\getExamInfo) + x/delay = 40QPS / 1k人
  2. 稳定QPS = x/delay = 10QPS / 1k人

2K人

  1. 进入考试exam 40%利用率,mysql20%利用率 平稳后降到一半
  2. 交卷时exam突然崩溃,内存在考试阶段一直在上升,怀疑有内存泄漏?

image-20240325161104757

image-20240325161034693

内存泄露排查

慢慢给请求,老年代占用率一直上升

观察
  1. 修改dockerfile为jdk
  2. 使用arthas分析查看,发现当内存过高时,GC开始活动占用大量CPU

0bc927b3-147f-4b59-a742-0c133f71e440

不停慢慢压测,老年代一直上升到极限 为什么?理论上没有对象一直被引用

image-20240325161230411

image-20240325161246988

一段时间后自动下降(后来推断原因:session过期)

image-20240325161326735

再压测

image-20240325161339492

定位

输出占用高的对象 jmap -histo:live pid | head -20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 num     #instances         #bytes  class name
----------------------------------------------
1: 631231 59534632 [C
2: 1458946 58357840 java.util.LinkedHashMap$Entry
3: 127056 17386296 [Ljava.util.HashMap$Node;
4: 628938 15094512 java.lang.String
5: 11921 11836728 [B
6: 149746 8385776 java.util.LinkedHashMap
7: 145070 7148200 [Ljava.lang.Object;
8: 113038 5425824 java.util.HashMap
9: 157285 3774840 java.util.ArrayList
10: 116809 3737888 java.util.concurrent.ConcurrentHashMap$Node
11: 37082 3263216 java.lang.reflect.Method
12: 24981 2762304 java.lang.Class
13: 11159 1845112 [I
14: 10436 1682984 [Ljava.util.concurrent.ConcurrentHashMap$Node;
15: 44592 1426944 java.util.HashMap$Node
16: 57008 912128 java.lang.Object
17: 9932 874016 org.apache.catalina.session.StandardSession
  1. jmap -dump:live,format=b,file=heapdump.hprof pid dump

  2. MAT定位为org.apache.catalina.session.StandardManager

    1
    2
    3
    4
    5
    6
    One instance of "org.apache.catalina.session.StandardManager" loaded by "org.springframework.boot.loader.LaunchedURLClassLoader @ 0xe0620e30" occupies 306,798,192 (76.02%) bytes. The memory is accumulated in one instance of "java.util.concurrent.ConcurrentHashMap$Node[]" loaded by "<system class loader>".

    Keywords
    java.util.concurrent.ConcurrentHashMap$Node[]
    org.springframework.boot.loader.LaunchedURLClassLoader @ 0xe0620e30
    org.apache.catalina.session.StandardManager
  3. 查看http返回确实返回了JSESSIONID,此外本地debug打开security日志也可也看到

    1
    2
    3
    4
    5
    6
    7
    8
    logging:
    level:
    com.student.exam.mapper: debug
    org:
    springframework:
    security: DEBUG

    o.s.s.w.c.HttpSessionSecurityContextRepository 91 : SecurityContext 'org.springframework.security.core.context.SecurityContextImpl@ae3cc182: Authentication: org.springframework.security.oauth2.provider.OAuth2Authentication@ae3cc182: Principal: 51255903044; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=127.0.0.1, tokenType=BearertokenValue=<TOKEN>; Granted Authorities: ROLE_student' stored to HttpSession: 'org.apache.catalina.session.StandardSessionFacade@72a3d742
  4. 修改EXAM下 ResourceServerConfig:session策略(登录使用JWT方式,整体微服务应该都需要满足无状态的)

    1
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

参考:一次压测中tomcat生成session释放不及时导致的频繁fullgc性能优化案例 - Agoly - 博客园 (cnblogs.com)

优化后无内存持续上升问题,内存泄漏解决。

6~8k

整体能够运行,但rt较高

2b31fee5-364d-46a7-bedd-713ee2fd3e77

mysql直接打满(上次压测已经发现,gateway在每一个接口都会去根据用户id拿用户的详情信息,导致mysql打满),符合预期

image-20240325162040056

如果8k人直接系统rt爆炸

eb369f59-afb1-457f-8e47-bef45cbfa1b0

优化:将JWT在鉴权后用户id转化为用户信息的查询使用本地缓存(guava)

10k (缓存+MQ)

P95 130MS

image-20240325162434988

image-20240325162453353

exam podimage-20240325162511530

Mysql(整体几乎平稳,因为代码基本上只操作数据库,但注意用户信息是缓存了,如果缓存刚好集体失效会有一个小峰值)

image-20240325162531512

usercenter 3pod

image-20240325162655144

gateway 5pod

image-20240325162709332

结论

  • 在强行不使用session后,系统不再OOM,恢复正常
  • 对查询用户信息缓存后,mysql不再是瓶颈,整体系统并发大幅度上升
  • 如果想增加系统的并发,扩展机器即可
  • 本次压测是在本地个人机器上,使用jmeter命令行进行,可能会受到本地带宽、性能影响;标准做法应该使用专门的云服务器进行压测