第 5 篇:「集群的记忆与裁判」—— GCS 全局控制服务解剖
阅读本文你将了解:
- GCS 的全部状态就是 6 张表(
src/ray/gcs/gcs_table_storage.h:200),持久化就是把这 6 张表换一个StoreClient后端——没有第二种机制。- 官方说的三种存储在源码里是
StorageType枚举:IN_MEMORY / REDIS_PERSIST / ROCKSDB_PERSIST(src/ray/gcs/gcs_server.h:130),默认gcs_storage="memory"(src/ray/common/ray_config_def.h:488)→ 默认不容错。- 官方文档列的「GCS 恢复期间不可用清单」(actor / placement group / 资源管理 / 节点注册 / worker 创建),逐条对应 GCS 里的 manager 对象,因为恢复期它们还没被
Initialize。- Task 与 Actor 的调度路径完全不同:Task 由本地 Raylet 调度(第 4 篇),Actor 由 GCS 主动向 Raylet 租 worker(
src/ray/gcs/actor/gcs_actor_scheduler.h:238)。- GCS 自己也持有一份
ClusterResourceScheduler(src/ray/gcs/gcs_server.h:268)——但不是用来调度 task,而是给 PlacementGroup 做全局装箱。- GCS 向全集群广播变更用的是**长轮询(long-poll)**而非流式 gRPC(
src/ray/gcs/pubsub_handler.h:47)。
5.0 一句话定性
GCS(Global Control Service)是 Ray 集群的唯一权威元数据仓库 + 少量全局决策的裁判。它不做数据面(对象传输不经过它),也不调度普通任务;它负责的是"谁活着、谁在哪、谁该被重建"这一类必须全局一致、且必须能持久化的信息。
理解 GCS 有一个极简的心智模型:
GCS = 6 张表 + 一堆围绕表工作的 manager + 一个把变更广播出去的 pubsub。
5.1 架构视角(Architect):GCS 管什么、不管什么
官方文档对 GCS 的定义(fault_tolerance/gcs.html):
The Global Control Service, or GCS, manages cluster-level metadata. It also provides a handful of cluster-level operations including actor, placement groups and node management.
拆成"管"与"不管":
| GCS 管(必须全局一致) | GCS 不管(数据面 / 本地决策) |
|---|---|
| 节点注册与存活判定 | 对象存储与传输(Plasma / ObjectManager) |
| Actor 注册表与重建 | 普通 Task 的调度(本地 Raylet 决策) |
| PlacementGroup 创建与装箱 | 任务参数拉取(PullManager) |
| 集群资源视图(近似、异步) | 引用计数(各 worker 的 ReferenceCounter) |
| Job / Worker / Task 事件元数据 | 对象溢写(LocalObjectManager) |
这个分工直接决定了 Ray 的可用性特征:GCS 挂掉不会让正在跑的任务停止(官方明确说 running Ray tasks and actors remain alive, and any existing objects stay available),但会让任何需要"改元数据"的操作停摆——新建 actor、新加节点、新创 placement group 都不行。
反过来,这也解释了一个常见误解:Ray 集群"没崩"不代表健康,可能只是 GCS 在恢复中。
5.2 GcsServer 的成员地图
GcsServer(src/ray/gcs/gcs_server.h:99)是整个控制面的容器。它的成员声明顺序有一条硬约束,写在源码注释里(:255-256):
NOTE: the declaration order for data members must follow the dependency structure between them.
因为 C++ 析构顺序是声明的逆序,声明顺序错了就会在析构时出现"依赖对象已销毁"的悬空访问。所以我们可以从成员声明顺序直接读出依赖关系(:268-330):

