Gatling技术问题:如何在虚拟用户间共享动态认证令牌?
优化Gatling JWT令牌续期方案及排除401响应统计
问题背景
使用Java版Gatling对需Bearer Token(JWT)认证的后端执行性能测试,令牌有效期为5分钟,但测试时长超过该时间,需要实现令牌自动续期。当前方案可运行,但getAuthentication调用过于频繁,导致认证后端被不必要地高频调用——原因是虚拟用户的Session相互独立,每个用户遇到401时都会单独请求新令牌,即使其他用户刚刷新过。此外,还希望在最终性能统计中排除401响应。
已尝试给getAuthentication添加synchronized关键字但无效,考虑过定时线程每4分30秒自动刷新令牌,但认为该方案不够灵活。
优化方案:共享式令牌管理器
核心思路是实现一个线程安全的单例令牌管理器,统一管理全局令牌,仅在令牌即将过期或已过期时触发刷新,且保证多线程下只有一个线程执行刷新操作,避免并发请求认证接口。
1. 实现线程安全的TokenManager
import java.time.Instant; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TokenManager { private static TokenManager instance; private String currentToken; private Instant expirationTime; // 提前30秒刷新,避免令牌在请求过程中刚好过期 private static final int REFRESH_BUFFER_SECONDS = 30; private final Lock lock = new ReentrantLock(); // 私有构造,确保单例 private TokenManager() {} // 双重检查锁实现单例 public static TokenManager getInstance() { if (instance == null) { synchronized (TokenManager.class) { if (instance == null) { instance = new TokenManager(); } } } return instance; } // 获取有效令牌,自动判断是否需要刷新 public String getValidToken() { lock.lock(); try { // 令牌不存在、已过期,或即将在缓冲时间内过期时触发刷新 if (currentToken == null || Instant.now().plusSeconds(REFRESH_BUFFER_SECONDS).isAfter(expirationTime)) { refreshToken(); } return currentToken; } finally { lock.unlock(); } } // 强制刷新令牌(兜底用) public void refreshToken() { // 调用原有的令牌获取逻辑 String newToken = getAuthentication(); // 这里需要根据实际情况设置过期时间: // 方式1:解析JWT的exp字段获取过期时间(推荐) // 方式2:如果认证接口返回过期时间,直接取响应中的值 // 方式3:已知令牌有效期的话,手动计算(如下示例为5分钟) expirationTime = Instant.now().plusSeconds(5 * 60); currentToken = newToken; System.out.println("已刷新新令牌,过期时间:" + expirationTime); } // 原有的令牌获取方法(保持原有逻辑即可) private String getAuthentication() { // 此处保留你原有的HttpURLConnection获取令牌的代码 return "new-valid-bearer-token"; } }
2. 修改Gatling测试代码
将协议中的令牌获取逻辑改为从TokenManager获取,同时简化场景中的401处理(仅保留兜底逻辑):
HttpProtocolBuilder httpProtocol = http.baseUrl("http://my.url/v1/") // 直接从TokenManager获取有效令牌,无需从Session读取 .authorizationHeader(() -> "Bearer " + TokenManager.getInstance().getValidToken()) .check(status().in(200, 401).saveAs("status")); ScenarioBuilder scn = scenario("My scenario") .repeat(100).on( exec(http("My HTTP call").get("/my/endpoint")) .pause(1) // 兜底:如果仍出现401,强制触发一次令牌刷新 .doIf(session -> session.getInt("status") == 401).then( exec(session -> { System.out.println("检测到令牌失效,强制刷新"); TokenManager.getInstance().refreshToken(); return session; }) ) ); setUp( scn.injectClosed( rampConcurrentUsers(5).to(40).during(1200), constantConcurrentUsers(40).during(600) ) ).protocols(httpProtocol);
排除401响应统计的方法
有两种常用方式可以在Gatling统计中排除401响应:
方式1:将401标记为"成功"响应
在请求的状态检查中,将401纳入成功状态范围,这样统计时401不会被计入失败请求:
exec(http("My HTTP call").get("/my/endpoint") .check(status().in(200, 401).withMessage("忽略401状态").saveAs("status")) )
方式2:在断言中过滤401记录
如果需要保留401的失败标记,但在统计汇总中排除,可以在setUp阶段添加断言过滤:
setUp(...) .protocols(httpProtocol) .assertions( // 全局失败请求数排除401 global().failedRequests().count().filter(status().not(401)).is(0), // 单个请求的响应统计排除401 details("My HTTP call").responses().count().filter(status().not(401)).ge(100) );
内容的提问来源于stack exchange,提问作者ahuemmer
相关产品推荐
相关产品推荐

