记一次 JMeter 压测 HTTPS 性能问题

news2024/11/25 6:59:57

目录

前言:

问题背景

问题分析

切入点:垃圾回收

调整思路:为什么频繁 SSL 握手

问题验证

回归测试

源码验证


前言:

在进行性能测试时,JMeter是一个常用的工具,可以模拟大量并发用户来测试系统的负载和性能。然而,当我们在使用JMeter进行HTTPS性能测试时,可能会遇到一些性能问题。

问题背景

在使用 JMeter 压测时,发现同一后端服务,在单机 500 并发下,HTTP 和 HTTPS 协议压测 RT 差距非常大。同时观测后端服务各监控指标水位都很低,因此怀疑性能瓶颈在 JMeter 施压客户端。

问题分析

切入点:垃圾回收

首先在施压机观察到 CPU 使用率和内存使用率都很高,详细看下各线程 CPU、内存使用情况:

top -Hp {pid}

发现进程的 CPU 使用率将近打满,其中 GC 线程 CPU 使用率很高

再看下 gc 的频率和耗时,发现每秒都有 YoungGC,且累计耗时比较长,因此先从频繁 GC 入手,定位问题。

java/bin/jstat -gcutil {pid} 1000

 在压测过程中,对 JMeter 的运行进程做了 HeapDump 后,分析下堆内存:

 可以看到 cacheMap 对象占用了 93.3%的内存,而它又被 SSLSessionContextImpl 类引用,分析下源码,可以看出,每个 SSLSessionContextImpl 对象构造时,都会初始化 sessionHostPortCache 和 sessionCache 两个软引用 Cache。因为是软引用,所以在内存不足时 JVM 才会回收此类对象。

 // 默认缓存大小
    private final static int DEFAULT_MAX_CACHE_SIZE = 20480;
    // package private
    SSLSessionContextImpl() {
        cacheLimit = getDefaultCacheLimit();    // default cache size,这里默认是20480
        timeout = 86400;                        // default, 24 hours
        // use soft reference
        // 这里初始化了2个默认大小20480的缓存,是频繁GC的原因
        sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
        sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
    }
    // 获取默认缓存大小
    private static int getDefaultCacheLimit() {
        try {
            int defaultCacheLimit = GetIntegerAction.privilegedGetProperty(
                    "javax.net.ssl.sessionCacheSize", DEFAULT_MAX_CACHE_SIZE);
            if (defaultCacheLimit >= 0) {
                return defaultCacheLimit;
            } else if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
                SSLLogger.warning(
                    "invalid System Property javax.net.ssl.sessionCacheSize, " +
                    "use the default session cache size (" +
                    DEFAULT_MAX_CACHE_SIZE + ") instead");
            }
        } catch (Exception e) {
            // unlikely, log it for safe
            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
                SSLLogger.warning(
                    "the System Property javax.net.ssl.sessionCacheSize is " +
                    "not available, use the default value (" +
                    DEFAULT_MAX_CACHE_SIZE + ") instead");
            }
        }
        return DEFAULT_MAX_CACHE_SIZE;
    }

通过上述代码,发现 sessionCache 和 sessionHostPortCache 缓存默认大小是 DEFAULT_MAX_CACHE_SIZE,也就是 20480。对于我们压测的场景来说,如果每次请求重新建立连接,那么就根本不需要这块缓存。再看下代码逻辑,发现其实可以通过 javax.net.ssl.sessionCacheSize 来设置缓存的大小,在 JMeter 启动时,添加 JVM 参数-D javax.net.ssl.sessionCacheSize=1,将缓存大小设置为 1,重新压测验证,观察 GC。

 可以看出,YGC 明显变少了,从 1 秒 1 次,变成了 5-6 秒 1 次。那么观察下压测的 RT,结果。。。竟然还是 1800ms,本来 100ms 的服务被压成 1800ms,看来问题不在于 SSLSession 的缓存。再回到 GC 的耗时分析部分,仔细看下,其实 Full GC 只有 1 次,阻塞性的耗时并不多,Young GC 虽然频繁,但阻塞时间很短,也不至于将 SSL 加解密的 CPU 计算时间片全部抢占。看起来压力就是单纯的 SSL 握手次数多,造成了性能瓶颈。

