LeetCode评测系统又双叒叕超时了?这6个Java架构技巧让你秒建在线评测平台!

LeetCode评测系统又双叒叕超时了?这6个Java架构技巧让你秒建在线评测平台!

在线评测系统,看似就是跑个代码的事儿,但实际上技术难度堪比造火箭。今天就结合我3年在线教育平台开发经验,跟大家分享如何用Java从0到1搭建一个稳如老狗的LeetCode式评测系统!

一、在线评测系统到底是个啥?为啥这么复杂?

在线评测系统(Online Judge)的核心就是:用户提交代码,系统自动编译运行,对比输出结果,给出通过/失败的判定

为啥在线评测系统这么复杂?

  • 安全性要求极高:用户代码可能包含恶意攻击,必须完全隔离
  • 性能要求苛刻:要在限定时间内完成编译、运行、评测
  • 并发量巨大:成千上万用户同时提交代码
  • 资源管控严格:防止恶意代码消耗过多CPU、内存

二、Java构建在线评测系统的6个核心技巧

技巧1:代码安全执行,Docker容器隔离是王道

用户提交的代码千奇百怪,安全隔离是第一要务。

@Component
public class DockerCodeExecutor {
    
    private static final String JAVA_IMAGE = "openjdk:11-jre-slim";
    private static final int TIME_LIMIT = 5000; // 5秒超时
    private static final long MEMORY_LIMIT = 128 * 1024 * 1024; // 128MB
    
    @Autowired
    private DockerClient dockerClient;
    
    /**
     * 在Docker容器中执行Java代码
     */
    public ExecutionResult executeJavaCode(String code, String input) {
        String containerId = null;
        
        try {
            // 1. 创建临时目录和文件
            String tempDir = createTempDirectory();
            String javaFile = tempDir + "/Solution.java";
            Files.write(Paths.get(javaFile), code.getBytes());
            
            // 2. 创建安全的Docker容器
            containerId = createSecureContainer(tempDir);
            
            // 3. 启动容器并编译代码
            dockerClient.startContainerCmd(containerId).exec();
            
            ExecutionResult compileResult = compileJavaCode(containerId);
            if (!compileResult.isSuccess()) {
                return compileResult;
            }
            
            // 4. 运行Java程序
            return runJavaProgram(containerId, input);
            
        } catch (Exception e) {
            log.error("代码执行异常", e);
            return ExecutionResult.builder()
                .status("SYSTEM_ERROR")
                .error("系统错误: " + e.getMessage())
                .build();
        } finally {
            // 清理容器资源
            cleanupContainer(containerId);
        }
    }
    
    /**
     * 创建安全的Docker容器
     */
    private String createSecureContainer(String hostPath) {
        CreateContainerResponse container = dockerClient
            .createContainerCmd(JAVA_IMAGE)
            .withCmd("sleep", "30") // 容器存活30秒
            .withWorkingDir("/app")
            .withHostConfig(HostConfig.newHostConfig()
                .withBinds(new Bind(hostPath, new Volume("/app")))
                .withMemory(MEMORY_LIMIT) // 内存限制
                .withCpuQuota(50000L) // CPU限制50%
                .withNetworkMode("none") // 禁用网络
                .withReadonlyRootfs(true) // 只读文件系统
                .withAutoRemove(true) // 自动清理
            )
            .exec();
        
        return container.getId();
    }
}

技巧2:多语言支持,策略模式优雅解决

不同编程语言需要不同的处理方式,策略模式完美适配。

public interface LanguageExecutor {
    ExecutionResult execute(String code, String input, int timeLimit, long memoryLimit);
    String getLanguage();
}

@Component
public class JavaExecutor implements LanguageExecutor {
    
    @Autowired
    private DockerCodeExecutor dockerExecutor;
    
    @Override
    public ExecutionResult execute(String code, String input, int timeLimit, long memoryLimit) {
        // 代码预处理
        String processedCode = preprocessJavaCode(code);
        return dockerExecutor.executeJavaCode(processedCode, input);
    }
    
    @Override
    public String getLanguage() {
        return "java";
    }
    
    /**
     * Java代码预处理
     */
    private String preprocessJavaCode(String code) {
        // 添加必要的import
        if (!code.contains("import java.util.*;")) {
            code = "import java.util.*;\n" + code;
        }
        if (!code.contains("import java.io.*;")) {
            code = "import java.io.*;\n" + code;
        }
        return code;
    }
}

@Service
public class CodeExecutionService {
    
    private final Map<String, LanguageExecutor> executorMap;
    
