Skip to content

refactor(dash_sc/proxy): switch forward addr config to SERVICE_ROUTE#1069

Open
sunmiaozju wants to merge 1 commit into
feat/dsv4_on_devfrom
feat/dash-sc-service-route
Open

refactor(dash_sc/proxy): switch forward addr config to SERVICE_ROUTE#1069
sunmiaozju wants to merge 1 commit into
feat/dsv4_on_devfrom
feat/dash-sc-service-route

Conversation

@sunmiaozju
Copy link
Copy Markdown
Collaborator

@sunmiaozju sunmiaozju commented Jun 5, 2026

Summary

  • Switch dash_sc proxy backend routing to typed SERVICE_ROUTE JSON object config with ip_port_list and vipserver.
  • Preserve compatibility by converting legacy DASH_SC_GRPC_FORWARD_ADDR into ip_port_list when SERVICE_ROUTE is unset.
  • Treat configured ip:port and vipserver ports as HTTP endpoints, resolve them into BackendAddr(ip, http_port, grpc_port), and use http_port + 8 as the gRPC target for GrpcHostChannelPool.
  • Enable proxy mode with DASH_SC_GRPC_PROXY_MODE=1, while preserving the legacy DASH_SC_GRPC_FORWARD_ADDR proxy-mode trigger.
  • Split service-route config parsing from runtime discovery, and add differentiated unavailable logs for discovery vs channel-cache failures.

Testing

  • git diff --check
  • python -B -m py_compile rtp_llm/dash_sc/proxy/__main__.py rtp_llm/dash_sc/proxy/service_route.py rtp_llm/dash_sc/proxy/service_route_config.py rtp_llm/dash_sc/test/proxy/servicer_test.py
  • python -B -m unittest rtp_llm.dash_sc.test.proxy.servicer_test.ServiceRouteConfigTest was attempted but the local Python environment is missing torch.

@sunmiaozju sunmiaozju requested a review from LLLLKKKK as a code owner June 5, 2026 03:23
Comment thread rtp_llm/dash_sc/proxy/servicer.py
Comment thread rtp_llm/dash_sc/proxy/service_route.py Outdated
@sunmiaozju sunmiaozju force-pushed the feat/dash-sc-service-route branch 10 times, most recently from 1c4ec5e to 8bc3560 Compare June 5, 2026 07:24
@LLLLKKKK
Copy link
Copy Markdown
Collaborator

LLLLKKKK commented Jun 5, 2026

AI Code Review - PR #1069

Status: BLOCKING

Summary: P0/1 · P1/2 · P2/2 · P3/1

Blocking Issues

P0

  • DASH_SC_GRPC_FORWARD_ADDR 语义变更导致连接错误端口 @ rtp_llm/dash_sc/proxy/service_route.py:44
    • 建议:legacy 路径应保持旧语义:parse_legacy_forward_addrs 产出的地址已经是 gRPC 端口,应直接构建 BackendAddr 时不加 offset。可新增 BackendAddr.from_grpc_target 工厂方法用于 legacy 路径,或在 IpPortListServiceDiscovery 接受 port_is_grpc 参数跳过 offset。若有意变更则需在 PR description 和迁移文档中明确说明。

P1

  • VipServerServiceDiscovery.resolve() 在 async 事件循环中执行同步阻塞 I/O @ rtp_llm/dash_sc/proxy/servicer.py:101
    • 建议:将 resolve() 调用包装为 await asyncio.to_thread(self._discovery.resolve),或在 DashScProxyServicer.open() 中预热缓存(同步调用一次 resolve 触发 cache fill),避免首个请求阻塞事件循环。
  • 删除 --forward_addr CLI 参数是对独立代理部署的破坏性变更 @ rtp_llm/dash_sc/proxy/__main__.py:19
    • 建议:保留 --forward_addr 参数并发出 DeprecationWarning,解析后写入 os.environ['DASH_SC_GRPC_FORWARD_ADDR'] 再构造 servicer。或在 PR description / changelog 中明确标注此 breaking change 并提供迁移指引。

Non-blocking Suggestions

