执行摘要
- 一句话:引入工作者提供者抽象层,封装 RayWorkerManager 的地址查询逻辑。
- 推荐动作:此 PR 值得精读,特别是对于关注软件架构和可测试性设计的工程师。它清晰地展示了如何通过引入抽象层来解耦具体实现、为未来扩展预留空间,并配合高质量的测试来保证行为正确性。其设计决策(如地址不被缓存、使用
create 工厂方法)都值得学习。建议结合关联的 Issue #1837 和后续 PR #2343、#2649 一起阅读,以理解完整的架构演进蓝图。
功能与动机
该 PR 是 Issue #1837 “重构与增强跟踪”中的一个环节。其核心动机是引入一层抽象(BaseWorkerProvider),使得上层代码可以通过统一接口(get_addr)获取工作者的网络地址,而不必直接依赖 RayWorkerManager 这个具体实现。这为未来支持非 Ray 的工作者管理机制、或更复杂的地址解析逻辑(如缓存、负载均衡)铺平了道路,是实现更灵活、可测试架构的关键一步。
实现拆解
- 定义抽象接口:新增
miles/utils/workers/worker_provider/base.py,定义 BaseWorkerProvider 抽象基类及其 get_addr 异步抽象方法,确立工作者地址查询的契约。
- 实现 Ray 具体提供者:新增
miles/utils/workers/worker_provider/ray.py,实现 RayWorkerProvider 类。其 create 类方法通过 RayWorkerManager.get_handle() 获取 Ray actor 句柄,get_addr 方法则代理调用该句柄的 get_worker_addr 远程方法。
- 适配现有管理器:修改
miles/utils/workers/ray_worker_manager.py,将 instance() 方法重命名为语义更清晰的 get_handle()。同时新增一个 get_worker_addr 实例方法(目前为 raise NotImplementedError),作为 RayWorkerProvider 调用的远程端点存根,为后续具体实现预留接口。
- 创建包结构:新增
miles/utils/workers/worker_provider/__init__.py 和 tests/fast/utils/workers/worker_provider/__init__.py,建立清晰的包结构。
- 配套测试覆盖:新增
tests/fast/utils/workers/worker_provider/test_ray.py,通过模拟 RayWorkerManager 句柄和 ray.get_actor 调用,验证 RayWorkerProvider 的创建过程和 get_addr 行为,包括不缓存地址的关键设计点。
关键文件:
miles/utils/workers/worker_provider/ray.py(模块 工作者提供者;类别 source;类型 core-logic;符号 RayWorkerProvider, init, create, get_addr): 核心变更:实现了 RayWorkerProvider,是新抽象层的具体体现,负责通过 Ray actor 进行工作者地址查询。
tests/fast/utils/workers/worker_provider/test_ray.py(模块 单元测试;类别 test;类型 test-coverage;符号 _FakeRemoteMethod, _FakeManagerHandle, TestRayWorkerProviderCreate, TestRayWorkerProviderGetAddr): 关键测试:全面验证了 RayWorkerProvider 的创建和地址查询行为,特别是验证了“地址不缓存”的设计。
miles/utils/workers/ray_worker_manager.py(模块 工作者管理器;类别 source;类型 core-logic;符号 RayWorkerManager.get_handle, RayWorkerManager.get_worker_addr): 适配性修改:为配合新抽象层,将 instance 重命名为 get_handle,并添加了 get_worker_addr 存根方法,是架构迁移的必要步骤。
miles/utils/workers/worker_provider/base.py(模块 工作者提供者;类别 source;类型 core-logic;符号 BaseWorkerProvider, get_addr): 契约定义:定义了 BaseWorkerProvider 抽象基类,确立了工作者地址查询的接口,是整个抽象层的基石。
关键符号:RayWorkerProvider.init, RayWorkerProvider.create, RayWorkerProvider.get_addr, BaseWorkerProvider.get_addr, RayWorkerManager.get_handle, RayWorkerManager.get_worker_addr
关键源码片段
miles/utils/workers/worker_provider/ray.py
核心变更:实现了 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
关键测试:全面验证了 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
适配性修改:为配合新抽象层,将 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
评论区精华
唯一的一条 review 评论由 guapisolo 提供,提供了重要的历史上下文和集成考量:
风险与影响
- 风险:
- 接口存根未实现:
RayWorkerManager.get_worker_addr 方法目前直接抛出 NotImplementedError。这是有意为之的“先行骨架”,但意味着任何调用 RayWorkerProvider.get_addr 的代码在当前阶段都将失败。风险在于误调用,但鉴于该抽象层尚未在主流业务路径中被广泛使用,且有明确的 TODO 注释,当前风险可控。
- 抽象层引入的复杂性:引入
BaseWorkerProvider 和 RayWorkerProvider 增加了代码层次。对于简单的 RayWorkerManager 调用,多了一层间接性。但这是一个标准的设计模式权衡,旨在换取更好的可测试性和可扩展性。
- 测试覆盖的充分性:测试通过 mock 验证了核心交互路径,特别是验证了地址不被缓存(每次调用都查询管理器)的关键行为,这对于动态环境(如 worker 重启)至关重要。测试质量高,覆盖了主要的边界情况。
- 影响:
- 对开发者/团队:为工作者地址解析建立了新的、可扩展的入口点。后续实现新工作者管理器(例如,用于 Kubernetes 或其他编排器)或增强现有逻辑(如添加重试、缓存)时,只需实现或扩展
BaseWorkerProvider,而无需修改上层调用者。这符合开闭原则,提升了代码的可维护性。
- 对系统/功能:这是一个纯粹的内部架构重构,不直接改变用户可见的功能。其影响在于为未来的功能(如 PR #1842 等 RolloutManager 拆分和异步化工作)奠定了更坚实的基础,使得工作者管理更加模块化。
- 对依赖与集成:新增的包和类引入了新的依赖关系(
RayWorkerProvider 依赖 RayWorkerManager)。但变更被严格限制在 miles/utils/workers/ 子系统内部,对外部模块的侵入性最小。
- 风险标记:抽象层引入, 接口存根方法
关联脉络
- PR #1837 Tracking issue for refactoring and enhancements: 本 PR 是此重构跟踪 Issue 中明确列出的一个工作项(op2 链),属于更宏大的架构增强计划的一部分。
- PR #2343 (推测)后续完善 WorkerProvider 抽象的 PR: 根据 Review 评论,本 PR 引入的抽象层在此 PR 中得到完善。它是理解本 PR 最终状态和意图的关键后续步骤。
- PR #2649 (推测)安全合并检查点 PR: Review 评论提及本 PR 在 #2649 检查点被验证为完整,这是判断本 PR 代码有效性和独立性的重要参考点。
- PR #2176 (推测)安全合并检查点 PR: Review 评论指出在 #2176 检查点时,本 PR 的抽象尚不独立安全,这揭示了 PR 合并顺序与依赖关系的重要性。
参与讨论