判题服务优化
2025/5/19大约 3 分钟
判题服务的优化
并发问题优化
在原来的代码中有一个小的问题会导致判题结果不一致,尤其时测试点较多时,可能会引发并发安全问题:测试点之间的原始输入输出会搞混,导致判题出现别的测试点的结果的情况
改进方法:
将图片中这个dto单独抽离出来,而不对它进行修改
这个dto里配置着判题的配置项,其中标准输入输出需要修改

改变之后的simpleJudge方法:
public JudgeResult simpleJudge(JudgeDomain judgeBefore, String input, String output) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(configuration.getURL() + "/submissions" + "?wait=true");
JudgeDomain judge = JudgeDomain.builder().build();
BeanUtils.copyProperties(judgeBefore,judge);
judge.setStdin(input);
judge.setExpected_output(output);
//其他代码
}
性能优化
原来的判题方式为发送判题请求之后通过轮询来等待判题结果,现在改为使用同步等待
改变的代码:
JudgeResult result = JSON.parseObject(body, JudgeResult.class);
if (result.getStatus() == null) {
result.setIsError(true);
} else {
if (result.getMemory() == null) {
result.setMemory(0);
}
if (result.getTime() == null) {
result.setTime(0.0);
}
if (result.getStatus().getId().equals(JudgeResultConstant.WrongAnswer)) {
System.out.println(JSON.toJSONString(result));
System.out.println(input + " --- " + output);
}
}
错误判题判断
在原来的判题逻辑中,如果提交了错误的判题语言对应的编码,或者提交了空的代码
则会出现空指针等一系列异常
现对其进行以下优化判断:
if (result.getStatus() == null) {
result.setIsError(true);
} else {
if (result.getMemory() == null) {
result.setMemory(0);
}
if (result.getTime() == null) {
result.setTime(0.0);
}
if (result.getStatus().getId().equals(JudgeResultConstant.WrongAnswer)) {
System.out.println(JSON.toJSONString(result));
System.out.println(input + " --- " + output);
}
}
然后再相关的接口方法使用for循环来检查测试数据是否有效,如果无效则返回提交id为-1,最后有Controller层报出失败反馈
修改后的代码
package com.codelong.service.impl;
import com.alibaba.fastjson.JSON;
import com.codelong.config.JudgeConfiguration;
import com.codelong.constant.CodeLanguageConstant;
import com.codelong.constant.JudgeResultConstant;
import com.codelong.pojo.domain.*;
import com.codelong.pojo.dto.JudgeDTO;
import com.codelong.pojo.dto.JudgeResult;
import com.codelong.service.*;
import com.codelong.utils.CurrentIdUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Service
@Slf4j
@RequiredArgsConstructor
public class JudgeServiceImpl implements JudgeService {
private final ProblemService problemService;
private final JudgePointService judgePointService;
private final SubPointService subPointService;
private final SubmissionService submissionService;
private final JudgeConfiguration configuration;
private final ExecutorService executor = Executors.newFixedThreadPool(20);
@Transactional
public Long judge(JudgeDTO judgeDTO) {
//获取题目信息
Problem problem = problemService.getById(judgeDTO.getProblemId());
List<JudgePoint> list = judgePointService.lambdaQuery().
eq(JudgePoint::getProblemId, judgeDTO.getProblemId()).
list();
CopyOnWriteArrayList<JudgePoint> judgePointList = new CopyOnWriteArrayList<>(list);
//设置latch便于等待多线程判题结束
CountDownLatch latch = new CountDownLatch(judgePointList.size());
//保存本次提交记录,获取id
Submission submission = new Submission();
submission.setCreateTime(LocalDateTime.now());
submission.setUserId(CurrentIdUtils.getCurrentId());
submission.setProblemId(judgeDTO.getProblemId());
submissionService.save(submission);
//设置判题限制
JudgeDomain dto = JudgeDomain.builder()
.source_code(judgeDTO.getCode())
.language_id(judgeDTO.getLanguage())
.memory_limit(problem.getMemoryLimit() * 1024)
.stack_limit(problem.getStackLimit())
.cpu_time_limit(problem.getTimeLimit() / 1000.0)
.build();
//创建判题结果队列
CopyOnWriteArrayList<JudgeResult> results = new CopyOnWriteArrayList<>();
//开始判题
try {
for (JudgePoint judgePoint : judgePointList) {
executor.execute(() -> {
try {
JudgeResult result = simpleJudge(dto, judgePoint.getInput(), judgePoint.getOutput());
//设置测试点id和测试点输入值
result.setPointId(judgePoint.getId());
result.setInput(judgePoint.getInput());
results.add(result);
latch.countDown();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
latch.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
for (JudgeResult result : results) {
if (result.getIsError()) {
return -1L;
}
}
int full = 0;
int score = 0;
int judgeTime = 0;
int judgeMemory = 0;
int judgeResult = JudgeResultConstant.Accepted;
boolean flag = true;
ArrayList<SubPoint> subPointList = new ArrayList<>();
for (JudgeResult result : results) {
//将测试点数据加入列表以便批量插入
SubPoint subPoint = SubPoint.builder()
.submissionId(submission.getId()).judgePointId(result.getPointId())
.codeLanguage(judgeDTO.getLanguage()).judgeResult(result.getStatus().getId())
.judgeMemory(result.getMemory()).judgeTime((int) (result.getTime() * 1000))
.input(result.getInput()).output(result.getStdout())
.build();
subPointList.add(subPoint);
//计算分数
full++;
if (subPoint.getJudgeResult().equals(JudgeResultConstant.Accepted)) {
score++;
}
//记录内存和时间(多个测试点的最大值)
judgeTime = Integer.max(judgeTime, subPoint.getJudgeTime());
judgeMemory = Integer.max(judgeMemory, subPoint.getJudgeMemory());
//记录判题总结果(如果有测试点没有编译通过则编译失败,通过有测试点不成功则失败)
if (flag && subPoint.getJudgeResult().equals(JudgeResultConstant.CompilationError)) {
judgeResult = JudgeResultConstant.CompilationError;
flag = false;
}
if (flag && !subPoint.getJudgeResult().equals(JudgeResultConstant.Accepted)) {
judgeResult = subPoint.getJudgeResult();
flag = false;
}
}
//完善提交记录
submission.setCodeLanguage(CodeLanguageConstant.getLanguageName(judgeDTO.getLanguage()));
submission.setJudgeScore((int) ((score * 1.0) / (full * 1.0) * 100));
submission.setCode(judgeDTO.getCode());
submission.setJudgeResult(judgeResult);
submission.setJudgeTime(judgeTime);
submission.setJudgeMemory(judgeMemory);
//保存到数据库中
submissionService.updateById(submission);
subPointService.saveBatch(subPointList);
if (full == score) {
problemService.increaseAC(problem.getId());
} else {
problemService.increase(problem.getId());
}
return submission.getId();
}
public JudgeResult simpleJudge(JudgeDomain judgeBefore, String input, String output) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(configuration.getURL() + "/submissions" + "?wait=true");
JudgeDomain judge = JudgeDomain.builder().build();
BeanUtils.copyProperties(judgeBefore,judge);
judge.setStdin(input);
judge.setExpected_output(output);
CloseableHttpResponse response;
String json = JSON.toJSONString(judge);
StringEntity entity = new StringEntity(json);
entity.setContentEncoding("utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
response = httpClient.execute(httpPost);
HttpEntity entity1 = response.getEntity();
String body = EntityUtils.toString(entity1);
JudgeResult result = JSON.parseObject(body, JudgeResult.class);
if (result.getStatus() == null) {
result.setIsError(true);
} else {
if (result.getMemory() == null) {
result.setMemory(0);
}
if (result.getTime() == null) {
result.setTime(0.0);
}
if (result.getStatus().getId().equals(JudgeResultConstant.WrongAnswer)) {
System.out.println(JSON.toJSONString(result));
System.out.println(input + " --- " + output);
}
}
response.close();
httpClient.close();
return result;
}
}