    public CodeExecutionService(List<LanguageExecutor> executors) {
        this.executorMap = executors.stream()
            .collect(Collectors.toMap(
                LanguageExecutor::getLanguage,
                Function.identity()
            ));
    }
    
    /**
     * 执行代码
     */
    public ExecutionResult executeCode(SubmissionRequest request) {
        LanguageExecutor executor = executorMap.get(request.getLanguage().toLowerCase());
        if (executor == null) {
            return ExecutionResult.builder()
                .status("UNSUPPORTED_LANGUAGE")
                .error("不支持的编程语言: " + request.getLanguage())
                .build();
        }
        
        return executor.execute(
            request.getCode(),
            request.getInput(),
            request.getTimeLimit(),
            request.getMemoryLimit()
        );
    }
}

技巧3:评测核心逻辑,测试用例批量验证

评测的核心是运行测试用例,对比输出结果。

@Service
public class JudgeService {
    
    @Autowired
    private CodeExecutionService codeExecutionService;
    
    @Autowired
    private TestCaseRepository testCaseRepository;
    
    /**
     * 评测提交的代码
     */
    @Async
    public CompletableFuture<JudgeResult> judgeSubmission(Submission submission) {
        try {
            Problem problem = getProblem(submission.getProblemId());
            List<TestCase> testCases = getTestCases(problem.getId());
            
            if (testCases.isEmpty()) {
                return CompletableFuture.completedFuture(
                    createSystemErrorResult("该题目没有测试用例")
                );
            }
            
            // 执行测试用例
            JudgeResult result = executeAllTestCases(submission, testCases, problem);
            
            // 保存结果
            saveJudgeResult(submission, result, testCases.size());
            
            return CompletableFuture.completedFuture(result);
            
        } catch (Exception e) {
            log.error("评测异常: submissionId={}", submission.getId(), e);
            return CompletableFuture.completedFuture(
                createSystemErrorResult("系统错误: " + e.getMessage())
            );
        }
    }
    
    /**
     * 执行所有测试用例
     */
    private JudgeResult executeAllTestCases(Submission submission, List<TestCase> testCases, Problem problem) {
        int passedCount = 0;
        long maxExecutionTime = 0;
        long maxMemoryUsage = 0;
        
        for (int i = 0; i < testCases.size(); i++) {
            TestCase testCase = testCases.get(i);
            
            // 执行单个测试用例
            ExecutionResult execResult = executeSingleTestCase(submission, testCase, problem);
            
            // 更新统计信息
            maxExecutionTime = Math.max(maxExecutionTime, execResult.getExecutionTime());
            maxMemoryUsage = Math.max(maxMemoryUsage, execResult.getMemoryUsage());
            
            // 检查执行状态
            if (!"SUCCESS".equals(execResult.getStatus())) {
                return createFailedResult(execResult, i + 1, maxExecutionTime, maxMemoryUsage, passedCount);
            }
            
            // 比较输出结果
            if (compareOutput(execResult.getOutput(), testCase.getExpectedOutput())) {
                passedCount++;
            } else {
                return createWrongAnswerResult(execResult, testCase, i + 1, maxExecutionTime, maxMemoryUsage, passedCount);
            }
        }
        
        // 所有测试用例通过
        return JudgeResult.builder()
            .status("ACCEPTED")
            .message("所有测试用例通过")
            .executionTime(maxExecutionTime)
            .memoryUsage(maxMemoryUsage)
            .passedCount(passedCount)
            .build();
    }
    
    /**
     * 比较输出结果
     */
    private boolean compareOutput(String actualOutput, String expectedOutput) {
        if (actualOutput == null || expectedOutput == null) {
            return Objects.equals(actualOutput, expectedOutput);
        }
        
        // 去除首尾空白字符后比较
        String actual = actualOutput.trim();
        String expected = expectedOutput.trim();
        
        // 处理换行符差异
        actual = actual.replaceAll("\\r\\n", "\\n");
        expected = expected.replaceAll("\\r\\n", "\\n");
        
        return actual.equals(expected);
    }
}

技巧4:异步队列处理,避免评测阻塞

大量提交时,用消息队列异步处理,避免系统阻塞。

@RestController
@RequestMapping("/api/submissions")
public class SubmissionController {
    
