You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 16:09:52