调整思路:为什么频繁 SSL 握手

回到问题背景,我们是在做压力测试,单机会跑很高的并发模拟用户量,出于性能考虑,完全可以一次握手后共享 SSL 连接,后续不再握手,为什么 JMeter 会如此频繁握手呢?

带着这个问题,看了下 JMeter 官方文档,果然有惊喜!

原来 JMeter 有 2 个开关在控制是否重置 SSL 上下文的选项,首先是 https.sessioncontext.shared 控制是否全局共享同一个 SSLContext,如果设为 true,则各线程共享同一个 SSL 上下文,这样对施压机性能压力最低,但不能模拟真实多用户 SSL 握手的情况。

第二个开关 httpclient.reset_state_on_thread_group_iteration 是线程组每次循环是否重置 SSL 上下文,5.0 之后默认为true,也就是说每次循环都会重置 SSL 上下文,看来这就是导致 SSL 频繁握手的原因。

问题验证

回归测试

在 jmeter.properties 中将配置每个线程循环时,不重置 SSL 上下文,在 PTS 控制台再次启动压测,RT 直接下降 10 倍。

httpclient.reset_state_on_thread_group_iteration=false

源码验证

下面从源码层面分析下 JMeter 是怎么实现循环重置 SSL 上下文的,代码如下:

/**
     *  Whether SSL State/Context should be reset
     *  Shared state for any HC based implementation, because SSL contexts are the same 
     */
    protected static final ThreadLocal<Boolean> resetStateOnThreadGroupIteration =
            ThreadLocal.withInitial(() -> Boolean.FALSE);
    /**
     * Reset SSL State. <br/>
     * In order to do that we need to:
     * <ul>
     *  <li>Call resetContext() on SSLManager</li>
     *  <li>Close current Idle or Expired connections that hold SSL State</li>
     *  <li>Remove HttpClientContext.USER_TOKEN from {@link HttpClientContext}</li>
     * </ul>
     * @param jMeterVariables {@link JMeterVariables}
     * @param clientContext {@link HttpClientContext}
     * @param mapHttpClientPerHttpClientKey Map of {@link Pair} holding {@link CloseableHttpClient} and {@link PoolingHttpClientConnectionManager}
     */
    private void resetStateIfNeeded(JMeterVariables jMeterVariables, 
            HttpClientContext clientContext,
            Map<HttpClientKey, Pair<CloseableHttpClient, PoolingHttpClientConnectionManager>> mapHttpClientPerHttpClientKey) {
        if (resetStateOnThreadGroupIteration.get()) {
            // 关闭当前线程对应连接池的超时、空闲连接,重置连接池状态
            closeCurrentConnections(mapHttpClientPerHttpClientKey);
            // 移除Token
            clientContext.removeAttribute(HttpClientContext.USER_TOKEN);
            // 重置SSL上下文
            ((JsseSSLManager) SSLManager.getInstance()).resetContext();
            // 标记置为false,保证一次循环中,只有第一个采样器走进此逻辑
            resetStateOnThreadGroupIteration.set(false);
        }
    }
    @Override
    protected void notifyFirstSampleAfterLoopRestart() {
        log.debug("notifyFirstSampleAfterLoopRestart called "
                + "with config(httpclient.reset_state_on_thread_group_iteration={})",
                RESET_STATE_ON_THREAD_GROUP_ITERATION);
        resetStateOnThreadGroupIteration.set(RESET_STATE_ON_THREAD_GROUP_ITERATION);
    }

在每次基于 Apache HTTPClient4 的 HTTP 采样器执行时,都会调用 resetStateIfNeeded 方法,在进入方法时读取 httpclient.reset_state_on_thread_group_iteration 配置,即 resetStateOnThreadGroupIteration。如果是 true,重置当前线程的连接池状态、重置 SSL 上下文,然后再将 resetStateOnThreadGroupIteration 置为 false。

因为 JMeter 的并发是基于线程实现的,resetStateOnThreadGroupIteration 这个开关放在 ThreadLocal 里,在每次循环开始时,会调用 notifyFirstSampleAfterLoopRestart 方法,重置开关,运行一次后,强制把开关置为 false。这保证了每次循环只有第一个采样器进入此逻辑,也就是每次循环只执行一次。

 作为一位过来人也是希望大家少走一些弯路,希望能对你带来帮助。(WEB自动化测试、app自动化测试、接口自动化测试、持续集成、自动化测试开发、大厂面试真题、简历模板等等),相信能使你更好的进步!

 留【自动化测试】即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/701704.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT学习笔记3--信号和槽

