← blog 专题系列 · 2026-04-06
master0worker资源隔离
梳理 RL-Job-Scheduler 中 Master 与 Worker 的进程、心跳与任务抢占隔离现状,指出 GPU、内存、并发与文件系统隔离的缺失并给出分级改进方案。
Master-Worker 资源隔离分析
一、当前架构的资源隔离现状
graph TD
subgraph Master["Master (8082)"]
SS["SchedulerService → StringRedisTemplate → Redis"]
LM["LogManager → WebSocket (SimpMessagingTemplate)"]
NS["MasterNettyServer → NioEventLoopGroup (Port 9000)"]
end
subgraph Workers["Worker Agents"]
W1["Worker-1 Java Agent<br/>train.py / GPU: 1"]
W2["Worker-2 Java Agent<br/>train.py / GPU: 1"]
WN["Worker-N Java Agent<br/>train.py / GPU: 1"]
end
REDIS["Redis (共享数据)"]
Master --> W1
Master --> W2
Master --> WN
W1 --> REDIS
W2 --> REDIS
WN --> REDIS
note["内存:JVM Heap (Spring Boot)<br/>CPU:独立线程池 (NioEventLoop)"]
二、已实现的隔离
1. 进程级隔离 ✅
WorkerAgent 是独立 JVM 进程 ├── 每个 Worker 有独立的 Java Heap ├── 每个 Worker 有独立的 Python 进程 └── Worker 之间完全进程隔离
优点:
- 一个 Worker 崩溃不影响其他 Worker
- 内存不共享,无法相互影响
- 可部署在不同机器上实现物理隔离
2. 任务抢占原子性 ✅
// SchedulerService.tryPreemptWorker() 使用 Lua 脚本保证原子性
String script =
"if redis.call('get', KEYS[1]) == 'alive' and redis.call('exists', KEYS[2]) == 0 then " +
" redis.call('set', KEYS[2], ARGV[1], 'EX', 120); " + // 2分钟过期
" return 1; " +
"else " +
" return 0; " +
"end";
// KEYS[1] = worker:{workerId}:hb (心跳存活检查)
// KEYS[2] = worker:{workerId}:task (任务锁)
// ARGV[1] = taskId
防止:
- 同一 Worker 被多个任务同时抢占
- 任务分发重复
3. 心跳租约隔离 ✅
graph LR
W["Worker"] --> R["Redis Key"] --> EX["独占"]
N["worker:1:hb → 仅 Worker-1 的心跳<br/>worker:1:task → 仅 Worker-1 的任务<br/>task:xxx:workerId → 任务所有权"]
W -.-> N
Worker 间资源状态完全隔离
三、缺失的隔离(风险点)
1. GPU 资源隔离 ❌
# WorkerAgent.runPythonTask() 直接启动 train.py
# 无 GPU 隔离配置
ProcessBuilder pb = new ProcessBuilder();
pb.command("uv", "run", "python", "scripts/train.py", ...);
// 问题:如果同一 GPU 被多个任务占用,会 OOM
问题:
Worker-1: 运行 task-A (占用 GPU 0)
Worker-2: 运行 task-B (也尝试用 GPU 0)
↓
GPU 内存竞争 → OOM
解决方案:
# train.py 应该用 CUDA_VISIBLE_DEVICES 限制 GPU
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0" # 指定 GPU
# 或者用 NVIDIA Container Toolkit
# 或者用 nvidia-docker GPU 隔离
2. 内存/CPU 限制 ❌
// WorkerAgent 启动 Python 进程时无资源限制
ProcessBuilder pb = new ProcessBuilder();
pb.directory(new File(System.getProperty("user.dir")));
// 问题:Python 进程可能无限占用内存
应该使用:
// 方案1: ProcessBuilder 限制
ProcessBuilder pb = new ProcessBuilder();
pb.command("python", "train.py", ...);
// 设置资源限制 (Linux)
new ProcessBuilder("prlimit", "--mem=4096000", "--pid", pid); // 4GB 内存
// 方案2: Docker 容器隔离
// docker run --memory=4g --cpus=2 train.py
// 方案3: cgroups (Kubernetes)
3. 任务并发数限制 ❌
// 当前:Worker 同时只能运行 1 个任务
// 但没有机制强制这个限制
// MasterHandler 接收 EXECUTE_TASK 后直接启动线程
new Thread(() -> runPythonTask(ctx, req), "Task-Executor-" + taskId).start();
改进方案:
// Worker 端信号量控制并发
private static final Semaphore TASK_SEMAPHORE = new Semaphore(1);
private void handleExecuteTask(ChannelHandlerContext ctx, ExecuteTaskRequest req) {
if (!TASK_SEMAPHORE.tryAcquire()) {
// 拒绝任务,让 Master 重新调度
sendRejection(ctx, req.getTaskId(), "Worker busy");
return;
}
try {
// 执行任务
} finally {
TASK_SEMAPHORE.release();
}
}
4. 任务间文件系统隔离 ❌
// 所有任务共享同一个工作目录
File workerLogDir = new File("server_log");
File workerLogFile = new File(workerLogDir, taskId + ".log");
// 问题:如果任务同名文件会冲突
改进:
// 每个任务独立的目录
File taskDir = new File("server_log/" + taskId);
taskDir.mkdirs();
File workerLogFile = new File(taskDir, "output.log");
四、完整资源隔离矩阵
| 资源类型 | 隔离方式 | 当前状态 | 风险等级 |
|---|---|---|---|
| 进程内存 | JVM 进程隔离 | ✅ Worker 进程级隔离 | 低 |
| GPU 显存 | 无 | ❌ 多任务可能 GPU 冲突 | 高 |
| CPU 时间 | 无 | ❌ 任务可能 CPU 垄断 | 中 |
| 磁盘空间 | 无 | ❌ 日志可能撑爆磁盘 | 中 |
| 网络带宽 | 无 | ❌ 任务日志可能打爆网络 | 中 |
| 任务并发 | 无 | ❌ Worker 可接受多任务 | 中 |
| 文件路径 | 无 | ❌ 同名文件可能冲突 | 低 |
| Redis 连接 | 独立连接 | ✅ 每 Worker 独立连接 | 低 |
五、生产级隔离架构
graph TD
subgraph WA["推荐的 Worker 资源隔离 — Worker Agent (JVM)"]
subgraph S1["TaskSlot-1 (Semaphore)"]
P1["Python Process<br/>cgroup: memory / cpu / gpu"]
end
subgraph S2["TaskSlot-2 (Semaphore)"]
P2["Python Process<br/>cgroup: memory / cpu / gpu"]
end
subgraph SN["TaskSlot-N (Semaphore)"]
PN["Python Process<br/>cgroup: memory / cpu / gpu"]
end
end
资源隔离技术栈:
| 隔离层 | 技术实现 |
|--------|---------|
| GPU隔离 | NVIDIA Device Plugin (K8s) / CUDA_VISIBLE_DEVICES |
| 内存隔离 | cgroups memory.limit / Docker --memory |
| CPU隔离 | cgroups cpu.shares / Docker --cpus |
| IO隔离 | cgroups blkio.throttle / Docker --device-read-bps |
| 进程隔离 | Linux Namespace / Docker container |
六、快速改进建议
1. 最小改进:GPU 绑定
// WorkerAgent.java
// 根据 workerId 绑定不同 GPU
private String getGpuForWorker(String workerId) {
// 简单哈希:确保同一 Worker 永远用同一 GPU
int gpuIndex = Math.abs(workerId.hashCode() % availableGpuCount);
return String.valueOf(gpuIndex);
}
// 启动 Python 时设置环境变量
ProcessBuilder pb = new ProcessBuilder();
pb.environment().put("CUDA_VISIBLE_DEVICES", getGpuForWorker(workerId));
2. 中等改进:cgroups 资源限制
# 启动 Worker 前创建 cgroup
sudo cgcreate -g memory,cpu:/worker-1
sudo cgset -r memory.limit_in_bytes=4G /worker-1
sudo cgset -r cpu.shares=1024 /worker-1
# 在 cgroup 中启动进程
sudo cgexec -g memory,cpu:/worker-1 java -jar worker.jar
3. 最佳改进:Kubernetes + Docker
# worker-deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: worker
resources:
limits:
nvidia.com/gpu: 1
memory: "4Gi"
cpu: "2"
requests:
memory: "2Gi"
cpu: "1"
env:
- name: CUDA_VISIBLE_DEVICES
value: "0"
七、总结
| 方面 | 当前实现 | 建议 |
|------|---------|------|
| 进程隔离 | ✅ JVM 进程独立 | 保持 |
| 任务原子性 | ✅ Lua 脚本保证 | 保持 |
| GPU 隔离 | ❌ 完全缺失 | 添加 CUDA_VISIBLE_DEVICES |
| 内存限制 | ❌ 无限制 | 添加 cgroups/docker limit |
| 并发控制 | ❌ 无限制 | 添加 Semaphore |
| 文件隔离 | ❌ 共享目录 | 改为任务独立目录 |
| 网络 QoS | ❌ 无 | 限流 + 异步日志 |
当前系统适合:
- 单机单 GPU 少量任务
- 任务负载可控的环境
不适合:
- 多租户环境
- 大规模多任务并行
- 关键业务(无资源保障)