P2

  • IpPortListServiceDiscovery.resolve() 从 round-robin 改为 random 选择导致负载不均 @ rtp_llm/dash_sc/proxy/service_route.py:69
    • 建议:使用 itertools.cycle 或原子计数器取模实现 round-robin,与旧行为一致。如果 random 是有意设计(如避免多 proxy 实例间锁步),应在注释或 PR 描述中说明设计理由。
  • DASH_SC_GRPC_PROXY_MODE=1 但未设置路由地址时错误信息不直观 @ rtp_llm/dash_sc/app.py:41
    • 建议:在 proxy 模式入口处提前校验路由配置是否存在,给出明确错误信息如 "DASH_SC_GRPC_PROXY_MODE=1 requires SERVICE_ROUTE or DASH_SC_GRPC_FORWARD_ADDR to be set"。

P3

  • _normalise_addrs 参数缺少类型标注 @ rtp_llm/dash_sc/proxy/service_route_config.py:109
    • 建议:改为 def _normalise_addrs(addrs: Iterable[str]) -> list[str]:,并在文件头添加 from collections.abc import Iterable。

Checklist Violations (7 fail / 63 total)

General Principles Checklist

  • [6.1] Architecture — 错误语义:fail-fast/retry/fallback/silent 行为显式 → issue DASH_SC_GRPC_PROXY_MODE=1 但未设置路由地址时错误信息不直观
    DASH_SC_GRPC_PROXY_MODE=1 但未设置路由地址时,错误从 load_service_route_config_from_env 抛出通用 RuntimeError,错误信息未提及 PROXY_MODE 标志,运维难以快速定位。
  • [6.1] Architecture — 兼容性:公开 API/持久数据/配置/环境迁移安全 → issue DASH_SC_GRPC_FORWARD_ADDR 语义变更导致连接错误端口
    DASH_SC_GRPC_FORWARD_ADDR 旧语义为直接 gRPC 端口连接,新代码将其解释为 HTTP 端口并加 +8 偏移,静默改变连接目标。main.py 的 --forward_addr CLI 参数被移除无 deprecation。

RTP-LLM Checklist

  • [A] 兼容性与配置 — 环境变量/命令行参数/proto 重命名有向后兼容 fallback → issue DASH_SC_GRPC_FORWARD_ADDR 语义变更导致连接错误端口
    DASH_SC_GRPC_FORWARD_ADDR 保留 fallback 读取但端口语义从「直接 gRPC 端口」变为「HTTP 端口 + 偏移 8」,破坏 fallback 的语义等价性。--forward_addr CLI 参数被直接移除。
  • [A] 兼容性与配置 — 分布式端口/地址 getter 与实际监听端口一致 → issue DASH_SC_GRPC_FORWARD_ADDR 语义变更导致连接错误端口
    BackendAddr.from_http_port 假设输入是 HTTP/base 端口并加 +8 得到 gRPC 端口。但 legacy DASH_SC_GRPC_FORWARD_ADDR 中的端口已经是 gRPC 端口(base+8),再加 8 后变成 base+16,与实际监听不一致。

Python Static-First Checklist

  • [P.C] 并发与异步 — async def 中禁止 blocking 调用 → issue VipServerServiceDiscovery.resolve() 在 async 事件循环中执行同步阻塞 I/O
    _servicer.py:101 在 async ModelStreamInfer 中同步调用 self.discovery.resolve()。VipServerServiceDiscovery.resolve() 内部可能执行同步 HTTP 请求(get_one_validate_host cache miss)和 threading.Lock 争抢,阻塞事件循环。
  • [P.H] 类型标注 — Any 必须附注释说明原因 → checklist-only
    service_route.py:17 VipHostResolver = Callable[[str], Optional[Any]] 和 :28 from_host(cls, host: Any) 使用 Any 但无注释。host 来自 vip_client 无类型定义的对象,Any 有合理理由但未标注。
  • [P.H] 类型标注 — 输入参数用 Sequence/Mapping/Iterable,返回值用具体类型 → issue _normalise_addrs 参数缺少类型标注
    _normalise_addrs(addrs) 参数完全缺少类型标注(应为 Iterable[str])。create_service_discovery_from_env() 缺少返回类型标注。

Strengths

  • 架构从定制 ForwardChannelPool 重构为服务发现 + 共享 GrpcHostChannelPool,删除 ~330 行复杂自定义 channel 池代码,显著降低维护成本
  • 配置解析(service_route_config)、地址发现(service_route)、代理逻辑(servicer)三层职责分离清晰,符合 SRP
  • VipServerServiceDiscovery 通过 resolver 回调参数注入实现了良好的可测试性,测试无需依赖真实 VipServer 服务
  • BackendAddr frozen dataclass 设计良好,端口校验完备(范围检查、类型转换、raise from 保留异常链),工厂方法覆盖多种构造场景
  • 测试新增全面:ServiceRouteConfigTest 覆盖配置解析和 legacy 转换,VipServer discovery 覆盖正常和异常路径,ChannelPoolTest 验证缓存复用

