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

如何让Java Gatling虚拟用户请求使用动态递增的modelVersion?

解决Gatling多用户场景下ModelVersion递增冲突问题

问题背景

需要实现Gatling仿真脚本满足以下要求:

  • 每个POST请求使用递增1的唯一ModelVersion,避免409冲突
  • 初始版本号从GET接口获取的数据库现有值加1
  • POST响应返回的ModelVersion需作为后续请求的依据

但现有两种尝试均存在问题:

  1. 第一种脚本:多用户共享Session中的ModelVersion,导致多个POST请求使用相同版本号
  2. 第二种原子变量脚本:多用户并发执行时,全局原子变量被重复初始化,且POST请求执行顺序混乱,出现版本号顺序颠倒的情况

核心问题分析

  • Gatling中每个用户拥有独立Session,全局原子变量会被多用户并发修改,产生竞态条件
  • 多用户同时执行GET请求,都会去设置全局原子变量,导致初始值被最后执行的用户覆盖
  • 原子变量的get()和incrementAndGet()是两个独立原子操作,并发下会出现版本号获取和递增的顺序混乱

解决方案

通过初始化流程单例执行+原子性版本号获取+服务器状态同步来解决问题,完整代码如下:

import io.gatling.javaapi.core.ChainBuilder;
import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;
import io.gatling.javaapi.http.HttpProtocolBuilder;

import java.util.concurrent.atomic.AtomicInteger;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.http;
import static io.gatling.javaapi.http.HttpDsl.status;

public class MySimulation extends Simulation {
    private static final String BASE_URL = "myurl";
    private static final String API_PATH = "/notifications/00000000-0000-6000-7000-000000000000/status";

    // 全局原子变量:存储当前最新可用的ModelVersion
    private static final AtomicInteger currentModelVersion = new AtomicInteger(0);
    // 初始化完成标记:确保只有第一个用户执行GET获取初始值
    private static volatile boolean isInitialized = false;

    private final HttpProtocolBuilder httpProtocol = http
            .disableFollowRedirect()
            .baseUrl(BASE_URL)
            .acceptHeader("application/json");

    // 初始化流程:仅第一个到达的用户执行GET获取初始版本号
    private final ChainBuilder initModelVersion = onceOnly(
            exec(http("GetInitialStatus")
                    .get(API_PATH)
                    .check(status().is(200))
                    .check(jsonPath("$[0].modelVersion").optional().saveAs("initialVersion"))
            ).exec(session -> {
                String initialVersionStr = session.getString("initialVersion");
                int initialVersion = (initialVersionStr == null || initialVersionStr.isEmpty()) ? 0 : Integer.parseInt(initialVersionStr);
                // 设置初始可用版本为现有值+1(第一个POST直接使用该值)
                currentModelVersion.set(initialVersion + 1);
                isInitialized = true;
                return session;
            })
    );

    // 等待初始化完成:后续用户需等待第一个用户完成初始值加载
    private final ChainBuilder waitForInit = exec(session -> {
        while (!isInitialized) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return session;
    });

    private final ChainBuilder postStatus = exec(session -> {
        // 原子性获取当前版本号并递增,确保每个用户拿到唯一值
        int versionToUse = currentModelVersion.getAndIncrement();
        return session.set("modelVersion", String.valueOf(versionToUse));
    }).exec(http("PostStatus")
            .post(API_PATH)
            .header("Content-Type", "application/json")
            .header("Accept", "application/json")
            .body(StringBody(session -> """
                    {
                       "comment":"",
                       "fiFlaggedStatus":"FLAGGED",
                       "investigationAssessment":"WORTHY",
                       "investigationStatus":"COMPLETED",
                       "panRiskScore":986,
                       "modelVersion":"%s",
                       "id":""
                    }
                    """.formatted(session.getString("modelVersion"))))
            .check(status().is(200))
            .check(jsonPath("$.modelVersion").saveAs("responseVersion"))
            .check(jsonPath("$.id").notNull())
    ).exec(session -> {
        // 用服务器返回的版本号更新全局变量,与服务器状态同步
        int responseVersion = Integer.parseInt(session.getString("responseVersion"));
        currentModelVersion.set(responseVersion + 1);
        return session;
    });

    private final ScenarioBuilder mainScn = scenario("MainScenario")
            .exec(initModelVersion)
            .exec(waitForInit)
            .exec(postStatus);

    {
        setUp(mainScn.injectOpen(atOnceUsers(2))).protocols(httpProtocol);
    }
}

关键代码说明

  • onceOnly:确保初始化流程仅由第一个到达的用户执行,避免多用户重复获取初始值
  • waitForInit:让后续用户等待初始化完成,保证所有用户基于正确的初始版本号发起请求
  • getAndIncrement():原子性地获取当前版本号并递增,彻底避免并发下的竞态问题,每个用户拿到唯一的版本号
  • POST响应后更新全局变量:确保后续请求使用服务器返回的最新版本号,与服务器状态完全同步

内容的提问来源于stack exchange,提问作者Francislainy Campos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 09:32:00