7.4 KiB
7.4 KiB
Location Resolver Shared Pipeline Plan
状态:已实现,当前用户流程见 Earth 位置候选采集使用手册,开发接口见 通用位置估算管线开发说明。
Goal
把"给定一条记录,决定它的 lat/lon"这件事抽象成一条统一的可插拔管线,让算力中心、BGP 观测站、BGP 事件——以及未来任何需要位置估算的实体——共用同一套接口。新算法(peeringdb 设施查询、IXP 表、用户认领的精确点位等)通过实现一个 Resolver 类即可挂入,不需要改任何上层调用方。
Background
实施前现状
- 算力中心 (
backend/app/services/compute_center_locations.py) 早期曾使用源坐标 → 本地 JSON 注册表 → 城市兜底 → Nominatim 在线地理编码。后续为避免硬编码位置污染事实链路,算力中心本地注册表已移除;主地图只使用源坐标,手动候选采集使用 ROR 和 Nominatim。 - BGP 观测站 (
collectors/bgp_common.py:RIPE_RIS_COLLECTOR_COORDS) 是一张写死的字典,26 个 RIPE RIS collector 的城市级坐标。新增 collector / 升级到设施级精度都得改 Python。 - BGP 事件继承所属 collector 的城市级坐标(
BGPObservation.collector_geo)。 - 用户原本以为 BGP 观测站位置是通过 iptoasn 推断的——其实 iptoasn 只用于前缀级国家归属(
bgp_enrichment.py),不影响 marker 坐标。
痛点
- 算力中心那条 4 层链路写死在算力中心模块里,BGP 想用得复制一遍。
- 三类实体各走各的坐标策略,缺统一抽象。
- 未来要插更精的算法(peeringdb / IXP / 用户认领),现在没有挂入点。
Design
接口契约
backend/app/services/location/:
models.py——LocationQuery(输入)、LocationCandidate(候选)、ResolverOutput(单 resolver 输出)、ResolutionResult/ResolutionDiagnostic(管线最终结果)pipeline.py——LocationResolverProtocol、LocationPipeline编排器resolvers/source_coordinates.py—— 记录自带 lat/lon 时直通resolvers/registry.py—— 本地 JSON 注册表(locations + city_fallbacks),按别名得分resolvers/nominatim.py—— 通用 Nominatim 客户端(rate-limited + LRU 缓存)+ 可注入 query planresolvers/inherit.py—— 从外部回调取候选(事件继承 collector 用)text.py—— 文本规范化共享工具
核心 Protocol:
class LocationResolver(Protocol):
name: str
def resolve(self, query: LocationQuery) -> ResolverOutput: ...
LocationPipeline.collect_candidates() 跑全部 resolver,聚合所有候选,按 (source_rank, precision_rank, -confidence) 排序去重;resolve_best() 选 top 候选。
各领域管线
# compute_center_locations.py(重构后,公共 API 不变)
COMPUTE_CENTER_PIPELINE = LocationPipeline([
SourceCoordinatesResolver(),
])
COMPUTE_CENTER_COLLECTION_PIPELINE = LocationPipeline([
SourceCoordinatesResolver(),
ROROrganizationResolver(),
NominatimResolver(query_plan_builder=_compute_center_query_plan,
geocoder=lambda q: _geocode_online(q)),
])
# bgp_collector_locations.py(新)
BGP_COLLECTOR_PIPELINE = LocationPipeline([
SourceCoordinatesResolver(),
StoredCollectorLocationResolver(),
])
BGP_COLLECTOR_COLLECTION_PIPELINE = LocationPipeline([
SourceCoordinatesResolver(),
NominatimResolver(query_plan_builder=_bgp_collector_query_plan,
geocoder=lambda q: _geocode_online(q)),
])
# bgp_event_locations.py(新)
BGP_EVENT_PIPELINE = LocationPipeline([
SourceCoordinatesResolver(),
InheritFromAnotherEntityResolver(source_lookup=_inherit_from_owning_collector),
# 占位:将来插 ASNFacilityResolver / PrefixGeoResolver
])
关键设计决策
- 算力中心公共 API 完全不变:
resolve_compute_center_location()、collect_location_candidates()、ComputeCenterLocationdataclass、_geocode_online模块级符号都保留,前端 / 上层调用方零改动;现有 19 个回归测试全绿。 _geocode_online用 lambda 晚绑定:NominatimResolver(geocoder=lambda q: _geocode_online(q))能让测试monkeypatch.setattr(module, "_geocode_online", fake)继续生效。RIPE_RIS_COLLECTOR_COORDS自动从 DB-backed cache 重建:启动时 seed/refreshbgp_collector_locations维表,再原地刷新旧{rrcXX → {city, country, lat, lon}}字典。下游消费者(bgp_collectors.py、序列化、detector)不动即可获得新元数据。- 修复隐藏 bug:BGP collector 不再通过 registry/operator 模糊匹配晋升候选,避免
operator="RIPE NCC"让每个事件都落到rrc00。 - 事件继承走严格名字查询:事件继承不跑 collector 的完整 pipeline,改成直接查 DB-backed cache。"改进位置"用户触发流程只跑源坐标和在线地理编码候选。
Files
新增
backend/app/services/location/__init__.pybackend/app/services/location/models.pybackend/app/services/location/pipeline.pybackend/app/services/location/text.pybackend/app/services/location/resolvers/__init__.pybackend/app/services/location/resolvers/source_coordinates.pybackend/app/services/location/resolvers/registry.pybackend/app/services/location/resolvers/nominatim.pybackend/app/services/location/resolvers/inherit.pybackend/app/services/bgp_collector_locations.pybackend/app/services/bgp_event_locations.pybackend/app/models/bgp_collector_location.pybackend/tests/test_location_pipeline.py(16 用例)backend/tests/test_bgp_collector_locations.py(11 用例)
修改
backend/app/services/compute_center_locations.py—— 改为薄包装backend/app/services/collectors/bgp_common.py—— 删除写死字典,改调resolve_bgp_event_geo_dict()backend/app/api/v1/bgp.py—— 新增POST /api/v1/bgp/collectors/{collector_id}/collect-locationfrontend/public/earth/js/info-card.js——renderComputeCenterCollectSection→renderLocationCollectSection,BGP collector 走通用化路径frontend/public/earth/js/compute-centers.js—— 新增通用collectLocationCandidates(endpoint, payload)frontend/public/earth/js/main.js——previewComputeCenterCandidate→previewLocationCandidate,事件名改为earth:preview-location-candidate
Verification
uv run pytest backend/tests/test_visualization_compute_centers.py—— 19 个用例全绿(公共 API 未改)uv run pytest backend/tests/test_location_pipeline.py backend/tests/test_bgp_collector_locations.py—— 16 + 11 用例全绿- 抽象可插拔性测试:
test_pluggability_custom_resolver_works_without_changing_pipeline—— 临时实现_PeeringDBStubResolver直接接入LocationPipeline,验证管线不需要改一行就能识别新 source
Out of scope
- 持久化用户认领的精确坐标(写回 JSON 注册表)——
suggested_registry_entry字段已就绪,工作流单独立项 - 真正实现
ASNFacilityResolver/PrefixGeoResolver—— 接口已留好,具体算法(peeringdb / IXP 表 / iptoasn 升级)单独立项 - 算力中心 / 观测站 marker 合并避让 —— 上一轮已用
SURFACE_AVOIDANCE_PROFILES.city+ halo 收敛解决