Files
planet/docs/technical/zh/backend-collectors.md
rayd1o 5bf5c73ca0
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
release: bump version to 0.66.0
2026-05-26 03:41:47 +08:00

24 KiB
Raw Blame History

数据采集系统 (Collectors)

一、系统架构

┌─────────────────────────────────────────────────────────────────┐
│                        数据采集系统架构                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌─────────────┐    ┌─────────────┐    ┌─────────────┐       │
│   │  TOP500     │    │  Epoch AI   │    │ HuggingFace │       │
│   │  采集器      │    │  采集器      │    │  采集器      │       │
│   └──────┬──────┘    └──────┬──────┘    └──────┬──────┘       │
│          │                   │                   │              │
│          └───────────────────┼───────────────────┘              │
│                              ▼                                  │
│                  ┌─────────────────────┐                        │
│                  │   BaseCollector    │◄── 基类 (统一处理)      │
│                  │   run() 方法       │                        │
│                  └─────────┬───────────┘                        │
│                            │                                     │
│          ┌─────────────────┼─────────────────┐                   │
│          ▼                 ▼                 ▼                   │
│   ┌───────────┐    ┌───────────┐    ┌───────────┐              │
│   │ fetch()   │    │transform()│    │ _save_data│              │
│   │ 获取原始数据 │    │ 数据转换   │    │ 保存到DB  │              │
│   └───────────┘    └───────────┘    └───────────┘              │
│                              │                                  │
│                              ▼                                  │
│                  ┌─────────────────────┐                        │
│                  │  CollectedData 表   │◄── 统一存储            │
│                  └─────────────────────┘                        │
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │                    Scheduler (APScheduler)               │  │
│   │   定时任务调度: 每4小时/6小时/12小时/1天 自动执行        │  │
│   └─────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

二、工作流程 (Pipeline)

# 1. Scheduler 触发 (定时 或 手动触发)
#    ↓

# 2. run() 方法执行完整流水线
async def run(self, db):
    # 2.1 检查采集器是否启用
    if not collector_registry.is_active(self.name):
        return {"status": "skipped"}
    
    # 2.2 记录任务开始
    task = CollectionTask(status="running")
    db.add(task)
    await db.commit()
    
    # 2.3 FETCH - 获取原始数据 (由子类实现)
    raw_data = await self.fetch()
    
    # 2.4 TRANSFORM - 转换为统一格式
    data = self.transform(raw_data)
    
    # 2.5 SAVE - 保存到数据库
    records_count = await self._save_data(db, data)
    
    # 2.6 记录任务完成
    task.status = "success"
    task.records_processed = records_count
    await db.commit()

核心文件: backend/app/services/collectors/base.py

手动触发、删除数据、清理缓存现在统一进入 PostgreSQL 数据作业队列,任务账本仍是 collection_tasks。采集器只负责 fetch -> transform -> save,由 data_jobs.py worker 领取 collect / clear_data / clear_cache / earth_refresh 任务并回写进度。Earth 图层刷新关系集中在 earth_layer_adapters.py,不要再在单个采集器或按钮里手写缓存失效和 WebSocket 广播。

三、采集器列表

采集器 数据类型 数据内容 采集频率
TOP500 supercomputer 全球超级计算机排名 (算力、性能) 4小时
Epoch AI gpu_cluster GPU算力集群信息 6小时
HuggingFace Models model AI模型信息 12小时
HuggingFace Datasets dataset 数据集信息 12小时
HuggingFace Spaces space Demo应用 1天
PeeringDB ixp/network/facility 互联网交换点/网络/机房 1-2天
TeleGeography submarine_cable 海底光缆信息 7天
Space-Track TLE satellite_tle 卫星轨道 TLE 数据 依采集器配置
BarentsWatch AIS vessel 船只位置、航速、航向、MMSI 等 AIS 数据 依采集器配置
AISStream Vessels vessel_ais AIS WebSocket 实时流,写入原始观测层并由聚合接口展示 依采集器配置

AIS 船只类采集器和其它 CollectedData 采集器的落库路径不同。BarentsWatch、AISStream 和自定义 vessel_ais 源都会进入 AIS 原始观测层,随后由聚合服务合并成 Earth 船只图层使用的 GeoJSON 和详情数据。这样做可以保留来源、传输方式、字段冲突和观测时间,避免某个实时源直接覆盖最终展示表。

