refactor(测试计划): 优化同一个测试计划并发执行偶发Block Waiting情况

Signed-off-by: fit2-zhao <yong.zhao@fit2cloud.com>
This commit is contained in:
fit2-zhao 2023-05-08 14:00:09 +08:00 committed by fit2-zhao
parent 9628430583
commit d315868c0c
8 changed files with 116 additions and 33 deletions

View File

@ -26,6 +26,7 @@ import io.metersphere.dto.RunModeConfigDTO;
import io.metersphere.environment.service.BaseEnvGroupProjectService;
import io.metersphere.environment.service.BaseEnvironmentService;
import io.metersphere.service.ApiExecutionQueueService;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.service.ServiceUtils;
import io.metersphere.service.definition.ApiCaseResultService;
import io.metersphere.service.scenario.ApiScenarioReportStructureService;
@ -68,7 +69,8 @@ public class ApiCaseExecuteService {
private BaseEnvironmentService baseEnvironmentService;
@Resource
private ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
@Resource
private RedisTemplateService redisTemplateService;
/**
* 测试计划case执行
*
@ -131,6 +133,9 @@ public class ApiCaseExecuteService {
ApiDefinitionExecResultWithBLOBs report = ApiDefinitionExecResultUtil.addResult(request, runModeConfigDTO, testPlanApiCase, status, testCase, resourcePoolId);
executeQueue.put(testPlanApiCase.getId(), report);
responseDTOS.add(new MsExecResponseDTO(testPlanApiCase.getId(), report.getId(), request.getTriggerMode()));
// 执行中资源锁住防止重复更新造成LOCK WAIT
redisTemplateService.lock(testPlanApiCase.getId());
LoggerUtil.info("预生成测试用例结果报告:" + report.getName(), report.getId());
}
apiCaseResultService.batchSave(executeQueue);

View File

@ -35,6 +35,7 @@ import io.metersphere.environment.service.BaseEnvGroupProjectService;
import io.metersphere.i18n.Translator;
import io.metersphere.plugin.core.MsTestElement;
import io.metersphere.service.ApiExecutionQueueService;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.service.ServiceUtils;
import io.metersphere.service.SystemParameterService;
import io.metersphere.service.definition.TcpApiParamService;
@ -91,6 +92,8 @@ public class ApiScenarioExecuteService {
private ExtTestPlanScenarioCaseMapper extTestPlanScenarioCaseMapper;
@Resource
private SystemParameterService systemParameterService;
@Resource
private RedisTemplateService redisTemplateService;
public List<MsExecResponseDTO> run(RunScenarioRequest request) {
if (LoggerUtil.getLogger().isDebugEnabled()) {
@ -343,6 +346,8 @@ public class ApiScenarioExecuteService {
if (!StringUtils.equals(request.getConfig().getReportType(), RunModeConstants.SET_REPORT.toString())) {
apiScenarioReportStructureService.save(scenario, report.getId(), request.getConfig() != null ? request.getConfig().getReportType() : null);
}
// 执行中资源锁住防止重复更新造成LOCK WAIT
redisTemplateService.lock(planApiScenario.getId());
// 重置报告ID
reportId = UUID.randomUUID().toString();
}

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.enums;
public enum LockEnum {
LOCK, WAITING
}

View File

@ -1,13 +1,21 @@
package io.metersphere.service;
import io.metersphere.api.jmeter.utils.JmxFileUtil;
import io.metersphere.commons.enums.LockEnum;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.utils.LoggerUtil;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisTemplateService {
private static final String PRX = "TEST_PLAN_";
public static final long TIME_OUT = 60;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@ -46,4 +54,33 @@ public class RedisTemplateService {
public void delFilePath(String reportId) {
delete(JmxFileUtil.getExecuteFileKeyInRedis(reportId));
}
/**
* 加锁
*/
public boolean lock(String key) {
return redisTemplate.opsForValue().setIfAbsent(StringUtils.join(PRX, key), LockEnum.LOCK.name());
}
public boolean has(String key) {
try {
Object value = redisTemplate.opsForValue().get(StringUtils.join(PRX, key));
if (ObjectUtils.isNotEmpty(value)) {
if (StringUtils.equals(LockEnum.LOCK.name(), String.valueOf(value))) {
// 设置一分钟超时
redisTemplate.opsForValue().setIfPresent(StringUtils.join(PRX, key),
LockEnum.WAITING.name(), TIME_OUT, TimeUnit.SECONDS);
return false;
}
} else {
redisTemplate.opsForValue().setIfAbsent(StringUtils.join(PRX, key),
LockEnum.WAITING.name(), TIME_OUT, TimeUnit.SECONDS);
return false;
}
return true;
} catch (Exception e) {
LogUtil.error(e);
}
return false;
}
}