两类成员值得单独点名:
cluster_resource_scheduler_(:268):GCS 自己也有一份集群资源调度器,但不用于调度 task,而是给 PlacementGroup 做全局装箱决策(§5.10)。ray_syncer_(:295) 与InitRaySyncer()(:165):这回答了第 4 篇留下的悬念——各 Raylet 的集群资源视图靠它同步收敛(§5.7)。
5.3 状态模型:6 张表就是 GCS 的全部记忆
GcsTableStorage(src/ray/gcs/gcs_table_storage.h:200)的构造函数(:202)一目了然:
| 表 | 访问器 | 键 → 值 | 用途 |
|---|---|---|---|
job_table_ | JobTable()(:214) | JobID → JobTableData | Job 元数据 |
actor_table_ | ActorTable()(:219) | ActorID → ActorTableData | Actor 注册表(谁在哪、什么状态) |
actor_task_spec_table_ | ActorTaskSpecTable()(:224) | ActorID → TaskSpec | Actor 重建的凭据(保存创建参数) |
placement_group_table_ | PlacementGroupTable()(:229) | PlacementGroupID → PG 数据 | PG 状态 |
node_table_ | NodeTable()(:234) | NodeID → GcsNodeInfo | 节点清单 |
worker_table_ | WorkerTable()(:239) | WorkerID → WorkerTableData | Worker 清单 |
底层统一是 std::shared_ptr<StoreClient> store_client_(:250)——这就是持久化后端的抽象点。

