Prhub

#2049 Add a worker provider abstraction over RayWorkerManager

原始 PR 作者 fzyzcjy 合并时间 2026-09-04 13:50 文件变更 6 提交数 1 评论 1 代码增减 +109 / -1

执行摘要

引入工作者提供者抽象层,封装 RayWorkerManager 的地址查询逻辑。

该 PR 是 Issue #1837 “重构与增强跟踪”中的一个环节。其核心动机是引入一层抽象(BaseWorkerProvider),使得上层代码可以通过统一接口(get_addr)获取工作者的网络地址,而不必直接依赖 RayWorkerManager 这个具体实现。这为未来支持非 Ray 的工作者管理机制、或更复杂的地址解析逻辑(如缓存、负载均衡)铺平了道路,是实现更灵活、可测试架构的关键一步。

此 PR 值得精读,特别是对于关注软件架构和可测试性设计的工程师。它清晰地展示了如何通过引入抽象层来解耦具体实现、为未来扩展预留空间,并配合高质量的测试来保证行为正确性。其设计决策(如地址不被缓存、使用 create 工厂方法)都值得学习。建议结合关联的 Issue #1837 和后续 PR #2343、#2649 一起阅读,以理解完整的架构演进蓝图。

讨论亮点

唯一的一条 review 评论由 guapisolo 提供,提供了重要的历史上下文和集成考量:

  • 结论与状态:该评论指出本 PR(#2049)的抽象层在后续 PR #2343 中得到了完善,并且截至合并检查点 #2649 仍保持不变。因此,评论者判定本 PR 在其创建时是完整的,并予以批准。
  • 历史语境:评论提及本 PR 的抽象在合并检查点 #2176(safe-merge)之前并不独立安全,这暗示了它与其他重构 PR(可能涉及同一架构演进链)存在依赖关系或潜在冲突,体现了大型重构中协调的重要性。

实现拆解

  1. 定义抽象接口:新增 miles/utils/workers/worker_provider/base.py,定义 BaseWorkerProvider 抽象基类及其 get_addr 异步抽象方法,确立工作者地址查询的契约。
  2. 实现 Ray 具体提供者:新增 miles/utils/workers/worker_provider/ray.py,实现 RayWorkerProvider 类。其 create 类方法通过 RayWorkerManager.get_handle() 获取 Ray actor 句柄,get_addr 方法则代理调用该句柄的 get_worker_addr 远程方法。
  3. 适配现有管理器:修改 miles/utils/workers/ray_worker_manager.py,将 instance() 方法重命名为语义更清晰的 get_handle()。同时新增一个 get_worker_addr 实例方法(目前为 raise NotImplementedError),作为 RayWorkerProvider 调用的远程端点存根,为后续具体实现预留接口。
  4. 创建包结构:新增 miles/utils/workers/worker_provider/__init__.py 和 tests/fast/utils/workers/worker_provider/__init__.py,建立清晰的包结构。
  5. 配套测试覆盖:新增 tests/fast/utils/workers/worker_provider/test_ray.py,通过模拟 RayWorkerManager 句柄和 ray.get_actor 调用,验证 RayWorkerProvider 的创建过程和 get_addr 行为,包括不缓存地址的关键设计点。
文件 模块 状态 重要度
miles/utils/workers/worker_provider/ray.py 工作者提供者 added 8.17
tests/fast/utils/workers/worker_provider/test_ray.py 单元测试 added 7.55
miles/utils/workers/ray_worker_manager.py 工作者管理器 modified 6.97
miles/utils/workers/worker_provider/base.py 工作者提供者 added 6.79

关键符号

RayWorkerProvider.__init__ RayWorkerProvider.create RayWorkerProvider.get_addr BaseWorkerProvider.get_addr RayWorkerManager.get_handle RayWorkerManager.get_worker_addr

关键源码片段

miles/utils/workers/worker_provider/ray.py core-logic

核心变更:实现了 `RayWorkerProvider`,是新抽象层的具体体现,负责通过 Ray actor 进行工作者地址查询。

import logging
​
import ray.actor
​
from miles.utils.workers.ray_worker_manager import RayWorkerManager
from miles.utils.workers.worker_provider.base import BaseWorkerProvider
from miles.utils.workers.worker_spec import HostAndPort
​
logger = logging.getLogger(__name__)
​
POLL_INTERVAL_SECONDS = 5.0 # 预留常量,可能用于未来的轮询机制
​
​
class RayWorkerProvider(BaseWorkerProvider):
    """基于 Ray WorkerManager 的工作者提供者实现。"""
​
    def __init__(self, worker_manager_handle: ray.actor.ActorHandle):
        """内部初始化,通常通过 `create` 类方法调用。"""
        self._worker_manager_handle = worker_manager_handle
​
    @classmethod
    def create(cls) -> "RayWorkerProvider":
        """工厂方法:从已知的 Ray Actor 名称查找 WorkerManager 并创建提供者。"""
        # 通过静态方法获取管理器的 actor 句柄,实现了提供者与管理器获取方式的解耦。
        return cls(worker_manager_handle=RayWorkerManager.get_handle())
​
    async def get_addr(self, worker_name: str) -> HostAndPort:
        """异步获取指定名称工作者的网络地址。"""
        # 委托给底层的 Ray WorkerManager actor 执行实际查询。
        # 返回类型为 HostAndPort,是一个定义了 host 和 port 的简单数据类。
        return await self._worker_manager_handle.get_worker_addr.remote(worker_name)
tests/fast/utils/workers/worker_provider/test_ray.py test-coverage

关键测试:全面验证了 `RayWorkerProvider` 的创建和地址查询行为,特别是验证了“地址不缓存”的设计。

# 测试通过模拟 Ray actor 句柄来验证 RayWorkerProvider 的行为。
​
import pytest
from miles.utils.workers.worker_provider.ray import RayWorkerProvider
from miles.utils.workers.worker_spec import HostAndPort
​
​
class TestRayWorkerProviderCreate:
    async def test_looks_addresses_up_through_the_named_manager_actor(
        self, monkeypatch: pytest.MonkeyPatch
    ):
        """验证提供者能通过已知的 actor 名称找到管理器。"""
        # 模拟 RayWorkerManager.get_handle() 返回的 actor 句柄
        handle = _make_handle(HostAndPort(host="10.0.0.7", port=15000))
        looked_up: list[str] = []
​
        # 模拟 ray.get_actor 函数,记录被查询的 actor 名称
        class _FakeRay:
            @staticmethod
            def get_actor(name: str) -> _FakeManagerHandle:
                looked_up.append(name)
                return handle
​
        # 替换 RayWorkerManager 模块中的 ray 对象为我们的模拟实现
        import miles.utils.workers.ray_worker_manager as ray_worker_manager_mod
        monkeypatch.setattr(ray_worker_manager_mod, "ray", _FakeRay)
​
        provider = RayWorkerProvider.create()
        addr = await provider.get_addr(worker_name="router-0-0")
​
        # 断言:查询了正确的 actor 名称,并返回了正确的地址
        assert looked_up == ["ray_worker_manager"]
        assert addr == HostAndPort(host="10.0.0.7", port=15000)
​
​
class TestRayWorkerProviderGetAddr:
    async def test_every_lookup_asks_the_manager_again(self):
        """验证每次地址查询都直接调用管理器,不进行缓存。"""
        # 模拟管理器两次返回不同端口的地址
        handle = _make_handle(
            HostAndPort(host="10.0.0.7", port=15000),
            HostAndPort(host="10.0.0.7", port=15001),
        )
        provider = RayWorkerProvider(worker_manager_handle=handle)
​
        first = await provider.get_addr(worker_name="router-0-0")
        second = await provider.get_addr(worker_name="router-0-0")
​
        # 断言:两次查询得到了不同的端口,证明没有缓存
        assert (first.port, second.port) == (15000, 15001)
miles/utils/workers/ray_worker_manager.py core-logic

适配性修改:为配合新抽象层,将 `instance` 重命名为 `get_handle`,并添加了 `get_worker_addr` 存根方法,是架构迁移的必要步骤。

import ray
​
from miles.utils.workers.worker_spec import HostAndPort
​
# TODO: unique name, maybe with args.run_uuid
_ACTOR_NAME = "ray_worker_manager"
​
​
class RayWorkerManager:
    @staticmethod
    def launch(args, specs, pgs):
        return ray.remote(RayWorkerManager).options(name=_ACTOR_NAME).remote()
​
    @staticmethod
    def get_handle() -> ray.actor.ActorHandle:
        """重命名自 `instance()`,语义更明确:获取管理器 actor 的句柄。"""
        return ray.get_actor(_ACTOR_NAME)
​
    def get_worker_addr(self, worker_name: str) -> HostAndPort:
        """新添加的方法:作为 RayWorkerProvider.get_addr() 的远程调用端点。"""
        # 当前为存根实现,抛出异常以明确表示此方法需要后续补充。
        raise NotImplementedError
    # TODO: implement

评论区精华

PR 上下文与集成考量 设计

Review 评论指出本 PR 是更大重构链(#1837)的一部分,其抽象层在后续 PR #2343 中得到了完善,并且在特定合并检查点(#2649)被验证为安全。

结论:本 PR 作为架构演进的骨架提交,其设计和代码在完整的重构链中是正确且必要的。 · 已解决

风险与影响

  1. 接口存根未实现:RayWorkerManager.get_worker_addr 方法目前直接抛出 NotImplementedError。这是有意为之的“先行骨架”,但意味着任何调用 RayWorkerProvider.get_addr 的代码在当前阶段都将失败。风险在于误调用,但鉴于该抽象层尚未在主流业务路径中被广泛使用,且有明确的 TODO 注释,当前风险可控。
  2. 抽象层引入的复杂性:引入 BaseWorkerProvider 和 RayWorkerProvider 增加了代码层次。对于简单的 RayWorkerManager 调用,多了一层间接性。但这是一个标准的设计模式权衡,旨在换取更好的可测试性和可扩展性。
  3. 测试覆盖的充分性:测试通过 mock 验证了核心交互路径,特别是验证了地址不被缓存(每次调用都查询管理器)的关键行为,这对于动态环境(如 worker 重启)至关重要。测试质量高,覆盖了主要的边界情况。
  1. 对开发者/团队:为工作者地址解析建立了新的、可扩展的入口点。后续实现新工作者管理器(例如,用于 Kubernetes 或其他编排器)或增强现有逻辑(如添加重试、缓存)时,只需实现或扩展 BaseWorkerProvider,而无需修改上层调用者。这符合开闭原则,提升了代码的可维护性。
  2. 对系统/功能:这是一个纯粹的内部架构重构,不直接改变用户可见的功能。其影响在于为未来的功能(如 PR #1842 等 RolloutManager 拆分和异步化工作)奠定了更坚实的基础,使得工作者管理更加模块化。
  3. 对依赖与集成:新增的包和类引入了新的依赖关系(RayWorkerProvider 依赖 RayWorkerManager)。但变更被严格限制在 miles/utils/workers/ 子系统内部,对外部模块的侵入性最小。
抽象层引入 接口存根方法

关联 Issue

#1837 Tracking issue for refactoring and enhancements

完整报告

参与讨论