作用 信号槽是用来连接信号的发出端和接收端&#xff0c;其本身是没有关联的。利用connect函数将两个端耦合在一起。 函数格式 connect(信号的发送者&#xff0c;发送的具体信号&#xff0c;信号的接受者&#xff0c;信号的处理&#xff08;槽&#xff09;) 实例 利用按键关…

MultiTimer 软件定时器扩展模块的研究与优化【原创】

文章目录 前言收获main.cMultiTimer.cMultiTimer.h 前言 博主花费几天时间研究了此开源软件定时器扩展模块代码&#xff0c;并参考别的博主讲解解决了定时器计数值会溢出的问题&#xff08;很多博主写的文章并没有谈到这个计数溢出问题&#xff0c;我就想问一下看代码自己都不思…

MathType公式批量转换为Word自带公式的VBA实现及error ‘4198‘ 处理

VBA 纯新手&#xff0c;程序可能比较傻&#xff0c;大家将就看。有改进建议&#xff0c;欢迎留言 参考链接&#xff1a; 1、2种办法把MathType公式转换为Word公式 2、一键批量将mathtype公式转换成word自带公式-vba/vbnet_mathtype批量转word自带_一路向前的小Q的博客-CSDN博…

如何保证缓存与数据库的双写一致性?

分析&#xff1f; 你只要用缓存&#xff0c;就可能会涉及到缓存与数据库双存储双写&#xff0c;你只要是双写&#xff0c;就一定会有数据一 致性的问题&#xff0c;那么你如何解决一致性问题&#xff1f; 一般来说&#xff0c;如果允许缓存可以稍微的跟数据库偶尔有不一致的情…

合同数智化如何助力地产企业实现变革“突围”?

从稳步发展到求新求变&#xff0c; 数智化成破局关键 近年来&#xff0c;随着宏观经济政策调整&#xff0c;在中央房住不炒的大基调下&#xff0c;房地产逐步回归居住属性。在这样的大背景下&#xff0c;针对不同类型的房地产企业&#xff0c;国家出台了不同的数字化转型指导文…

【Bio】头骨组成,及其剖切面

在总结这篇文章之前&#xff0c;我看过了一本书《认知觉醒》&#xff0c;里边有提到一个观点&#xff1a;我们不仅要去获取新知识&#xff0c;也要注重对新知识的“缝接”&#xff0c;这个过程也就是关联。这样&#xff0c;如沙粒般的新知识才能关联到一起&#xff0c;达到聚沙…

go get google.golang.org/grpc报错

win10环境&#xff0c;报错完整内容如下 go get google.golang.org/grpc: module google.golang.org/grpc: Get https://proxy.golang.org/google.golang.org/grpc/v/list: dial tcp [2404:6800:4012:3::2011]:443: connectex: A connection attempt failed because the conne…

Spring 的依赖注入(DI)

前言 欢迎来到本篇文章&#xff0c;书接上回&#xff0c;本篇说说 Spring 中的依赖注入&#xff0c;包括注入的方式&#xff0c;写法&#xff0c;该选择哪个注入方式以及可能出现的循环依赖问题等内容。 如果正在阅读的朋友还不清楚什么是「依赖」&#xff0c;建议先看看我第一…

34岁上岸,我终于圆了自己的考研梦

​ 大家好&#xff0c;我是独孤风&#xff0c;一位曾经的港口煤炭工人&#xff0c;目前在某国企任大数据负责人&#xff0c;公众号大数据流动的作者。 ​ 虽然告诉自己要平静&#xff0c;但是当接到EMS录取通知书的那一刻&#xff0c;眼眶还是忍不住有些湿润。今年正好是是东北…

SpringBoot源码分析(1)--@SpringBootApplication注解使用和原理/SpringBoot的自动配置原理详解