关键结论:GCS 没有任何"隐藏状态"。所有元数据都在这 6 张表里,持久化 = 换后端,恢复 = 从后端把表读回来。这就是为什么官方说恢复后 “it loads all the data back from the backing store and resumes regular functions”。
actor_task_spec_table_ 尤其值得注意:它保存的是 Actor 的创建参数(TaskSpec)。Actor 能在其所在节点宕机后被重建,靠的就是这张表。
5.4 三种存储后端:官方说法 ↔ 源码落点
StorageType 枚举(src/ray/gcs/gcs_server.h:130)与配套常量(:137-139)逐字对应官方的环境变量取值:
官方 RAY_gcs_storage 取值 | 源码常量 | 枚举 |
|---|---|---|
memory(默认) | kInMemoryStorage(:137) | IN_MEMORY = 1 |
redis | kRedisStorage(:138) | REDIS_PERSIST = 2 |
rocksdb | kRocksDbStorage(:139) | ROCKSDB_PERSIST = 3 |
配置项在 GetStorageType()(:237)里读出,默认值全部对得上官方文档:
| 官方参数 | 源码 | 默认值 |
|---|---|---|
RAY_gcs_storage | gcs_storage(src/ray/common/ray_config_def.h:488) | "memory" |
RAY_gcs_storage_path | gcs_storage_path(:492) | ""(rocksdb 模式必填) |
RAY_gcs_rocksdb_io_pool_size | gcs_rocksdb_io_pool_size(:501) | 4 |
RAY_gcs_rocksdb_strand_buckets | gcs_rocksdb_strand_buckets(:508) | 64 |
RAY_gcs_rpc_server_reconnect_timeout_s | gcs_rpc_server_reconnect_timeout_s(:549) | 60 |
官方文档还提到 RocksDB 是 single-writer(同一时刻只能有一个 GCS 打开该路径),以及 I/O 走独立线程池避免阻塞 GCS 事件循环——后者对应的正是 gcs_rocksdb_io_pool_size(默认 4)。
关于 strand_buckets=64 的含义,源码注释(:508 附近)与官方一致:按键哈希分桶,同桶串行、异桶并发,在保序与并发之间取平衡。
5.5 GCS 容错:官方"不可用清单"的源码解释
官方文档列出 GCS 恢复期间不可用的功能:
Actor creation, deletion and reconstruction. / Placement group creation, deletion and reconstruction. / Resource management. / Worker node registration. / Worker process creation.
这五条逐条对应 GcsServer 的 manager 初始化方法——因为恢复期这些对象要么还没构建、要么还没 Initialize:
| 官方"不可用" | 对应初始化方法 | 位置 |
|---|---|---|
| Actor 创建/删除/重建 | InitGcsActorManager | src/ray/gcs/gcs_server.h:178 |
| PlacementGroup 创建/删除/重建 | InitGcsPlacementGroupManager | :183 |
| 资源管理 | InitGcsResourceManager | :162 |
| Worker 节点注册 | InitGcsNodeManager | :152 |
| Worker 进程创建 | InitGcsWorkerManager | :198 |
而"正在跑的 task/actor 不受影响",源码层面的原因是:它们的执行完全在本地 Raylet + CoreWorker 上,只在需要改元数据时才联系 GCS。
另一条容易忽略的官方说明,源码里也有对应:
If a raylet can’t reconnect for more than 60 seconds, that raylet exits and the corresponding node fails.
这个 60 秒就是 gcs_rpc_server_reconnect_timeout_s(src/ray/common/ray_config_def.h:549)。这是生产上非常关键的一个旋钮:GCS 恢复时间超过它,整个集群会连锁性地"节点自杀"。在 GCS 恢复较慢的环境(大集群、RocksDB 数据量大)里,应适当调大。
5.6 节点管理:注册、心跳与失败判定
节点生命周期由 GcsNodeManager(src/ray/gcs/gcs_node_manager.h)与健康检查协作完成:
| 方法 | 行号 | 作用 |
|---|---|---|
HandleRegisterNode | :71 | 节点注册(写入 node_table_ 并广播) |
HandleUnregisterNode | :76 | 主动注销 |
HandleDrainNode | :84 | 节点排水(优雅下线) |
HandleGetAllNodeInfo | :89 | 查询全量节点 |
HandleGetAllNodeAddressAndLiveness | :94 | 查询地址与存活 |
HandleCheckAlive | :112 | 存活探测 |
OnNodeFailure | :122 | 节点失败处理 |
IsNodeDead / IsNodeAlive | :146 / :152 | 存活状态查询 |
UpdateAliveNode | :232 | 更新存活节点集合 |
事件通知用监听器模式:AddNodeAddedListener(:196)/ AddNodeRemovedListener(:184)/ AddNodeDrainingListener(:208)。这是 GCS 内部解耦的关键——节点状态一变,资源管理器、Actor 管理器、PG 管理器都能收到通知(它们各自的 OnNodeDead:src/ray/gcs/gcs_resource_manager.h:102、src/ray/gcs/actor/gcs_actor_manager.h:176、src/ray/gcs/gcs_placement_group_manager.h:155)。
健康检查是独立机制(GcsHealthCheckManager):AddNode(:74)注册节点通道,MarkNodeHealthy(:92)标记健康,FailNode(:109)判定失败。它不依赖心跳,而是主动探测——这意味着节点失败有两条检测路径(心跳超时 + 主动探测失败),互为兜底。

