容错与可观测性
分布式程序一定会遇到失败:节点挂了、任务抛异常了、对象丢失了。Ray 在这里要做两件事:把失败以可理解的方式暴露出来,并在部分场景下自动恢复。
任务失败
远程任务抛异常时,异常不会在 .remote() 时出现,而是在 ray.get 时出现。
python
@ray.remote
def parse(line):
raise ValueError("bad line")
ref = parse.remote("...")
ray.get(ref) # RayTaskError这是因为 .remote() 只提交任务,任务可能还没开始执行。
重试
Ray Task 可以配置重试:
python
@ray.remote(max_retries=3)
def flaky():
...适合重试的错误:
- 临时网络抖动。
- 外部服务偶发失败。
- 节点异常导致任务没完成。
不适合盲目重试的错误:
- 输入数据格式错误。
- 代码逻辑错误。
- 不可重复执行的副作用任务。
副作用任务
如果任务会写数据库、扣库存、发消息,重试可能导致重复副作用。此时需要幂等设计或业务级去重。
Actor 失败
Actor 持有状态,所以 Actor 失败比 Task 失败复杂。
你需要考虑:
- Actor 是否可以重启。
- 状态如何恢复。
- 已排队方法如何处理。
- 调用方是否能重试。
Actor 可以配置 max_restarts、max_task_retries 等策略,但业务状态恢复仍然需要你设计。
对象丢失与 lineage
Ray 可以通过任务 lineage 知道某些对象是由哪个任务生成的。当对象丢失时,理论上可以重新执行生成它的任务。
这个机制的前提是任务可重放。如果任务依赖不可重复的外部副作用,重建结果就不可靠。
GCS 容错
现代 Ray 中 GCS 是集群元数据核心组件,官方文档提供了 GCS fault tolerance 配置。生产环境要关注:
- GCS 是否配置持久化。
- Head Node 故障后如何恢复。
- Kubernetes 上的 RayCluster 生命周期。
- Dashboard 和 State API 是否可用。
可观测性工具
Ray 提供多种观察入口:
| 工具 | 用途 |
|---|---|
| Dashboard | 查看节点、任务、Actor、对象、日志和指标 |
| State API / CLI | 程序化查询任务、Actor、对象状态 |
| 日志目录 | 查看 Worker、Raylet、GCS 日志 |
| Metrics | 接入 Prometheus/Grafana |
| Timeline / profiling | 定位性能瓶颈 |
调试思路
遇到 Ray 程序慢或卡住时,按这个顺序查:
- 任务是否真的并行提交,还是在循环里
ray.get。 - 资源是否足够,是否有任务等待 GPU 或自定义资源。
- 是否有大对象反复复制或 spill。
- Actor 队列是否堆积。
- Worker 日志是否有异常。
- 节点是否掉线或内存压力过大。
Mini Ray 的故障模型
Mini Ray 只做最小异常传播:
- Task 抛异常时,
ObjectStore记录异常。 ray.get(ref)时抛出MiniRayTaskError。- Actor 初始化失败时,后续方法调用也失败。
运行:
bash
uv run python examples/mini_ray_runtime/demos/06_failures.py你会看到远程异常被包装后再传回 Driver。这和真实 Ray 的错误传播语义一致,只是没有自动重试和分布式恢复。
工程建议
- 任务函数尽量纯,方便重试。
- 外部副作用要幂等。
- 大状态放 Actor 前先想清楚恢复策略。
- 生产环境必须打开 Dashboard、日志采集和指标采集。
- 不要把所有错误都交给
max_retries,它不是业务正确性的替代品。