如何让Java Gatling虚拟用户请求使用动态递增的modelVersion?
解决Gatling多用户场景下ModelVersion递增冲突问题
问题背景
需要实现Gatling仿真脚本满足以下要求:
- 每个POST请求使用递增1的唯一ModelVersion,避免409冲突
- 初始版本号从GET接口获取的数据库现有值加1
- POST响应返回的ModelVersion需作为后续请求的依据
但现有两种尝试均存在问题:
- 第一种脚本:多用户共享Session中的ModelVersion,导致多个POST请求使用相同版本号
- 第二种原子变量脚本:多用户并发执行时,全局原子变量被重复初始化,且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
相关产品推荐
相关产品推荐

