1604 words
8 minutes
master0worker资源隔离
Master-Worker 资源隔离分析
一、当前架构的资源隔离现状
┌─────────────────────────────────────────────────────────────────┐ │ Master (8082) │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ SchedulerService ───▶ StringRedisTemplate ───▶ Redis│ │ │ │ LogManager ───▶ WebSocket (SimpMessagingTemplate)│ │ │ │ MasterNettyServer ───▶ NioEventLoopGroup (Port 9000) │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ 内存:JVM Heap (Spring Boot) │ │ CPU:独立线程池 (NioEventLoop) │ └─────────────────────────────────────────────────────────────────┘ │ ┌────────────────────┼────────────────────┐ │ │ │ ▼ ▼ ▼ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ Worker-1 │ │ Worker-2 │ │ Worker-N │ │ Java Agent │ │ Java Agent │ │ Java Agent │ │ ──────── │ │ ──────── │ │ ──────── │ │ train.py │ │ train.py │ │ train.py │ │ GPU: 1 │ │ GPU: 1 │ │ GPU: 1 │ └────────────┘ └────────────┘ └────────────┘ │ │ │ └────────────────────┼────────────────────┘ │ ▼ ┌────────────┐ │ Redis │ │ (共享数据) │ └────────────┘二、已实现的隔离
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. 心跳租约隔离 ✅
Worker ──▶ Redis Key ──▶ 独占 worker:1:hb ──▶ 仅 Worker-1 的心跳 worker:1:task ──▶ 仅 Worker-1 的任务 task:xxx:workerId ──▶ 任务所有权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 独立连接 │ 低 │ └────────────┴──────────────┴─────────────────────────┴──────────┘五、生产级隔离架构
┌─────────────────────────────────────────────────────────────────────┐ │ 推荐的 Worker 资源隔离 │ └─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐ │ Worker Agent (JVM) │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ TaskSlot-1 │ │ TaskSlot-2 │ │ TaskSlot-N │ ← Semaphore│ │ │ │ │ ┌───────┐ │ ┌───────┐ │ │ ┌───────┐ │ │ │ │ │ │ │Python │ │ │Python │ │ │ │Python │ │ │ │ │ │ │ │Process│ │ │Process│ │ │ │Process│ │ │ │ │ │ │ │cgroup │ │ │cgroup │ │ │ │cgroup │ │ │ │ │ │ │ │memory │ │ │memory │ │ │ │memory │ │ │ │ │ │ │ │ cpu │ │ │ cpu │ │ │ │ cpu │ │ │ │ │ │ │ │ gpu │ │ │ gpu │ │ │ │ gpu │ │ │ │ │ │ │ └───────┘ │ └───────┘ │ │ └───────┘ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘资源隔离技术栈:
┌─────────────────────────────────────────────────────────────────────┐│ 隔离层 │├─────────────────────────────────────────────────────────────────────┤│ 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 绑定
// 根据 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.jar3. 最佳改进:Kubernetes + Docker
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 少量任务
- 任务负载可控的环境
不适合:
- 多租户环境
- 大规模多任务并行
- 关键业务(无资源保障)
master0worker资源隔离
https://sgjki547.top/posts/master-worker-isolation/