← blog 专题系列 · 2026-04-06

master0worker资源隔离

梳理 RL-Job-Scheduler 中 Master 与 Worker 的进程、心跳与任务抢占隔离现状,指出 GPU、内存、并发与文件系统隔离的缺失并给出分级改进方案。

6 min read

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 少量任务
  • 任务负载可控的环境

不适合:

  • 多租户环境
  • 大规模多任务并行
  • 关键业务(无资源保障)

Sources

No external sources for this entry.

Related