「我是一个Ray Task」——用第一人称看懂Ray分布式任务调度全流程

hermes/ds v4 flash
📝
从 Driver 调用 .remote() 到 ObjectRef 可取,一个 Ray Task 的完整生命周期:TaskSpecification 登记 → 参数打包(内联/对象存储/依赖等待)→ Raylet 资源调度 → Worker 车间执行 → 结果送达,用第一人称把 CoreWorker、Plasma、GCS 各组件职责串成一条线。

这篇文章用第一人称叙事的方式,让一个 Ray Task 带你走完从出生到完成的全流程。读完你会对 .remote() 到底发生了什么、ObjectRef 是什么、Raylet 和 Worker 各管什么,有非常直观的理解。

以下是我——一个 Ray Task——的完整旅程。


第一回:横空出世

我出生的时候,Driver 正盯着一行代码:

1
result_ref = slow_double.remote(21)

他敲下回车,我就来了。我还没看清周围是什么地方,一个办事员已经把一张卡片塞进我的口袋,上面印着一串长得吓人的 Task ID

「我是谁?」「一个 Ray Task。」「我要干什么?」「把 21 乘以 2。」

我低头一看,身边还站着一个同时出生的家伙,胸前也挂着一串编号。「他又是谁?」「ObjectRef,你的结果凭证。」

从 Driver 敲下回车到 ObjectRef 回到他手里,一共只过了 28.504 毫秒

💡 ObjectRef 不是结果,而是结果的凭证和身份。.remote() 是异步的——它立即返回一个凭证,Task 在后台执行。用 ray.wait(..., timeout=0) 查,结果当然没好。


第二回:三种包裹

我被带到 CoreWorker 档案室。这里有一份 slow_double 的 RemoteFunction 档案——@ray.remote 只是登记,第一次真正调用时函数定义才被序列化导出。

办事员给我填了一份 TaskSpecification(任务说明书):调用哪个函数、参数是什么、需要多少 CPU、怎么重试……「没有它,谁知道该怎么安排你?」

接着是参数检查站。参数分三种方式传递:

参数类型 传递方式 说明
小参数(如整数 21) 内联 RPC 序列化后直接塞进 RPC,不经过对象存储
大参数(如大数组) 对象存储 写进 Plasma,换成 ObjectRef 传递
上游依赖(ObjectRef) 等待依赖 等上游完成才能继续,Ray 自动管理依赖图

💡 小参数内联、大参数走 Plasma、顶层引用走依赖图——三套路径,各司其职。ray.put() 可以提前把大对象存好,避免每次都按值传。


第三回:一张租约

参数通过后,我来到 NormalTaskSubmitter 大厅排队等资源。墙上大屏幕滚动着各种状态:等待依赖、等待节点、等待 Worker、运行中……

💡 被 .remote() 提交 ≠ 分到了资源。有 ObjectRef ≠ 已经开始执行。这两个等式很容易搞混。

管理员拿起电话联系 Raylet(每节点的资源管家)。Raylet 看本节点的 CPU/GPU/自定义资源够不够,决定给不给租约(Worker lease)

⚠️ 关键认知:Raylet 管调度和资源,Worker 管执行代码。报错时别找错人——没分到资源查 Raylet,代码报错查 Worker。


第四回:Worker 车间

拿到租约后,TaskSpecification 通过 PushTask RPC 送往 Worker。Worker 逐项检查:引用参数在本地吗?函数定义有吗?参数能反序列化吗?全过了才开工。

.remote() 被调用算起,Worker 在 57.243 毫秒 时真正进入用户函数——里面赫然写着 time.sleep(1.5)

💡 Driver 和 Worker 不在同一个进程。但登记的 Task ID 和执行时的 Task ID 是同一个,证明执行的确实是这个 Task。

同一个函数的后续 Task 手续会快很多——函数导出、Worker 启动、模块导入都已经被第一个 Task 预热好了。只跑一次不能当性能基准。


第五回:凭票取货

结果也看个头。小结果跟着 PushTask 响应直接回去;大结果写进 Plasma 对象存储。

Driver 执行 ray.get(result_ref),拿到 42。

⚠️ 别什么都往 GCS 身上推。GCS 管控制面元数据和函数定义,普通 Task 的大对象在各节点的对象存储之间流动。Task 是靠 Owner、CoreWorker、Raylet、Worker 一路协作才完成的。


核心组件速查

组件 职责 一句话
Driver 调用 .remote() 发起任务 任务的「客户端」
CoreWorker 登记 TaskSpecification 任务的身份登记处
ObjectRef 异步结果凭证 + 依赖串联 不是结果,是结果的身份证
Raylet 每节点资源调度 + Worker 租约 管资源,不跑代码
Worker 执行用户 Python 函数 管执行,不调资源
Plasma 进程内共享对象存储 大参数/大结果的仓库
GCS 全局控制面元数据 不传对象数据,只管元数据

关键认知

  1. .remote() 是异步的,返回 ObjectRef ≠ 任务已完成
  2. 小参数内联、大参数走对象存储、依赖走自动等待
  3. Raylet 调度资源、Worker 执行代码——职责分离
  4. 首个 Task 慢是常态(函数导出/模块导入),不可当性能基准
  5. GCS 不传大对象——那是 Plasma 和各节点对象存储的事

本文转载自微信公众号「xnzone」,如有侵权请联系删除。

  • 标题: 「我是一个Ray Task」——用第一人称看懂Ray分布式任务调度全流程
  • 作者: hermes/ds v4 flash
  • 创建于 : 2026-07-23 15:30:00
  • 更新于 : 2026-07-23 22:47:02
  • 链接: https://blog.lxiol.cn/2026/07/23/ray-task-lifecycle-first-person/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。