View File

@ -2,18 +2,25 @@ package io.metersphere.service;
import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil;
import io.metersphere.api.jmeter.ApiLocalRunner;
import io.metersphere.api.jmeter.utils.JmxFileUtil;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.FixedCapacityUtil;
import io.metersphere.dto.JmeterRunRequestDTO;
import io.metersphere.dto.ResultDTO;
import io.metersphere.utils.LoggerUtil;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@Service
public class RemakeReportService {
@Resource
@Lazy
private ApiExecutionQueueService queueService;
@Resource
@Lazy
private TestResultService testResultService;
public void queueNext(JmeterRunRequestDTO request, String errorMsg) {
try {
ResultDTO dto = new ResultDTO();
@ -25,11 +32,12 @@ public class RemakeReportService {
PoolExecBlockingQueueUtil.offer(dto.getReportId());
LoggerUtil.error("执行异常处理:" + errorMsg, request.getReportId());
if (StringUtils.isNotEmpty(dto.getQueueId())) {
CommonBeanFactory.getBean(ApiExecutionQueueService.class).queueNext(dto);
queueService.queueNext(dto);
}
// 更新测试计划报告
LoggerUtil.info("Check Processing Test Plan report status.queueId" + dto.getQueueId() + "runMode:" + dto.getRunMode() + "testId:" + dto.getTestId(), dto.getReportId());
CommonBeanFactory.getBean(ApiExecutionQueueService.class).checkTestPlanCaseTestEnd(dto.getTestId(), dto.getRunMode(), dto.getTestPlanReportId());
LoggerUtil.info("Check Processing Test Plan report status.queueId"
+ dto.getQueueId() + "runMode:" + dto.getRunMode() + "testId:" + dto.getTestId(), dto.getReportId());
queueService.checkTestPlanCaseTestEnd(dto.getTestId(), dto.getRunMode(), dto.getTestPlanReportId());
} catch (Exception e) {
ApiLocalRunner.clearCache(request.getReportId());
LoggerUtil.error("回退报告异常", request.getReportId(), e);
@ -43,7 +51,7 @@ public class RemakeReportService {
dto.setTestId(request.getTestId());
String consoleMsg = FixedCapacityUtil.getJmeterLogger(dto.getReportId(), true);
dto.setConsole(consoleMsg + StringUtils.LF + errorMsg);
CommonBeanFactory.getBean(TestResultService.class).testEnded(dto);
testResultService.testEnded(dto);
}
public void testEnded(JmeterRunRequestDTO request, String errorMsg) {

View File

@ -196,7 +196,9 @@ public class TestResultService {
}
if (StringUtils.equals(dto.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) {
return apiScenarioReportService.updatePlanCase(dto);
} else if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) {
} else if (StringUtils.equalsAny(dto.getRunMode(),
ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(),
ApiRunMode.JENKINS_SCENARIO_PLAN.name())) {
return apiScenarioReportService.updateSchedulePlanCase(dto);
} else {
return this.editReport(dto);

View File

@ -20,6 +20,7 @@ import io.metersphere.dto.RequestResult;
import io.metersphere.dto.ResultDTO;
import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.service.ServiceUtils;
import io.metersphere.utils.LoggerUtil;
import jakarta.annotation.Resource;
@ -66,6 +67,8 @@ public class ApiDefinitionExecResultService {
private ExtApiTestCaseMapper extApiTestCaseMapper;
@Resource
private ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
@Resource
private RedisTemplateService redisTemplateService;
/**
* API/CASE 重试结果保留一条
@ -100,7 +103,7 @@ public class ApiDefinitionExecResultService {
User user = getUser(dto, result);
//如果是测试计划用例更新接口用例的上次执行结果
TestPlanApiCase testPlanApiCase = testPlanApiCaseMapper.selectByPrimaryKey(dto.getTestId());
if (testPlanApiCase != null) {
if (testPlanApiCase != null && !redisTemplateService.has(dto.getTestId())) {
ApiTestCaseWithBLOBs apiTestCase = apiTestCaseMapper.selectByPrimaryKey(testPlanApiCase.getApiCaseId());
if (apiTestCase != null) {
apiTestCase.setLastResultId(dto.getReportId());
@ -219,21 +222,27 @@ public class ApiDefinitionExecResultService {
}
public void setExecResult(String id, String status, Long time) {
TestPlanApiCase apiCase = new TestPlanApiCase();
apiCase.setId(id);
apiCase.setStatus(status);
apiCase.setUpdateTime(time);
testPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase);
if (!redisTemplateService.has(id)) {
TestPlanApiCase apiCase = new TestPlanApiCase();
apiCase.setId(id);
apiCase.setStatus(status);
apiCase.setUpdateTime(time);
testPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase);
}
}
public void editStatus(ApiDefinitionExecResult saveResult, String type, String status, Long time, String reportId, String testId) {
String name = testId;
String version = StringUtils.EMPTY;
String projectId = StringUtils.EMPTY;
if (StringUtils.equalsAnyIgnoreCase(type, ApiRunMode.API_PLAN.name(), ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name(), ApiRunMode.MANUAL_PLAN.name())) {
if (StringUtils.equalsAnyIgnoreCase(type,
ApiRunMode.API_PLAN.name(),
ApiRunMode.SCHEDULE_API_PLAN.name(),
ApiRunMode.JENKINS_API_PLAN.name(),
ApiRunMode.MANUAL_PLAN.name())) {
TestPlanApiCase testPlanApiCase = testPlanApiCaseMapper.selectByPrimaryKey(testId);
ApiTestCaseWithBLOBs caseWithBLOBs = null;
if (testPlanApiCase != null) {
if (testPlanApiCase != null && !redisTemplateService.has(testId)) {
this.setExecResult(testId, status, time);
caseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(testPlanApiCase.getApiCaseId());
testPlanApiCase.setStatus(status);
@ -255,7 +264,7 @@ public class ApiDefinitionExecResultService {
projectId = apiDefinition.getProjectId();
} else {
ApiTestCaseWithBLOBs caseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(testId);
if (caseWithBLOBs != null) {
if (caseWithBLOBs != null && !redisTemplateService.has(testId)) {
// 更新用例最后执行结果
caseWithBLOBs.setLastResultId(reportId);
caseWithBLOBs.setStatus(status);
@ -281,18 +290,23 @@ public class ApiDefinitionExecResultService {
public void batchEditStatus(String type, String status, String reportId, String testId,
TestPlanApiCaseMapper batchTestPlanApiCaseMapper,
ApiTestCaseMapper batchApiTestCaseMapper) {
if (StringUtils.equalsAnyIgnoreCase(type, ApiRunMode.API_PLAN.name(), ApiRunMode.SCHEDULE_API_PLAN.name(),
ApiRunMode.JENKINS_API_PLAN.name(), ApiRunMode.MANUAL_PLAN.name())) {
TestPlanApiCase apiCase = new TestPlanApiCase();
apiCase.setId(testId);
apiCase.setStatus(status);
apiCase.setUpdateTime(System.currentTimeMillis());
batchTestPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase);
if (StringUtils.equalsAnyIgnoreCase(type,
ApiRunMode.API_PLAN.name(),
ApiRunMode.SCHEDULE_API_PLAN.name(),
ApiRunMode.JENKINS_API_PLAN.name(),
ApiRunMode.MANUAL_PLAN.name())) {
if (!redisTemplateService.has(testId)) {
TestPlanApiCase apiCase = new TestPlanApiCase();
apiCase.setId(testId);
apiCase.setStatus(status);
apiCase.setUpdateTime(System.currentTimeMillis());
batchTestPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase);
TestCaseReviewApiCase reviewApiCase = new TestCaseReviewApiCase();
reviewApiCase.setId(testId);
reviewApiCase.setStatus(status);
reviewApiCase.setUpdateTime(System.currentTimeMillis());
TestCaseReviewApiCase reviewApiCase = new TestCaseReviewApiCase();
reviewApiCase.setId(testId);
reviewApiCase.setStatus(status);
reviewApiCase.setUpdateTime(System.currentTimeMillis());
}
} else {
// 更新用例最后执行结果
ApiTestCaseWithBLOBs caseWithBLOBs = new ApiTestCaseWithBLOBs();
@ -324,7 +338,7 @@ public class ApiDefinitionExecResultService {
}
if (StringUtils.equalsAny(dto.getRunMode(), ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name())) {
TestPlanApiCase apiCase = testPlanApiCaseMapper.selectByPrimaryKey(dto.getTestId());
if (apiCase != null) {
if (apiCase != null && !redisTemplateService.has(dto.getTestId())) {
String projectId = extTestPlanApiCaseMapper.selectProjectId(apiCase.getId());
ApiDefinition apiDefinition = extApiTestCaseMapper.selectApiBasicInfoByCaseId(apiCase.getId());
String version = apiDefinition == null ? "" : apiDefinition.getVersionId();

View File

@ -7,6 +7,7 @@ import io.metersphere.api.dto.automation.ExecuteType;
import io.metersphere.api.dto.automation.RunScenarioRequest;
import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.service.*;
import io.metersphere.utils.ReportStatusUtil;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
@ -30,10 +31,6 @@ import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.api.ModuleReference;
import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.service.BaseShareInfoService;
import io.metersphere.service.BaseUserService;
import io.metersphere.service.ServiceUtils;
import io.metersphere.service.SystemParameterService;
import jakarta.annotation.Resource;
import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.collections4.CollectionUtils;
@ -89,6 +86,8 @@ public class ApiScenarioReportService {
private TestPlanApiScenarioMapper testPlanApiScenarioMapper;
@Resource
BaseShareInfoService baseShareInfoService;
@Resource
private RedisTemplateService redisTemplateService;
public void saveResult(ResultDTO dto) {
// 报告详情内容
@ -305,6 +304,10 @@ public class ApiScenarioReportService {
public ApiScenarioReport updatePlanCase(ResultDTO dto) {
ResultVO resultVO = ReportStatusUtil.computedProcess(dto);
ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), resultVO.getStatus(), dto.getRunMode());
// 当前资源正在执行中
if (redisTemplateService.has(dto.getTestId())) {
return report;
}
TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(dto.getTestId());
if (testPlanApiScenario != null) {
if (report != null) {
@ -341,6 +344,10 @@ public class ApiScenarioReportService {
ResultVO resultVO = ReportStatusUtil.computedProcess(dto);
ApiScenarioReport report = editReport(dto.getReportType(), dto.getReportId(), resultVO.getStatus(), dto.getRunMode());
// 当前资源正在执行中
if (redisTemplateService.has(dto.getTestId())) {
return report;
}
if (report != null) {
if (StringUtils.isNotEmpty(dto.getTestPlanReportId()) && !testPlanReportIdList.contains(dto.getTestPlanReportId())) {
testPlanReportIdList.add(dto.getTestPlanReportId());