    @Autowired
    private SubmissionService submissionService;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 提交代码
     */
    @PostMapping
    public ResponseEntity<SubmissionResponse> submitCode(@RequestBody SubmitRequest request) {
        try {
            // 1. 基础校验
            validateSubmission(request);
            
            // 2. 创建提交记录
            Submission submission = createSubmission(request);
            submissionService.save(submission);
            
            // 3. 发送到评测队列
            JudgeMessage message = JudgeMessage.builder()
                .submissionId(submission.getId())
                .problemId(submission.getProblemId())
                .userId(submission.getUserId())
                .language(submission.getLanguage())
                .priority(calculatePriority(request))
                .build();
            
            rabbitTemplate.convertAndSend("judge.exchange", "judge.normal", message);
            
            return ResponseEntity.ok(SubmissionResponse.builder()
                .submissionId(submission.getId())
                .status("PENDING")
                .message("代码已提交,正在评测中...")
                .build());
            
        } catch (ValidationException e) {
            return ResponseEntity.badRequest().body(
                SubmissionResponse.builder()
                    .status("INVALID")
                    .message(e.getMessage())
                    .build()
            );
        }
    }
}

@Component
public class JudgeQueueConsumer {
    
    @Autowired
    private JudgeService judgeService;
    
    /**
     * 处理普通优先级的评测任务
     */
    @RabbitListener(queues = "judge.normal.queue")
    public void handleNormalJudge(JudgeMessage message) {
        processJudgeMessage(message);
    }
    
    /**
     * 处理评测消息
     */
    private void processJudgeMessage(JudgeMessage message) {
        try {
            Submission submission = submissionService.findById(message.getSubmissionId());
            if (submission == null) {
                log.warn("提交记录不存在: {}", message.getSubmissionId());
                return;
            }
            
            // 更新状态为评测中
            submission.setStatus("JUDGING");
            submissionService.save(submission);
            
            // 执行评测
            CompletableFuture<JudgeResult> future = judgeService.judgeSubmission(submission);
            
            // 等待评测完成
            JudgeResult result = future.get(60, TimeUnit.SECONDS);
            
            log.info("评测完成: submissionId={}, status={}", 
                submission.getId(), result.getStatus());
                
        } catch (Exception e) {
            log.error("评测异常: submissionId={}", message.getSubmissionId(), e);
            updateSubmissionStatus(message.getSubmissionId(), "SYSTEM_ERROR", "系统错误");
        }
    }
}

技巧5:题目管理系统,支持多种题型

完善的题目管理是在线评测系统的基础。

@Entity
@Table(name = "problems")
public class Problem {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "title", nullable = false)
    private String title;
    
    @Column(name = "description", columnDefinition = "TEXT")
    private String description;
    
    @Column(name = "difficulty")
    @Enumerated(EnumType.STRING)
    private Difficulty difficulty; // EASY, MEDIUM, HARD
    
    @Column(name = "time_limit")
    private Integer timeLimit; // 秒
    
    @Column(name = "memory_limit")
    private Long memoryLimit; // 字节
    
    @Column(name = "sample_input", columnDefinition = "TEXT")
    private String sampleInput;
    
    @Column(name = "sample_output", columnDefinition = "TEXT")
    private String sampleOutput;
    
    @Column(name = "tags")
    private String tags; // 用逗号分隔的标签
    
    // getters and setters...
}

@Service
public class ProblemService {
    
    @Autowired
    private ProblemRepository problemRepository;
    
    @Autowired
    private TestCaseRepository testCaseRepository;
    
    /**
     * 创建题目
     */
    @Transactional
    public Problem createProblem(CreateProblemRequest request) {
        // 1. 创建题目基本信息
        Problem problem = Problem.builder()
            .title(request.getTitle())
            .description(request.getDescription())
            .difficulty(request.getDifficulty())
            .timeLimit(request.getTimeLimit())
            .memoryLimit(request.getMemoryLimit())
            .sampleInput(request.getSampleInput())
            .sampleOutput(request.getSampleOutput())
            .tags(String.join(",", request.getTags()))
            .author(request.getAuthor())
            .status(ProblemStatus.DRAFT)
            .createTime(LocalDateTime.now())
            .build();
        
        problem = problemRepository.save(problem);
        
        // 2. 创建测试用例
        if (request.getTestCases() != null && !request.getTestCases().isEmpty()) {
            createTestCases(problem.getId(), request.getTestCases());
        }
        
        return problem;
    }
    
    /**
     * 获取题目列表(支持筛选和分页)
     */
    public Page<ProblemListDTO> getProblems(ProblemQueryRequest request, Pageable pageable) {
        Specification<Problem> spec = Specification.where(null);
        
        // 按难度筛选
        if (request.getDifficulty() != null) {
            spec = spec.and((root, query, cb) -> 
                cb.equal(root.get("difficulty"), request.getDifficulty()));
        }
        
        // 按标题搜索
        if (StringUtils.isNotBlank(request.getKeyword())) {
            spec = spec.and((root, query, cb) -> 
                cb.like(root.get("title"), "%" + request.getKeyword() + "%"));
        }
        
        Page<Problem> problems = problemRepository.findAll(spec, pageable);
        return problems.map(this::convertToProblemListDTO);
    }
}