Earth 国界不再属于采集器体系。它是 Earth 静态渲染资产,由控制台 运维与配置 -> Earth 内容 -> 国界精度 维护源配置,并由 /api/v1/earth/boundaries/* 构建 frontend/public/earth/data/boundaries/earth-boundaries-china-pov-v1.pmtiles。本地没有高精 PMTiles 时,前端会使用仓库内置的低精度 GeoJSON 作为 fallback不会向 CollectedData 写入国界记录。

TOP500 和 Epoch AI 算力数据的公开源不总是提供可用经纬度。Earth 统一算力中心接口在主地图启动链路中只使用源数据自带坐标或 compute_center_locations 维表坐标;缺少坐标的记录会进入 unresolved,不会通过本地注册表、国家质心或猜测城市自动渲染。用户手动采集候选时,后端会用源字段调用 ROR 组织注册 API 和 Nominatim/OpenStreetMap 在线搜索;候选经前端保存后写入 compute_center_locations,后续地图刷新再从维表渲染。

四、数据格式 (统一存储到 CollectedData 表)

# 每个采集器 parse_response() 返回格式
{
    "source_id": "top500_1",          # 原始系统ID (必填)
    "name": "El Capitan",             # 名称 (必填)
    "description": "系统描述...",       # 描述
    "country": "United States",        # 国家
    "city": "Livermore, CA",           # 城市
    "latitude": "37.6819",            # 纬度 (字符串)
    "longitude": "-121.7681",         # 经度 (字符串)
    "value": "1742.00",               # 性能值 (如算力)
    "unit": "PFlop/s",                # 单位
    "metadata": {                      # 额外数据 (JSON)
        "rank": 1,
        "r_peak": 2746.38,
        "cores": 11039616
    },
    "reference_date": "2025-11-01"    # 数据参考日期
}

五、数据库表结构

CollectedData 表 (collected_data)

字段 类型 说明
id SERIAL 主键
source VARCHAR(100) 数据源名称 (top500, huggingface等)
source_id VARCHAR(100) 原始数据ID
data_type VARCHAR(50) 数据类型 (supercomputer, model等)
name VARCHAR(500) 名称
title VARCHAR(500) 标题
description TEXT 描述
country VARCHAR(100) 国家
city VARCHAR(100) 城市
latitude VARCHAR(50) 纬度
longitude VARCHAR(50) 经度
value VARCHAR(100) 性能值
unit VARCHAR(20) 单位
metadata JSONB 额外元数据
collected_at TIMESTAMP 采集时间
reference_date TIMESTAMP 数据参考日期
is_valid INTEGER 是否有效

核心文件: backend/app/models/collected_data.py

六、TOP500 采集器示例 (完整流程)

# 1. fetch() - 从网页获取HTML
async def fetch(self):
    url = "https://top500.org/lists/top500/list/2025/11/"
    response = await client.get(url)
    return response.text  # 返回HTML

# 2. parse_response() - 解析HTML为统一格式
def parse_response(self, html):
    soup = BeautifulSoup(html, "html.parser")
    table = soup.find("table")
    
    for row in table.find_all("tr")[1:]:  # 跳过表头
        cells = row.find_all("td")
        
        entry = {
            "source_id": f"top500_{cells[0].text}",      # "top500_1"
            "name": cells[1].text.strip(),               # "El Capitan"
            "country": cells[2].text.strip(),            # "United States"
            "city": "",                                   # 城市
            "latitude": "",                               # 需进一步解析
            "longitude": "",
            "value": "1742.00",                          # Rmax
            "unit": "PFlop/s",
            "metadata": {
                "rank": 1,
                "cores": "11340000"
            },
            "reference_date": "2025-11-01"
        }
        data.append(entry)
    
    return data

# 3. run() 自动调用 _save_data() 保存到数据库

核心文件: backend/app/services/collectors/top500.py

七、调度机制

# 启动时注册所有采集器到定时任务
def start_scheduler():
    for name, collector in collectors.items():
        if collector_registry.is_active(name):
            scheduler.add_job(
                run_collector_task,
                trigger=IntervalTrigger(hours=collector.frequency_hours),
                id=name,
                name=name
            )
采集器 采集频率
TOP500 每4小时
Epoch AI 每6小时
HuggingFace 每12小时
PeeringDB 每1-2天
TeleGeography 每7天

核心文件: backend/app/services/scheduler.py

成功采集与连接状态

内置采集器成功采集后,调度器会记录当前有效配置已通过连接验证:

if datasource.last_status == "success":
    effective_candidate = await get_builtin_effective_candidate(db, datasource.source)
    checksum, _ = await build_builtin_connectivity_checksum(...)
    await save_connectivity_success(
        db,
        datasource.source,
        checksum,
        {"status_code": None},
        connected_by="collection",
    )

这个记录用于控制台“采集管理 -> 采集器”中的连接状态判断:如果当前配置和成功采集时的 checksum 一致,就视为已连接,不要求用户再手动点击连接按钮。只有 endpoint、请求头、基础配置或凭证指纹变化时才需要重新验证。

采集管理与快照

Admin 的采集管理入口按业务层级组织:

  • 采集器:配置 endpoint、认证方式、请求头、基础参数、启用状态和凭证教程。
  • 采集调度:查看和调整调度状态,触发、停止或刷新采集任务。
  • 采集历史 / 快照:按采集器聚合展示历史,详情中用快照选择器查看同一采集器的不同版本。

快照列表不应把同一个采集器的每次快照都拍平成独立主列表项。主列表负责选择采集器,详情区负责时间版本切换。

凭证教程由 backend/app/services/credential_guides.py 维护,控制台通过 read / generate / reset 三个动作读取或生成 Markdown 教程。教程面向运维配置人员,前端应渲染 Markdown 正文,而不是展示生成 prompt 或原始元数据。

相关实现见:

八、相关代码文件

backend/app/services/collectors/
├── base.py              # 基类: run() 流水线, _save_data() 保存
├── registry.py          # 采集器注册表
├── scheduler.py         # 定时任务调度 (APScheduler)
├── top500.py            # TOP500采集器
├── epoch_ai.py          # Epoch AI采集器
├── huggingface.py       # HuggingFace采集器
├── peeringdb.py         # PeeringDB采集器
├── telegeraphy.py       # TeleGeography海底光缆采集器
├── vessel_ais.py        # BarentsWatch AIS 船只采集器
├── aisstream.py         # AISStream WebSocket 船只采集器
└── earth_boundaries.py  # Earth 国界源校验和静态瓦片 artifact 采集器

backend/app/services/
├── custom_datasource_runtime.py      # 自定义 REST / WebSocket 映射运行时
├── datasource_mapping.py             # 确定性字段映射与目标写入
├── vessel_ais_aggregation.py         # AIS 原始观测写入与聚合读取
├── vessel_aggregation_strategy.py    # 多源字段选择、freshness fallback 和冲突记录
└── vessel_enrichment.py              # 船舶资料富化缓存

backend/app/models/
├── collected_data.py           # 统一数据模型
└── vessel_enrichment.py        # 船舶富化结果缓存

九、凭证型采集器

部分采集器需要外部服务凭证,例如:

采集器 credential provider 凭证来源
barentswatch_vessels barentswatch 控制台“采集管理 -> 采集器”、环境变量、~/.zshrc
aisstream_vessels aisstream 控制台“采集管理 -> 采集器”、环境变量、~/.zshrc(连接验证可读;正式采集建议保存到“采集管理 -> 采集器”或注入后端环境)
spacetrack_tle spacetrack 环境变量、~/.zshrc

BarentsWatch AIS

BarentsWatch AIS 的凭证解析统一在:

VesselAISCollector 只负责采集和转换 AIS 数据,不再自己读取环境变量或拼 token 请求。它通过:

  • resolve_barentswatch_config()
  • fetch_barentswatch_access_token()

获取运行时配置。

解析优先级:

  1. DataSourceConfig.auth_config
  2. DataSourceConfig.config
  3. 环境变量
  4. ~/.zshrc

支持变量:

export BARENTSWATCH_CLIENT_ID="..."
export BARENTSWATCH_CLIENT_SECRET="..."

并兼容历史拼写:

export BARRENTSWATCH_CLIENT_ID="..."
export BARRENTSWATCH_CLIENT_SECRET="..."

连接验证会先请求 https://id.barentswatch.no/connect/token 获取 scope=ais 的 access token再用 Authorization: Bearer <token> 请求 AIS endpoint。

AISStream 实时船舶

AISStream 使用 wss://stream.aisstream.io/v0/stream WebSocket endpoint。默认运行方式是长连接实时采集而不是传统 REST collector 的“请求一次、进度到 100%、完成”模型。

运行时配置:

  • api_key:优先从 DataSourceConfig.auth_config.api_keyconfig.api_key 读取;也可由后端进程环境变量 AISSTREAM_API_KEY 提供。
  • bounding_boxesAISStream 订阅范围,默认示例为全球 [[[-90, -180], [90, 180]]],生产或演示建议先缩小区域。
  • message_types:默认 PositionReportShipStaticData
  • streaming_enabled:默认启用长连接;关闭后回退到批次式 fetch -> transform -> save
  • streaming_max_messages:测试用上限,非 0 时收到指定消息数后停止。
  • reconnect_delay_secondsreceive_timeout_seconds:控制断线重连和空闲等待。

状态语义:

  • connecting:正在连接 AISStream。
  • streaming:持续接收实时消息,records_processed 表示已见消息数,通常没有固定总量和百分比。
  • reconnecting:上游断开或网络异常,采集器记录 AISSourceHealth 后等待重连。
  • stopped / cancelled:任务被测试上限或用户停止。

AISStream 连接验证会通过 datasource_connectivity.py 读取保存的采集器配置、环境变量和 ~/.zshrc 中的 AISSTREAM_API_KEY。正式采集时,最稳妥的方式是把 API Key 保存到“采集管理 -> 采集器 -> AISStream 实时船舶”;如果只放在 ~/.zshrc,需要确认后端进程实际继承到了该环境变量。

控制台通过 /datasources -> 实时流 管理 AISStream而不是把它放进普通有限采集任务的进度条。实时流 API 会聚合运行态、健康状态、配置摘要和 raw observation 计数:

GET /api/v1/realtime-sources
POST /api/v1/realtime-sources/{source}/start
POST /api/v1/realtime-sources/{source}/stop
POST /api/v1/realtime-sources/{source}/restart

aisstream_vessels 和自定义 source_type=websocket 数据源会出现在该接口中。它们不参与一键采集百分比;前端按长连接服务展示消息计数、延迟、最近成功和最近错误。

AIS 原始观测与聚合

AIS 观测写入后不会直接替换最终船只记录,而是先保存为 raw observation

  • source 记录来源,例如 barentswatch_vesselsaisstream_vessels 或自定义源名称。
  • delivery_mode 表达实时性,realtime_stream 优先于 polling
  • transport 记录 websockethttp
  • 位置、速度、航向等动态字段会按 freshness 和来源优先级选择。
  • 静态字段优先保留非空值;冲突候选会记录到详情接口,便于排查多源差异。

Earth 船只展示现在使用受控快照接口和实时增量通道:

GET /api/v1/vessels/snapshot?bbox=lon_min,lat_min,lon_max,lat_max&zoom=12&limit=1000
GET /api/v1/visualization/vessels/{mmsi}
GET /api/v1/visualization/vessels/{mmsi}/track
GET /api/v1/visualization/vessels/{mmsi}/conflicts

/api/v1/vessels/snapshot 必须携带 bboxzoom,默认 limit=1000,最大 limit=5000。它优先消费 ais_raw_observations 聚合结果;当当前 raw 窗口为空时,会受控回退到 legacy vessel_position / vessel_static 最新点,并在 diagnostics.legacy_fallback_used 中标明。旧 /api/v1/visualization/geo/vessels 路由已移除。

实时增量通过 /wsvessels channel 推送。客户端订阅时必须带当前视口:

{
  "type": "subscribe",
  "data": {
    "channel": "vessels",
    "bbox": [120.8, 30.7, 122.1, 31.8],
    "zoom": 12,
    "limit": 1000
  }
}

后端按连接保存轻量订阅条件,只向 bbox 命中的连接发送船只更新。collector 广播会先进入 1 秒节流队列,同一 MMSI 在一个 flush 周期内只保留最新位置,避免高频实时流拖垮 WebSocket。

图层接口与全量统计分离

Earth 后续迁移到两类接口:

GET /api/v1/data-products
GET /api/v1/data-products/{product_id}/status
GET /api/v1/layers/vessels/snapshot?bbox=lon_min,lat_min,lon_max,lat_max&zoom=12&limit=1000
GET /api/v1/layers/cables?bbox=lon_min,lat_min,lon_max,lat_max&zoom=12&limit=1000
GET /api/v1/layers/landing-points?bbox=lon_min,lat_min,lon_max,lat_max&zoom=12&limit=1000
GET /api/v1/layers/satellites?bbox=lon_min,lat_min,lon_max,lat_max&zoom=12&limit=1000
GET /api/v1/layers/bgp/anomalies?bbox=lon_min,lat_min,lon_max,lat_max&zoom=12&limit=1000
GET /api/v1/layers/bgp/incidents?bbox=lon_min,lat_min,lon_max,lat_max&zoom=12&limit=1000
GET /api/v1/layers/bgp/collectors?bbox=lon_min,lat_min,lon_max,lat_max&zoom=12&limit=1000

/api/v1/data-products/* 面向聚合面板,统计口径是全量/全局,不受地图 bbox 影响。/api/v1/layers/* 面向地图渲染,必须携带 bboxzoom,默认 limit=1000,最大 limit=5000;低 zoom 会降级到更小的返回上限,并在 diagnostics 中暴露 degradedtruncatedlimit_clampedstats_scope=viewport。当前版本的非船只图层先复用已有 GeoJSON 转换再做保护层,后续可继续把 bbox 下推到各产品专用查询。

十、采集器设置与连接验证

控制台的“采集管理 -> 采集器”页提供所有内置采集器的 endpoint、请求头、超时、重试和凭证配置。连接验证不是只看前端按钮状态而是由后端计算 checksum

  • endpoint
  • auth type
  • headers
  • config
  • credential provider
  • 凭证指纹

相关 API

GET  /api/v1/datasources/configs/all
POST /api/v1/datasources/configs/builtin/connection-status
POST /api/v1/datasources/configs/builtin/connect
POST /api/v1/settings/integrations/barentswatch/connect
GET  /api/v1/settings/credential-guides/{provider}
POST /api/v1/settings/credential-guides/{provider}/generate
POST /api/v1/settings/credential-guides/{provider}/reset

更多细节见:

十一、数据使用场景

采集的数据最终会:

  1. 可视化展示 - 在UE5大屏上显示超级计算机、GPU集群、海底光缆的地理位置
  2. 态势分析 - 统计全球算力分布、增长趋势
  3. 告警系统 - 检测重要节点变化

十二、采集器注册机制

采集器在应用启动时自动注册:

# backend/app/services/collectors/__init__.py

collector_registry.register(TOP500Collector())
collector_registry.register(EpochAIGPUCollector())
collector_registry.register(HuggingFaceModelCollector())
collector_registry.register(HuggingFaceDatasetCollector())
collector_registry.register(HuggingFaceSpacesCollector())
collector_registry.register(PeeringDBIXPCollector())
collector_registry.register(PeeringDBNetworkCollector())
collector_registry.register(PeeringDBFacilityCollector())
collector_registry.register(TeleGeographyCableCollector())
collector_registry.register(TeleGeographyLandingPointCollector())
collector_registry.register(TeleGeographyCableSystemCollector())

核心文件: backend/app/services/collectors/registry.py

十三、触发采集

方式一:定时触发

系统启动时APScheduler会自动根据各采集器的frequency_hours设置定时任务。

方式二:手动触发 API

# 触发TOP500采集
curl -X POST http://localhost:8000/api/v1/datasources/1/trigger \
  -H "Authorization: Bearer <token>"

批量采集使用:

POST /api/v1/datasources/trigger-batch

请求体可以传 source_ids 精确触发选中项;如果不传 source_ids,后端按 productmoduleis_activerun_statuscollectedcredential_statusq 过滤后触发。接口会跳过禁用源、正在运行且未 force 的源,以及未到频率窗口的源,并返回 triggeredskippedfailed 三组结果。

核心文件: backend/app/api/v1/datasources.py