文章目录 前言主启动类的配置1、SpringBootApplication注解1.1、SpringBootConfiguration注解验证启动类是否被注入到spring容器中 1.2、ComponentScan 注解ComponentScan 注解解析与路径扫描 1.3、EnableAutoConfiguration注解1.3.1、AutoConfigurationPackage注解1.3.2、Impo…

【MySQL】事务及其隔离性/隔离级别

目录 一、事务的概念 1、事务的四种特性 2、事务的作用 3、存储引擎对事务的支持 4、事务的提交方式 二、事务的启动、回滚与提交 1、准备工作&#xff1a;调整MySQL的默认隔离级别为最低/创建测试表 2、事务的启动、回滚与提交 3、启动事务后未commit&#xff0c;但是…

HTB-Pilgrimage

HTB-Pilgrimage 信息收集80端口立足emily -> root 信息收集 80端口 扫描目录发现存在.git。 通过scrabble获取网站的git文件。 有如下这些文件。 在index.php中使用了magick来处理图像。 正好我们靠git弄了一个&#xff0c;查看一下版本。 这个版本似乎有些不得了的东西…

Quiz 9: Dictionaries | Python for Everybody 配套练习_解题记录

文章目录 课程简介Quiz 9: Dictionaries 单选题&#xff08;1-11&#xff09;编程题Exercise 9.4 课程简介 Python for Everybody 零基础程序设计&#xff08;Python 入门&#xff09; This course aims to teach everyone the basics of programming computers using Python.…

conda的多线程下载工具mamba(解决Anaconda3 solving environment 巨慢的方法)

solving environment为什么会越来越慢&#xff1f; 根据原博的解释以及我查阅的相关资料&#xff0c;这是由于conda在新安装一个包或者更新包时需要搜索当前环境中所有的包的依赖空间&#xff0c;以找到满足所有依赖项的版本&#xff0c;随着用户安装的包越来越多&#xff0c;…

C#核心知识回顾——1.结构体、构造函数、GC、成员属性、索引器

1.结构体&#xff1a; 在 C# 中&#xff0c;结构体是值类型数据结构。它使得一个单一变量可以存储各种数据类型的相关数据。例如我定义了一个结构体&#xff0c;它有两个变量&#xff0c;创建一个这个类型的结构体&#xff0c;通过一个变量名调用多个变量&#xff0c;这些变量可…

Layui时间范围选择器,添加【本周、本月、本季度、本年等常用时间快捷键】

文章目录 1. 界面实现2. JS具体实现2.1 第一种实现2.2 第二种实现 1. 界面实现 <input id"Date_select" type"text" class"form-control" placeholder"请选择时间范围" style"border-radius: 4px;" /><input id&qu…

RuoYi-Vue Swagger 上传文件接口

前言 RuoYi-Vue&#xff1a; v3.8.5swagger 1.6.2 &#xff08;https://github.com/swagger-api/swagger-core, https://gitee.com/mirrors/swagger-core&#xff09; Swagger 上传接口定义 ApiOperation(value "图片上传") PostMapping(value "/upload&qu…

SpringBoo集成MongoDB

一、集成简介 spring-data-mongodb提供了MongoTemplate与MongoRepository两种方式访问mongodb&#xff0c;MongoRepository操作简单&#xff0c;MongoTemplate操作灵活&#xff0c;我们在项目中可以灵活适用这两种方式操作mongodb&#xff0c;MongoRepository的缺点是不够灵活…

OpenMMLab-AI实战营第二期——相关3. RGB语义分割标注图像转为Gray格式的mask

文章目录 1. 转换代码1.1 查看原始图像1.2 转换1.3 cv::IMREAD_GRAYSCALE与CV_BGR2GRAY结果不一致1.3.1 现象描述1.3.2 原因1.3.3 推荐做法 1.4 CV_BGR2GRAY和CV_RGB2GRAY不一致 2. macOS上查看mask&#xff08;使用默认的预览&#xff09; 1. 转换代码 找到了一个语义分割的数…

rc表格卡方检验

一、案例介绍 某医院用三种穴位针刺治疗急性腰扭伤&#xff0c;现在想比较三种穴位针刺效果有无差别&#xff0c;结果汇总如下表&#xff1a; 二、问题分析 本案例想比较三种穴位针刺效果有无差别&#xff0c;可以使用RxC卡方检验进行分析。 通常情况下&#xff0c;共有三种…