Skip to content

3. 架构设计:组件分层与控制面/数据面 ​

本章画出 Ray 的进程模型与分层架构。内容以 Ray v2 架构白皮书(官方明确"取代学术论文")为准。


3.1 集群与进程模型 ​

一个 Ray 集群 = 1 个 head 节点 + N 个 worker 节点(集群关键概念):

                ┌──────────────────────── HEAD NODE ────────────────────────┐
                │  GCS Server   Dashboard(:8265)   Autoscaler driver         │
                │  ray_syncer   Raylet   Plasma Store                        │
                └───────────────▲────────────────────────▲───────────────────┘
                                │ gRPC (ray_syncer 心跳/资源) │
        ┌───────────────────────┼──────────────────────────┼──────────────────┐
        │              WORKER NODE 1                WORKER NODE 2  ...        │
        │   Raylet  Plasma Store  Workers     Raylet  Plasma Store  Workers   │
        └─────────────────────────────────────────────────────────────────────┘

每个节点(head 与 worker)都跑:

进程角色关键职责
GCS Server(仅 head)全局控制面存 actor/job/placement-group/node/resource 元数据;调度协调;autoscaler 状态
Raylet(每节点,C++)节点调度器调度任务、管 worker 池、对象存储生命周期、溢写编排
Plasma Store(每节点)数据面共享内存对象存储(mmap /dev/shm)
Dashboard Agent(每节点)可观测采集指标、日志,上报 head 的 Dashboard(:8265)
Driver / Worker(Python)用户代码Driver 是入口;Worker 执行 task/actor 方法;都内嵌 C++ CoreWorker