技巧6:实时状态展示,WebSocket推送结果

用WebSocket实时推送评测结果,提升用户体验。

@Component
public class JudgeResultNotifier {
    
    @Autowired
    private SimpMessagingTemplate messagingTemplate;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    /**
     * 通知评测结果
     */
    public void notifyJudgeResult(Long submissionId, JudgeResult result) {
        try {
            // 1. 构造通知消息
            JudgeResultNotification notification = JudgeResultNotification.builder()
                .submissionId(submissionId)
                .status(result.getStatus())
                .message(result.getMessage())
                .executionTime(result.getExecutionTime())
                .memoryUsage(result.getMemoryUsage())
                .passedCount(result.getPassedCount())
                .timestamp(System.currentTimeMillis())
                .build();
            
            // 2. 发送WebSocket消息给对应用户
            String userKey = "submission:" + submissionId + ":user";
            String userId = redisTemplate.opsForValue().get(userKey);
            
            if (StringUtils.isNotBlank(userId)) {
                messagingTemplate.convertAndSendToUser(
                    userId, 
                    "/queue/judge-result", 
                    notification
                );
                
                log.info("评测结果已推送: submissionId={}, userId={}, status={}", 
                    submissionId, userId, result.getStatus());
            }
            
        } catch (Exception e) {
            log.error("推送评测结果失败: submissionId={}", submissionId, e);
        }
    }
}

三、实战案例:某在线教育平台评测系统

下面分享我之前搭建的一套完整在线评测系统的关键指标。

系统架构

前端: React + Monaco Editor + WebSocket
API网关: Spring Cloud Gateway + 限流
业务服务: Spring Boot微服务集群
消息队列: RabbitMQ + Redis
评测执行: Docker容器集群
数据存储: MySQL + MongoDB + Redis

关键性能指标

  • 并发处理:1万用户同时在线,500个评测任务并发
  • 评测速度:Java代码平均2秒,Python代码3秒
  • 系统可用性:99.9%可用率,7×24小时运行
  • 资源效率:单台8核16G支持100个并发评测

四、6个避坑指南,90%的人都踩过!

1. 代码安全隔离坑

问题:用户恶意代码攻击系统
解决方案:Docker容器完全隔离,禁用网络和文件写入

2. 内存泄漏坑

问题:Docker容器没有及时清理
解决方案:设置容器自动过期,定期清理僵尸进程

3. 评测结果不准确坑

问题:输出格式差异导致误判
解决方案:智能处理空白字符,支持多格式检查

4. 高并发性能坑

问题:大量提交导致系统响应慢
解决方案:异步队列处理,优先级调度,动态扩容

5. 测试用例管理坑

问题:用例更新后历史结果不一致
解决方案:版本化管理用例,保持历史评测结果

6. 监控告警缺失坑

问题:系统异常无法及时发现
解决方案:完善监控指标,实时告警机制

五、5个核心监控指标

1. 评测成功率

  • 目标值:>99%
  • 告警阈值:<95%

2. 评测平均耗时

  • 目标值:<3秒
  • 告警阈值:>10秒

3. 系统并发能力

  • 目标值:500并发
  • 告警阈值:排队超过100

4. 容器资源使用率

  • CPU:<70%
  • 内存:<80%
  • 告警阈值:>90%

5. 队列堆积情况

  • 目标值:<50条
  • 告警阈值:>200条

六、总结:在线评测系统的4个关键点

  1. 安全第一:Docker隔离用户代码,防止恶意攻击
  2. 性能优化:异步队列处理,合理的资源调度
  3. 准确评测:智能输出比对,完善的测试用例管理
  4. 用户体验:实时状态推送,友好的错误提示

这套在线评测系统我们已经稳定运行2年,处理了100万+次代码提交,支撑了从日活1000到10万的业务增长。记住:评测系统的核心是安全和准确,宁可慢一点,也不能出现安全漏洞和错误评测!

如果你也在搭建在线评测系统,欢迎关注公众号服务端技术精选,一起交流技术实现和避坑经验!


标题:LeetCode评测系统又双叒叕超时了?这6个Java架构技巧让你秒建在线评测平台!
作者:jiangyi
地址:http://jiangyi.space/articles/2025/12/21/1766304300646.html

    0 评论
avatar