Replaces the env-only `DASH_SC_GRPC_FORWARD_ADDR` (single string /
comma-list / JSON array of ip:port) with a typed `SERVICE_ROUTE` JSON
object that names the discovery mechanism explicitly:

  {"type": "ip_port_list", "address": "10.0.0.1:8096;10.0.0.2:8096"}
  {"type": "vipserver",    "address": "com.example.svc"}

For compatibility, when `SERVICE_ROUTE` is unset the proxy still parses
legacy `DASH_SC_GRPC_FORWARD_ADDR` and converts it to an `ip_port_list`
route. `SERVICE_ROUTE` wins when both env vars are set.

Configured `ip:port` values are HTTP endpoints. Runtime discovery returns a
`BackendAddr` with `ip`, `http_port`, and `grpc_port`, where `grpc_port` is
`http_port + 8`. The proxy uses the resulting gRPC target for
`GrpcHostChannelPool` lookup; vipserver results follow the same conversion.

`vipserver` is resolved per request via `rtp_llm.vipserver.vip_client`
(lazy import so non-vipserver deployments don't pay for the global
HostReactor refresh thread). Resolver failures are logged and surfaced to
the proxy as an unavailable backend instead of being swallowed silently.

`SERVICE_ROUTE` only describes proxy backend routing. `DashScApp` enters
proxy mode when `DASH_SC_GRPC_PROXY_MODE=1`; for compatibility, a non-empty
legacy `DASH_SC_GRPC_FORWARD_ADDR` also still enables proxy mode.

Aone Req: https://aone.alibaba-inc.com/req/82761547
@sunmiaozju sunmiaozju force-pushed the feat/dash-sc-service-route branch from 8bc3560 to 4c61436 Compare June 5, 2026 14:29
@LLLLKKKK
Copy link
Copy Markdown
Collaborator

LLLLKKKK commented Jun 5, 2026

AI Code Review - PR #1069

Status: BLOCKING

Summary: P0/0 · P1/2 · P2/2 · P3/0

Blocking Issues

P1

  • 旧版 DASH_SC_GRPC_FORWARD_ADDR 端口语义向后不兼容 @ rtp_llm/dash_sc/proxy/service_route.py:44
    • 建议:在 legacy fallback 路径中,将地址视为 gRPC 端口(不加偏移),或新增 BackendAddr.from_grpc_target 工厂方法供 legacy 路径调用;或在文档/changelog 中明确说明 DASH_SC_GRPC_FORWARD_ADDR 语义从 gRPC 端口变为 HTTP base 端口并提供迁移指引。
  • VipServerServiceDiscovery.resolve() 同步调用可能阻塞 gRPC 异步事件循环 @ rtp_llm/dash_sc/proxy/servicer.py:101
    • 建议:将 resolve() 包装为 await loop.run_in_executor(None, self._discovery.resolve) 或提供异步版本 async_resolve(),避免在事件循环线程上执行潜在阻塞操作。

Non-blocking Suggestions

P2

  • IpPortListServiceDiscovery 从 round-robin 改为随机选择,行为变更未记录 @ rtp_llm/dash_sc/proxy/service_route.py:69
    • 建议:考虑恢复 round-robin 语义以保持负载均衡行为一致(如 itertools.cycle 或原子递增索引),或在 PR description 中记录切换到 random 的原因。
  • standalone proxy main.py 移除 --forward_addr 参数是 breaking change @ rtp_llm/dash_sc/proxy/__main__.py:19
    • 建议:保留 --forward_addr 参数并在解析后转换为 SERVICE_ROUTE 环境变量传入(deprecated shim),或在 PR description / changelog 中明确标注此 breaking change 及迁移方式。

Checklist Violations (10 fail / 73 total)