5.7 集群资源视图如何收敛:回答第 4 篇的悬念
第 4 篇留了一个问题:各 Raylet 各自持有一份异步收敛的集群资源视图,那这份视图到底怎么同步?答案在 GCS 侧。
两条互补的通道:
- RaySyncer(
src/ray/gcs/gcs_server.h:295/InitRaySyncer():165):专门负责在 Raylet 与 GCS 之间同步资源视图消息。GCS 侧的消费入口是GcsResourceManager::ConsumeSyncMessage(src/ray/gcs/gcs_resource_manager.h:66)。 - Raylet 心跳上报:第 4 篇讲过的
FillResourceUsage(src/ray/raylet/scheduling/cluster_lease_manager.h:124)随心跳带上resource_load与resource_load_by_shape;GCS 侧对应UpdateResourceLoads(src/ray/gcs/gcs_resource_manager.h:150)、UpdateNodeResourceUsage(:127)、UpdateFromResourceView(:136)。
GCS 侧的集群资源账本由 GcsResourceManager 维护,对外提供查询接口:HandleGetAllAvailableResources(:71)、HandleGetAllTotalResources(:78)、HandleGetAllResourceUsage(:90)。
还有一条反向链路:InitGcsResourceLoadPuller(src/ray/gcs/gcs_server.h:209)与 resource_load_puller_(:273)——GCS 会主动拉取各 Raylet 的负载,而不是只被动接收心跳。拉取与上报并行,是为了让 autoscaler 拿到更及时的数据(gcs_autoscaler_state_manager_,:272)。
收敛性质:GCS 的集群资源视图同样是近似值(接收有延迟、拉取有周期)。所以第 4 篇说的 spillback 机制,本质上是在补偿"GCS 视图 + 本地视图都不精确"这一事实——去中心化调度 + 最终一致视图 + spillback 兜底,三者是一套完整设计。
5.8 pubsub:GCS 如何把变更推给全集群
GCS 的状态变更要通知到所有 Raylet 与 CoreWorker,机制是 pubsub。入口在 src/ray/gcs/pubsub_handler.h:
| 组件 | 行号 | 职责 |
|---|---|---|
HandleGcsPublish | :43 | 发布消息 |
HandleGcsSubscriberPoll | :47 | 长轮询拉取 |
HandleGcsSubscriberCommandBatch | :51 | 批量订阅/取消 |
sender_to_subscribers_ | :57 | 发送者 → 订阅者集合 |
ControlPlanePubSubHandler | :62 | 控制面频道(actor/job/node 变更) |
ObservabilityPubSubHandler | :95 | 可观测频道(日志、错误、Dashboard 资源 JSON) |
GCS 里有两个独立的 publisher(src/ray/gcs/gcs_server.h:275/:277):
gcs_publisher_:控制面,承载"必须可靠送达"的元数据变更observability_publisher_:可观测数据,允许丢弃(日志丢了不影响正确性)
这个拆分很关键:控制面与可观测面物理隔离,避免 Dashboard 日志洪峰把关键元数据通知挤掉。
值得注意的是实现选型:注释(:60-61)写明是 “long-poll subscribe”,而不是 gRPC streaming。长轮询的好处是穿透代理友好、实现简单、断线重连逻辑朴素可靠;代价是通知有额外一轮 RTT 延迟。对于"节点加入/Actor 状态变更"这种低频但关键的事件,这是合理的取舍。
5.9 Actor:调度权在 GCS 手里(与 Task 完全不同)
这是本篇最重要、也最容易被误解的一点。
第 2 篇讲过 Task 的路径:f.remote() → 本地 Raylet → 本地决策或 spillback。Task 的调度权在 Raylet。
Actor 不是。Actor 创建请求先到 GCS,由 GCS 向 Raylet 租 worker:
| 步骤 | 方法 | 位置 |
|---|---|---|
| 注册 Actor(写表) | HandleRegisterActor | src/ray/gcs/actor/gcs_actor_manager.h:122 |
| 创建 Actor | HandleCreateActor | :131 |
| 调度 pending actors | SchedulePendingActors | :166 |
| 调度单个 actor | GcsActorScheduler::Schedule | src/ray/gcs/actor/gcs_actor_scheduler.h:136 |
| 向指定节点租 worker | LeaseWorkerFromNode | :238 |
| 租约被授予 | HandleWorkerLeaseGrantedReply | :275 |
| 租约被拒绝(重试) | HandleWorkerLeaseRejectedReply | :281 |
| 在 worker 上创建 actor | CreateActorOnWorker | :300 |
| 创建成功/失败 | OnActorCreationSuccess / OnActorSchedulingFailed | src/ray/gcs/actor/gcs_actor_manager.h:214 / :203 |
为什么 Actor 要 GCS 调度? 因为 Actor 是有状态且需要全局唯一的实体:
- 它的位置一旦确定,所有调用方都得知道去哪找它(需要写进
actor_table_) - 它可能需要被重建(节点宕机后)——只有持有元数据的 GCS 能做这件事
- detached actor 的生命周期与 owner 解耦,需要 GCS 托管(
PollOwnerForActorRefDeleted,:302)
这也解释了官方"GCS 恢复期间 Actor 创建/删除/重建不可用"——Actor 的调度权就在 GCS,GCS 不在,Actor 就生不出来。
反过来,Task 无状态、可 spillback、位置不需要被记住,所以完全下放给 Raylet 是合理的。Ray 把调度权按"状态是否需要被记住"来切分,这是理解其架构的一条主线。
Actor 相关的其它 GCS 入口:HandleGetActorInfo(:135)、HandleGetNamedActorInfo(:139,具名 actor)、HandleListNamedActors(:143)、HandleKillActorViaGcs(:151)、HandleReportActorOutOfScope(:155)、RestartActor(:339)、DestroyActor(:317)、HandleRestartActorForLineageReconstruction(:126,lineage 重建入口,第 6 篇详述)。
5.10 PlacementGroup:GCS 用自己的调度器做全局装箱
PlacementGroup(PG)是"预留一组资源"的原语,其调度同样在 GCS:
| 方法 | 行号 | 作用 |
|---|---|---|
HandleCreatePlacementGroup | src/ray/gcs/gcs_placement_group_manager.h:76 | 创建 PG |
RegisterPlacementGroup | :115 | 注册(写表) |
SchedulePendingPlacementGroups | :121 | 调度待定 PG |
OnPlacementGroupCreationSuccess / Failed | :144 / :136 | 结果回调 |
HandleWaitPlacementGroupUntilReady | :95 | 等待就绪 |
RemovePlacementGroup | :148 | 移除 |
OnNodeDead / OnNodeAdd | :155 / :161 | 节点变化响应 |
CleanPlacementGroupIfNeededWhenJobDead | :183 | Job 死亡清理 |
CleanPlacementGroupIfNeededWhenActorDead | :199 | Actor 死亡清理 |
调度执行者是 gcs_placement_group_scheduler_(src/ray/gcs/gcs_server.h:284),它依赖 raylet_client_pool_(源码注释明确写了这个依赖)。
而它做决策时用的工具,就是 GcsServer 自己那份 cluster_resource_scheduler_(src/ray/gcs/gcs_server.h:268)——GCS 侧的这份调度器是为 PG 的"全局装箱(bundle packing)"服务的,与 Raylet 那份(第 4 篇)用途不同。
PG 的装箱结果还要回写资源账本:GcsResourceManager::UpdatePlacementGroupLoad(src/ray/gcs/gcs_resource_manager.h:144)。
一句话总结 PG 与 Actor 的差异:Actor 是"找一个节点放一个实体",PG 是"在集群里划一块地"——后者天然需要全局视角,所以只能由 GCS 做。
5.11 GCS 与外界的通信:gRPC 与客户端池
GCS 对外暴露 gRPC 服务,端口与线程数来自 GcsServerConfig(src/ray/gcs/gcs_server.h:60-76):
| 字段 | 行号 | 说明 |
|---|---|---|
grpc_server_port / grpc_server_thread_num | :60 / :61 | 服务端口与线程数 |
redis_address / redis_port / redis_username / redis_password | :65-66 / :63-64 | Redis 后端连接信息 |
enable_redis_ssl / retry_redis | :67 / :68 | Redis 安全与重试 |
raylet_config_list | :74 | 下发给 Raylet 的配置列表 |
对外调用则通过客户端池(:264-267):raylet_client_pool_、worker_client_pool_,以及专用于拉取负载的 resource_load_pull_raylet_client_pool_。
RPC 线程数可调:gcs_server_rpc_server_thread_num(src/ray/common/ray_config_def.h:453)与 gcs_server_rpc_client_thread_num(:459)。大集群(数百节点)里 GCS 的 RPC 线程常常是隐性瓶颈,如果观察到 GCS CPU 打满但吞吐上不去,先看这两个值。
存储写入还有一个批量上限:maximum_gcs_storage_operation_batch_size(:444,默认 1000)——控制单次批量写存储的条数,避免大事务拖垮后端。
5.12 GCS 启动时序
GcsServer::Start()(src/ray/gcs/gcs_server.h:107)→ DoStart()(:149)内部按依赖顺序初始化各 manager。注释(:95-98)明确了生命周期:
1. Gcs server contains a lot of data member, gcs server outlives all of them.
2. Gcs table storage and all gcs managers share a lifetime, that starts from aDoStartcall toStop.
初始化方法清单(:152-230)本身就是一张启动流程图:
InitGcsNodeManager(:152) → InitGcsHealthCheckManager(:155) → InitIOContextMonitor(:159)
→ InitGcsResourceManager(:162) → InitRaySyncer(:165) → InitClusterResourceScheduler(:168)
→ InitGcsJobManager(:171) → InitGcsActorManager(:178) → InitGcsPlacementGroupManager(:183)
→ InitGcsWorkerManager(:198) → InitGcsTaskManager(:201) → InitGcsAutoscalerStateManager(:206)
→ InitGcsResourceLoadPuller(:209) → InitUsageStatsClient(:212)
→ InitKVManager(:215) → InitKVService(:218) → InitFunctionManager(:221)
→ InitPubSubHandler(:224) → InitRuntimeEnvManager(:227) → InitMetricsExporter(:230)
→ InstallEventListeners(:233)
其中 InitIOContextMonitor(:159)与成员 io_context_monitor_thread_(:330)值得一提:它在一个独立线程上探测 GCS 的 io_context 是否卡死,并把结果反馈到 gRPC health check(SERVING / NOT_SERVING)。这是 GCS 的"自我心跳"——GCS 进程还在但事件循环卡住时,外部能感知到。
还有 GetOrGenerateClusterId(:249):从存储里读 cluster id,读不到就生成并持久化,且要求幂等。这也是为什么 kv_manager_(:288)要在存储层就绪后才能初始化。
5.13 Job、Task 事件与内部 KV
除核心三件套(节点/Actor/PG)外,GCS 还托管若干元数据服务:
- Job:
gcs_job_manager_(src/ray/gcs/gcs_server.h:289),表为job_table_ - Task 事件:
gcs_task_manager_(:309),配task_events_reported/dropped/stored三个 metric(初始化处:201-203)——注意有 dropped 计数,说明任务事件允许丢弃(可观测数据,非关键路径) - 内部 KV:
kv_manager_(:288),接口InternalKVInterface(src/ray/gcs/gcs_kv_manager.h:34),实现GcsInternalKVManager(:103)。用于 RuntimeEnv URI 存储、cluster id 持久化等 - RuntimeEnv:
runtime_env_manager_(src/ray/gcs/gcs_server.h:287)与runtime_env_handler_(:303) - 函数管理:
function_manager_(:285,InitFunctionManager:221)
5.14 生产实践(Production)
必须知道的三件事
- 默认不容错:
gcs_storage默认"memory"(src/ray/common/ray_config_def.h:488)。生产集群如果没显式配置,GCS 一挂整个集群就废。这是 Ray 部署里最常见的高可用缺口。 - 恢复期节点会自杀:Raylet 重连 GCS 超过
gcs_rpc_server_reconnect_timeout_s(默认 60s)就退出。大集群 + RocksDB 恢复慢时,务必调大这个阈值,否则会出现"GCS 恢复了但节点全没了"。 - RocksDB 是单写者:同一存储路径同一时刻只能有一个 GCS 打开。绝不能在两个集群间共享同一个
RAY_gcs_storage_path。
后端选型(对齐官方表格)
| 维度 | External Redis | Embedded RocksDB |
|---|---|---|
| 额外组件 | 需要 HA Redis | 无(本地持久化卷) |
| 存活条件 | Redis 存活 | 持久卷存活且能重新挂载 |
| 平台 | 全平台 | 仅 Linux |
| 成熟度 | 正式支持(KubeRay + Ray Serve) | Alpha |
| 源码 | REDIS_PERSIST(src/ray/gcs/gcs_server.h:133) | ROCKSDB_PERSIST(:134) |
观测清单
| 看什么 | 说明 |
|---|---|
task_events_dropped 计数 | 持续增长说明 GCS 处理不过来,任务事件在被丢弃 |
| GCS RPC 线程 | gcs_server_rpc_server_thread_num(:453)打满 → GCS 成为瓶颈 |
HandleGetAllResourceUsage 延迟 | GCS 侧资源视图落后于实际,会导致调度误判 |
| PlacementGroup 创建延迟 | InitGcsPlacementGroupManager 处注册了 latency histogram(src/ray/gcs/gcs_server.h:188-189) |
| GCS 事件循环 | io_context_monitor_thread_(:330)→ gRPC health 状态 |
| Actor 调度失败 | OnActorSchedulingFailed(src/ray/gcs/actor/gcs_actor_manager.h:203)是排查 actor 起不来的第一入口 |
典型故障排查
- 新节点加不进集群 →
HandleRegisterNode(src/ray/gcs/gcs_node_manager.h:71)是否被 GCS 接收;GCS 是否在恢复期;gcs_rpc_server_reconnect_timeout_s是否过小。 - Actor 创建卡住 → 走 GCS 路径:
SchedulePendingActors(src/ray/gcs/actor/gcs_actor_manager.h:166)→LeaseWorkerFromNode(src/ray/gcs/actor/gcs_actor_scheduler.h:238)→ 是否被HandleWorkerLeaseRejectedReply(:281)反复拒绝(通常是资源不足或 RuntimeEnv 起不来)。 - 具名 Actor 找不到 →
HandleGetNamedActorInfo(src/ray/gcs/actor/gcs_actor_manager.h:139),注意 namespace 隔离。 - GCS 重启后状态丢失 → 确认
gcs_storage不是memory;Redis 模式下确认RAY_REDIS_ADDRESS生效。 - PG 一直 pending →
SchedulePendingPlacementGroups(src/ray/gcs/gcs_placement_group_manager.h:121);装箱失败通常是资源碎片或策略过严(第 7 篇详述)。
5.15 进阶(Advanced):GCS 为什么不做调度,以及与同类系统的对比
为什么 GCS 不调度普通 Task
从第 4 篇与本篇的对比可以反推出设计意图:
- Task 的量级是百万级,若每个都要问 GCS,GCS 立刻成为瓶颈
- Task 无状态、可 spillback,位置不需要被记住 → 不需要全局一致
- Actor/PG 的量级是万级以下,且位置/状态必须全局可见 → 必须走 GCS
判据是"状态是否需要被全局记住",而不是"重要不重要"。这是一条可以迁移到其它系统设计上的经验。
与同类"集群元数据服务"对比
| 维度 | Ray GCS | Kubernetes (etcd + apiserver) | ZooKeeper | HDFS NameNode |
|---|---|---|---|---|
| 状态模型 | 6 张 KV 表 | 通用 KV + 对象模型 | 层次化 znode | 文件系统元数据树 |
| 一致性 | 单点权威(可选持久化) | Raft 强一致 | Zab 强一致 | 单点 + JournalNode |
| 高可用 | Redis/RocksDB 持久化重启 | 多副本 Raft | 多副本 | HA(JournalNode + ZKFC) |
| 参与调度 | 是(Actor / PG) | 是(scheduler 读 apiserver) | 否 | 否 |
| 故障时数据面 | 继续运行 | 已运行 Pod 继续 | 视业务而定 | 读写中断 |
GCS 最大的特点是单点 + 可选持久化,而不是 Raft 多副本。它用"数据面不受影响 + 快速重启恢复"来替代强一致多副本的复杂度。这个取舍在 AI 负载场景下是合理的(任务失败可重试、重算代价低于维护强一致的开销),但对控制面可用性要求极高的场景(如长期运行的在线服务)就需要 KubeRay 的 GCS FT 方案来补强。
5.16 小结与下篇预告
本篇把 GCS 拆成了五层:状态层(6 张表 / 3 种后端)、管理层(节点/资源/Actor/PG/Job/Task 各 manager)、同步层(RaySyncer + 心跳 + 负载拉取)、通知层(控制面 / 可观测面双 pubsub,长轮询)、服务层(gRPC + 客户端池 + 健康自监测)。
三条最值得记住的结论:
- GCS 的全部记忆就是 6 张表(
src/ray/gcs/gcs_table_storage.h:200),持久化只是换StoreClient——没有第二种机制,也意味着恢复期所有依赖表的 manager 都不可用。 - 调度权按"状态是否需被记住"切分:Task 归 Raylet,Actor 与 PlacementGroup 归 GCS(
src/ray/gcs/actor/gcs_actor_scheduler.h:238/src/ray/gcs/gcs_placement_group_manager.h:121)。 - 默认配置下 GCS 不容错(
gcs_storage="memory",src/ray/common/ray_config_def.h:488),且恢复超时会导致节点连锁退出(gcs_rpc_server_reconnect_timeout_s=60,:549)——这是生产部署最容易踩的坑。
下一篇(06 容错与 Lineage)将沿着本篇埋下的三条线索展开:actor_task_spec_table_(src/ray/gcs/gcs_table_storage.h:224)保存的创建参数如何支撑 Actor 重建、HandleRestartActorForLineageReconstruction(src/ray/gcs/actor/gcs_actor_manager.h:126)如何触发 lineage 重建、以及对象侧的 src/ray/core_worker/object_recovery_manager.h:108 与 src/ray/core_worker/reference_counter.h:159 如何与 GCS 协作完成容错闭环。
关键源码事实:
GcsServer:src/ray/gcs/gcs_server.h:99;Start/Stop:107/:110;DoStart:149;StorageType:130;存储常量:137-139;成员声明顺序约束:255-256- 初始化方法族:
InitGcsNodeManager:152、InitGcsHealthCheckManager:155、InitGcsResourceManager:162、InitRaySyncer:165、InitGcsActorManager:178、InitGcsPlacementGroupManager:183、InitGcsWorkerManager:198、InitGcsTaskManager:201、InitGcsResourceLoadPuller:209、InitKVManager:215、InitPubSubHandler:224- 核心成员:
cluster_resource_scheduler_:268、gcs_table_storage_:269、gcs_resource_manager_:271、resource_load_puller_:273、gcs_publisher_:275、gcs_node_manager_:279、gcs_actor_manager_:282、gcs_placement_group_scheduler_:284、kv_manager_:288、ray_syncer_:295、gcs_task_manager_:309、io_context_monitor_thread_:330- 6 张表:
src/ray/gcs/gcs_table_storage.h:200(访问器:214/:219/:224/:229/:234/:239)- 节点管理:
src/ray/gcs/gcs_node_manager.h:71/:112/:122/:184/:196;健康检查src/ray/gcs/gcs_health_check_manager.h:74/:92/:109- 资源视图:
src/ray/gcs/gcs_resource_manager.h:66/:127/:136/:150- Actor:
src/ray/gcs/actor/gcs_actor_manager.h:122/:166/:203/:214/:339;调度器src/ray/gcs/actor/gcs_actor_scheduler.h:136/:238/:275/:281/:300- PlacementGroup:
src/ray/gcs/gcs_placement_group_manager.h:76/:121/:144/:155- pubsub:
src/ray/gcs/pubsub_handler.h:43/:47/:62/:95- 配置:
src/ray/common/ray_config_def.h:444/:453/:459/:488/:492/:501/:508/:549

282

被折叠的 条评论
为什么被折叠?



