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

Spring Boot中调用Python评分脚本如何实现并发支持

Spring Boot 调用自定义Python评分脚本并发改造方案

核心问题定位

当前并发缺陷的本质是所有评分进程共用全局固定路径的TEST_GRADING_REPORT结果文件,多请求并行时会出现文件覆盖、读写竞争,直接导致批改结果串号、文件读取异常。

落地改造方案

1. 单任务独立工作目录隔离(零侵入兼容存量脚本,最快上线)

这是改造成本最低的方案,完全不需要教师调整已有评分脚本写法:

  • 每次调用gradeHomework()时,基于请求UUID/作业ID生成全局唯一的临时工作目录,路径规则参考./grading-workspace/{taskUuid}/
  • 启动Python进程前,通过ProcessBuilder.directory()方法将进程的工作目录设置为该专属临时目录。原有脚本写相对路径TEST_GRADING_REPORT的逻辑不需要任何修改,文件会自动落到专属目录下,彻底避免文件读写竞争。该方案对教师侧完全透明,相对路径基于进程工作目录解析,用户完全感知不到底层调整
  • 进程执行完成、结果反序列化成功后,立刻递归删除整个临时工作目录,避免磁盘空间泄漏
  • 核心代码参考:
// 生成全局唯一任务ID
String taskId = UUID.randomUUID().toString();
Path workDir = Files.createDirectories(Paths.get("./grading-workspace/" + taskId));
// 按需将评分脚本、学生作业文件拷贝到当前工作目录
Files.copy(teacherScriptPath, workDir.resolve("grade.py"), StandardCopyOption.REPLACE_EXISTING);
Files.copy(studentHomeworkPath, workDir.resolve("student_submit.py"), StandardCopyOption.REPLACE_EXISTING);
// 构建进程时指定专属工作目录
ProcessBuilder pb = new ProcessBuilder("python3", "grade.py", "student_submit.py");
pb.directory(workDir.toFile());
// 合并错误流方便排查问题
pb.redirectErrorStream(true);
Process process = pb.start();
// 设置执行超时,防止死循环脚本占满资源
boolean execSuccess = process.waitFor(3, TimeUnit.SECONDS);
if (!execSuccess) {
    process.destroyForcibly();
    // 清理临时目录
    FileUtils.deleteDirectory(workDir.toFile());
    throw new RuntimeException("评分脚本执行超时");
}
// 读取当前任务专属目录下的结果文件,无并发冲突
Path reportPath = workDir.resolve("TEST_GRADING_REPORT");
Grade grade = new ObjectMapper().readValue(reportPath.toFile(), Grade.class);
// 执行完成清理临时文件
FileUtils.deleteDirectory(workDir.toFile());

2. 进程资源池管控(避免无限制创建进程拖垮服务)

单任务50ms耗时虽短,但无限制创建进程会快速耗尽CPU、内存资源,必须做并发管控:

  • 自定义专用线程池执行评分任务,核心线程数设置为CPU核心数*2即可(Python单进程受GIL限制,CPU密集场景下过多线程不会提升效率),队列设置合理长度阈值,超出阈值直接返回系统繁忙提示,避免雪崩
  • 禁止直接在Tomcat请求线程内阻塞等待进程执行,防止占满Web容器线程导致全服务不可用
  • 所有进程生命周期必须和请求绑定,请求中断、超时场景下要主动调用destroyForcibly()销毁对应进程,避免孤儿进程残留

3. 进阶性能优化:移除中间文件,通过标准输入输出传参

如果可以统一约束教师评分脚本的写法,可以彻底去掉磁盘文件读写开销,进一步降低单任务耗时:

  • 要求Python脚本从标准输入(stdin)读取作业参数、学生提交内容,评分完成后将结果JSON直接打印到标准输出(stdout),不需要写任何本地文件
  • Java端直接通过Process.getInputStream()读取进程输出流,拿到JSON后直接反序列化,连临时目录都不需要创建,彻底消除文件IO开销和文件竞争风险
  • 适配的Python脚本示例:
import sys, json
# 从标准输入读取传入的作业参数
submit_info = json.loads(sys.stdin.read())
# 执行原有评分逻辑
final_score = 95
grade_detail = "边界用例未通过,扣5分"
# 结果直接输出到标准输出
print(json.dumps({"score": final_score, "detail": grade_detail}))
  • 注意事项:必须单独开线程消费进程的stdout、stderr流,避免缓冲区满导致进程阻塞挂死

4. 必做安全加固

由于是执行教师上传的任意Python脚本,必须做基础隔离防止恶意脚本破坏系统:

  • 所有评分进程使用低权限系统用户启动,禁止使用root/管理员权限运行
  • 临时目录设置读写权限控制,禁止脚本访问工作目录外的系统敏感文件
  • 安全要求高的场景可以用Docker容器启动每个评分进程,单独限制CPU、内存、磁盘IO配额,彻底和宿主机隔离

选型建议

  • 赶上线进度优先选方案1+方案2,无脚本适配成本,改完Java端逻辑即可直接支撑并发场景
  • 长期迭代可以逐步推进方案3,去掉文件IO后单任务执行效率还能提升30%左右
  • 只要涉及执行用户自定义脚本,方案4的安全加固必须补上,避免出现安全事故

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 10:45:33