Raylet 是 C++ 写的(性能敏感的调度/对象传输),Python 侧通过 Cython 桥 _raylet.pyx 调用(正迁移到 pybind11,issue #46512)。


3.2 控制面 / 数据面 ​

控制面(Control Plane) ​

Driver ──(提交 task/actor)──► CoreWorker(owner)
                                   │
                                   │ ① RequestWorkerLease (gRPC)
                                   ▼
                            Raylet(本地) ──spillback──► Raylet(远端)
                                   │                       │
                                   │ ② GCS 注册/查询 actor·job·PG·node
                                   ▼
                                 GCS ──(ray_syncer 每 100ms 广播资源视图)──► 所有 Raylet
  • 任务不进 GCS(所有权模型):任务 spec 在 owner CoreWorker 里(TaskManager::OwnedTasks)。GCS 只存 actor/job/PG/node/resource + 任务事件/状态(供 State API/Dashboard,不驱动调度)。
  • GCS 资源广播每 100ms 一次(ray_syncer,gRPC,取代旧的 Redis-pubsub 心跳):GCS 从每节点 raylet 拉资源可用量、聚合、再广播。最终一致(某次查询未必反映精确快照);每节点自身资源视图强一致。
  • ray_syncer(src/ray/ray_syncer/)是关键解耦件:把集群状态同步从 Redis 假设中解放出来,是 1.11 "Redisless by default" 得以实现的前提。

数据面(Data Plane) ​

Worker A ──写返回值──► Plasma Store A(本地 mmap)
                            │
        ObjectRef 在 owner 处建引用计数
                            │
Worker B ──ray.get(ref)──► owner 告知位置 ──► ObjectManager.Pull ──► chunk 传输 / 本地命中零拷贝
  • ObjectManager(src/ray/object_manager/):分块 Push/Pull 传输,带 PullManager(拉取编排)与 PushManager(并发出站上限)。

  • OwnershipBasedObjectDirectory:唯一的对象目录实现——位置索引基于所有者 CoreWorker + pubsub,不集中存于 GCS。

  • 引用计数五元组(白皮书 "Reference Counting"):每个被拥有的对象维护

    1. 本地 Python 引用计数
    2. 已提交但未完成的任务数
    3. borrower(借走 ref 的 worker)
    4. nested(值里包含此 ref 的对象)
    5. lineage 计数

    对应 ray memory 标签:LOCAL_REFERENCE / PINNED_IN_MEMORY / USED_BY_PENDING_TASK / CAPTURED_IN_OBJECT / ACTOR_HANDLE。

  • Borrower 协议:ref 逃出作用域(传给 task、存进别的对象、被返回)时,owner 把它加入本地计数;任务完成时 worker 汇报"哪些 ref 仍被借用";borrower 形成递归树,owner 只跟踪直接 borrower。


3.3 对象存储分层(Plasma) ​

Plasma 不是单一类,而是一组协作组件(src/ray/object_manager/plasma/):

组件职责
PlasmaStore(单线程)对象存储主进程;创建/密封/删除对象;OOM 时回调溢写
ObjectStore持有 object_table_(ObjectID → LocalObject);CreateObject/SealObject
ObjectLifecycleManager引用计数(AddReference/RemoveReference);refcount 归 0 才可驱逐
EvictionPolicy(LRU)RequireSpace → ChooseObjectsToEvict
PlasmaAllocator从预 mmap 的 /dev/shm(或磁盘)分配;FallbackAllocate 在 OOM 时退到本地盘 mmap
ClientMmapTableEntry客户端 per-fd mmap 缓存(零拷贝)
CreateRequestQueueOOM/溢写触发逻辑

主副本 vs 可驱逐副本 ​

  • 主副本(primary copy):由生产任务或 ray.put 创建的第一份 plasma 副本,被 raylet pin,只要还有 ref 在作用域内就不会被 LRU 驱逐。
  • 可驱逐副本:ray.get/参数拉取产生的副本,可被 LRU 驱逐。
  • 兜底:超大对象在内存放不下时,raylet 用 FallbackAllocate 把它 mmap 到本地磁盘;磁盘满 → OutOfDiskError。

主动溢写(Object Spilling) ​

冷对象溢写到外部存储(默认 /tmp,可配 S3/GCS,但 S3 后端白皮书标为 experimental):

  • 触发:80% 容量时主动溢写(object_spilling_threshold=0.8,白皮书 "Handling OOM"),不止 OOM 时才溢。
  • 执行任务参数上限 70%:所有在途任务的参数总大小封顶对象存储 70%,留 30% 给任一任务写返回值。
  • 对象融合:< 100MB 的对象融合进单文件(避免 inode 耗尽),24 字节头 addr_len|metadata_len|buf_len,URL …/ray_spilled_objects_<nodeid>/<uuid>-multi-<count>?offset=N&size=M。
  • 三层:检测(CreateRequestQueue)→ 编排(raylet 的 LocalObjectManager,真正的"溢写管理器",状态机 Pinned→PendingSpill→Spilled)→ 执行(Python IO worker 进程,spill/restore 两个池,调用 external_storage.py 后端:FileSystemStorage 默认、ExternalStorageSmartOpenImpl 走 S3/GCS)。

3.4 GCS(Global Control Store) ​

GCS 是 head 节点上的单一 GcsServer actor(src/ray/gcs/),托管控制面状态。关键管理者:

  • gcs_node_manager / gcs_job_manager / gcs_resource_manager / gcs_worker_manager / gcs_function_manager
  • gcs_placement_group(_manager|_scheduler) —— placement group 两阶段提交
  • gcs_autoscaler_state_manager / gcs_health_check_manager
  • gcs_kv_manager —— 通用 KV(ray.kv、Serve checkpoint)
  • gcs_task_manager —— 只存任务事件/状态供 State API/Dashboard,不驱动调度(所有权模型;2.58 起可进一步迁出 GCS 热路径,见下文)

GCS 容错(关键工程约束) ​

默认 GCS 不可容错:状态全在内存,GCS 挂 = 整个集群挂。(GCS FT 文档 原文)

  • 自 1.11 起 GCS 默认存自己的内存结构(Redis 不再是默认运行时依赖,Redis in Ray)。
  • 2.57(REP-64)起不再需要外部 Redis:设置 RAY_gcs_storage=rocksdb + RAY_gcs_storage_path 即可启用内嵌 RocksDB 持久化 GCS 状态,外接 Redis 从"必需"变为遗留选项(2.57.0 release)。KubeRay v1.7 起提供 alpha 支持:operator 自动为内嵌 RocksDB 供给 PVC,彻底免除 Redis 运维。
  • 旧路径(外接 HA Redis,RAY_REDIS_ADDRESS)仍可用:GCS 重启时从中恢复。
  • 恢复期间不可用:actor/PG 的创建/删除/重建、资源管理、worker 节点注册、worker 进程创建。仍可用:运行中的 task/actor 继续活、已存在对象仍可访问。
  • raylet 重连超时 60s(RAY_gcs_rpc_server_reconnect_timeout_s)。
  • 官方警告(Redis 路径):"GCS 容错(外接 Redis)只在 KubeRay 上用于 Ray Serve 容错时官方支持;其他情况自担风险。"
  • KubeRay v1.3.0 起容错配置收敛为 spec.gcsFaultToleranceOptions(redisAddress/redisPassword),注意多集群 Redis key 冲突(kuberay#2694)。

任务事件迁出 GCS(2.58) ​

gcs_task_manager 原承担任务事件采集,曾是大集群 GCS 热路径的负担。2.58 起(RAY_enable_task_events_to_dashboard_head)任务事件缓冲改由 ray event recorder 导出到 head 上的独立 task events 服务(内存存储),State API 与 ray.timeline 改从它读取——任务事件的写入/服务移出 GCS 热路径(2.58.0 release)。这是所有权模型"调度不依赖 GCS"思想的延续。


3.5 全栈一张图 ​

┌─────────────────────────── 用户 Python ───────────────────────────┐
│  @ray.remote fn/class   ray.get/put/wait   ray.init   serve.run   │
└───────────────────────────────┬───────────────────────────────────┘
                          Cython 桥 _raylet.pyx
┌───────────────────────────────▼───────────────────────────────────┐
│              CoreWorker (C++,每 driver/worker 内嵌)               │
│  TaskManager(OwnedTasks)  ReferenceCounter(所有权表)               │
│  ObjectRecoveryManager  LeasePolicy  FutureResolver                │
└───┬───────────────────────────────────────────────┬───────────────┘
    │ RequestWorkerLease (gRPC)                       │ 对象读/写
    ▼                                                 ▼
┌─────────────────────────────┐               ┌─────────────────────┐
│  Raylet (C++, 每节点)        │               │  Plasma Store       │
│  ClusterLeaseManager         │  pin/溢写编排  │  ObjectStore        │
│  LocalLeaseManager           │◄─────────────►│  LRU/FallbackAlloc  │
│  WorkerPool(含 IO 池)        │               │  ClientMmapTable    │
│  LocalObjectManager(溢写)    │               └──────────▲──────────┘
└───────┬──────────────────────┘                          │ chunk Push/Pull
        │ ray_syncer (gRPC, 100ms)                        │ ObjectManager
        ▼                                                 │
┌──────────────────────────────────────────────────────────┴──────────┐
│   GCS (head): node/job/resource/worker/PG/kv/autoscaler + 任务事件    │
│   ray_syncer 广播 ◄──── 所有 Raylet 资源视图                          │
└──────────────────────────────────────────────────────────────────────┘

下一章 执行模型 把这张图变成"一个请求的完整生命周期"。

Released under CC-BY-SA-4.0 License.