General Principles Checklist

  • [6.1] Software Engineering — DIP:高层策略不依赖非必要具体细节 → checklist-only
    servicer.py 直接调用 create_service_discovery_from_env() 获取具体实现,返回值无 Protocol/ABC 抽象,类型检查器无法验证 resolve() 调用。实际耦合度不高(仅用 resolve()),但缺少显式抽象接口。
  • [6.1] Architecture — 兼容性:公开 API/持久数据/配置/环境迁移安全 → issue 旧版 DASH_SC_GRPC_FORWARD_ADDR 端口语义向后不兼容
    DASH_SC_GRPC_FORWARD_ADDR 旧值语义从 gRPC 端口变为 HTTP 基础端口(+8 偏移),已有部署的端口值加偏移后连接到错误端口。

RTP-LLM Checklist

  • [A] 兼容性与配置 — 环境变量/命令行参数/proto 重命名有向后兼容 fallback → issue 旧版 DASH_SC_GRPC_FORWARD_ADDR 端口语义向后不兼容
    DASH_SC_GRPC_FORWARD_ADDR legacy 路径保留但端口语义变更:旧代码直接用作 gRPC target,新代码解释为 HTTP base 端口 + 偏移 8 = gRPC 端口。现有部署若设置的是 gRPC 端口值,会连接到错误端口。
  • [A] 兼容性与配置 — 默认值变更已评估对现有用户影响 → issue IpPortListServiceDiscovery 从 round-robin 改为随机选择,行为变更未记录
    负载均衡策略从确定性 round-robin 改为 random.randrange 随机选择,对少量后端场景可能造成短时负载不均,行为变更未在 PR 中记录。
  • [G] 跨语言框架陷阱 — 环境变量滥用,功能开关走命令行参数 → checklist-only
    新增 DASH_SC_GRPC_PROXY_MODE 环境变量用于功能开关。理论上应走命令行参数,但 dash_sc 进程由父进程 multiprocessing.spawn 启动,命令行参数传递不便,env var 是合理选择。SERVICE_ROUTE 作为部署配置用 env var 传递合理。

Python Static-First Checklist

  • [P.A] 静态结构与类型纪律 — 禁止 getattr/setattr literal 访问 → checklist-only
    _test helpers _stop_mock_stub (servicer_test.py) 使用 getattr(servicer, '_test_stub_patcher', None),_install_mock_stub 使用 servicer.test_stub_patcher = patcher。均为测试代码中对测试辅助属性的 monkey-patch,非生产代码。
  • [P.A] 静态结构与类型纪律 — 字符串分发用 Enum/Literal → checklist-only
    service_route_config.py 使用字符串常量 SERVICE_ROUTE_TYPE_IP_PORT_LIST/VIPSERVER 做 if/elif 分发而非 Enum/Literal。但因 JSON 输入天然是字符串,常量集中定义,解析时有显式校验(未知 type raise RuntimeError),实际风险可控。
  • [P.C] 并发与异步 — async def 中禁止 blocking 调用 → issue VipServerServiceDiscovery.resolve() 同步调用可能阻塞 gRPC 异步事件循环
    _servicer.py:101 在 async generator ModelStreamInfer 中同步调用 self.discovery.resolve()。VipServerServiceDiscovery.resolve() 内部调用 get_one_validate_host() 为同步 I/O,可能阻塞事件循环。
  • [P.H] 类型标注 — Any 必须附注释说明原因 → checklist-only
    VipHostResolver = Callable[[str], Optional[Any]] 和 BackendAddr.from_host(host: Any) 使用 Any 未附注释说明原因。host 来自 vipserver SDK 未类型化的返回值,应加注释说明来源。
  • [P.H] 类型标注 — 输入参数用 Sequence/Mapping/Iterable,返回值用具体类型 → checklist-only
    _service_route_config.py:109 normalise_addrs(addrs) 参数无类型标注,应标注为 Iterable[str];service_route.py:106 create_service_discovery_from_env() 无返回类型标注。

Strengths

  • 将服务发现(service_route.py)、配置解析(service_route_config.py)与代理逻辑(servicer.py)分离,职责单一,架构清晰
  • 复用共享 GrpcHostChannelPool 替代自建 ForwardChannelPool,减少约 330 行并发管理代码(channel recycling、in-flight tracking、retiring list),降低维护负担
  • 测试覆盖全面:config 解析、legacy 兼容、vipserver 发现、channel 缓存、端口偏移均有独立测试覆盖,旧测试全部适配到新架构
  • BackendAddr frozen dataclass 统一 HTTP/gRPC 端口转换逻辑,消除散布在各处的魔数
  • 旧版 DASH_SC_GRPC_FORWARD_ADDR 环境变量向后兼容 fallback 路径保留,支持平滑过渡

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants