警告

本文使用的公开材料仅能确认 Dark Sky 的后端架构到一定程度,未披露的部分不应外推。本文不涉及 Dark Sky 的移动应用、网站或其他前端产品。本文由 Claude&Codex 协助整理,未经前 Dark Sky 团队确认。相关信息可能存在不准确或过时的情况。请谨慎参考。

引言

Forecast.io 后来更名为 Dark Sky,其核心产品是按经纬度查询的天气 API。Dark Sky 于 2020-03-31 被 Apple 收购,API 服务于 2023-03-31 结束。本文使用的公开材料包括 Wayback Machine 存档、Hacker News 原帖,以及 darkskyapp GitHub 组织下留存的仓库。

本文按以下两条证据边界整理这些材料:

  1. 每条结论注明材料来源及其覆盖范围;
  2. 材料未涉及的部分标为「未披露」,不依据相邻证据外推。

2012 年官方 Open Source 文章中「格点数据存成图像」的表述对应早期雷达临近预报链路。该文早于通用 Forecast Data API 发布,文内回指链接也指向介绍 NEXRAD 雷达处理的 How Dark Sky Works。因此,这一表述不能用于判断 GFS、NAM 等数值模式格点的在线存储格式。该存储格式至今未披露。

创始人公开说明的后端组件

2013-03-27,Forecast.io 和 Forecast Data API 发布次日,用户 JonathanHunt 在 Hacker News 发布帖 下询问后端使用的数据库。联合创始人 Adam Grossman(HN 账号 thegrossman回答

The top layer of our backend is node.js running behind nginx. We use redis for storage, along with a bunch of flat files for certain things. Under that is a mostly ruby / mysql layer that manages a bunch of C programs for doing various number crunching tasks.

在本文收集的材料中,这是唯一一条创始人对后端组件的直接说明,发布时间对应已经完成重写的通用预报服务。原文可以确认以下组件关系:

flowchart TD A["nginx"] --> B["Node.js 顶层"] B --> C["Redis storage"] B --> D["flat files"] B --> E["Ruby 和 MySQL 层"] E --> F["C 数值计算程序"]

这段话未披露 Redis 保存的是 API 结果、模式取值、会话还是元数据,也未披露 flat files 是否保存 GFS、NAM 等模式场,以及 MySQL 的表结构和数据职责。原文用词是 storage,缺少将其进一步描述为格点主库所需的数据对象映射证据。

其他结论依据 darkskyapp GitHub 公开仓库、Chef cookbook、API 文档语义和博客技术文章交叉推断,置信度低于这条直接说明。

对外产品形态

2013-03-26 发布的 Forecast Data API 是从原 Dark Sky 专用接口重写而来,当时官方称接入了 16 个数据源。到 2019 年的 API 文档快照,核心请求仍然是单一形式:

1
https://api.darksky.net/forecast/[key]/[latitude],[longitude]

返回内容为当前状况、未来一小时逐分钟预报(视地区可用性)、未来 48 小时逐小时预报、未来一周逐日预报。没有原始模式格点下载接口。

2019 年 API 文档说明,响应中的 flags 字段返回 sources(本次请求实际使用的源 ID 数组)和 nearest-station(最近贡献站点的距离)。2014 版文档中的 v1 还返回 isd-stationsmadis-stationsmetar-stationslamp-stationsdarksky-stations 等站点 ID 列表。每次请求的数据源组合随位置变化,这一组合只能在请求到达后确定。

数据源清单的变化

v1 时代的 forecast.io/raw 页面列出约 20 个源:

id覆盖
darksky自研雷达临近预报(NEXRAD + NIMROD)US / UK / IE
gfsNOAA Global Forecast System全球
namNOAA North American Mesoscale北美
rapNOAA Rapid Refresh北美
srefNOAA/NCEP Short-Range Ensemble Forecast北美
rtmaNOAA Real-Time Mesoscale Analysis(2.5 km)北美
cmcEnvironment Canada CMC 集合全球
naefsNOAA + EC North American Ensemble全球
fnmocUS Navy FNMOC 集合全球
metno / metno_ce / metno_ne挪威气象局 API 与 GRIB全球 / 欧洲
datapointUK Met Office DatapointUK
lampNOAA LAMP(统计后处理)US
isdNOAA Integrated Surface Database全球(历史)
madisNOAA/ESRL MADIS全球(站点)
metar全球 METAR全球(站点)
nwspa / metwarn美 / 英预警US / UK

2019 年的数据源文档 快照,清单缩减为 13 项:cmcdarkskyecpagfshrrriconimoisdmadismeteoalarmnamnwspasref

变化包括:

  • 移除 rapfnmocnaefsmetno*datapointlampmetar
  • 新增 hrrr(3 km 快速刷新)和 icon(DWD 二十面体全球模式);
  • 预警源按地区拆分为 nwspaecpaimometeoalarm

官方未披露移除各源的具体依据。

摄取:轮询与推送两条路径

node-sarra 仓库创建于 2017-08,最后更新于 2020-07。该项目是 Sarracenia 的 Node.js 客户端,Sarracenia 是加拿大 ECCC 的 AMQP 推送分发服务。README 说明连接失败后会自动重连,并区分 devprod 队列及其过期时间,同时指出队列非持久化或断连超过过期窗口时会丢失消息。支持推送的数据源使用 AMQP 订阅,其余数据源的获取方式未披露。

雷达数据使用独立的处理流程,相关公开材料较完整。根据 2011 年的 How Dark Sky Works 和 2013 年的 Cleaning Radar Images

  1. 从 NOAA 下载 NEXRAD 原始二进制雷达数据,转成图像;
  2. 用 FANN C 库训练的 25 神经元网络把回波块分类为信号或噪声,识别出 90% 到 95% 的噪声,剩余部分由人工编写的启发式规则处理,训练集由人工标注;
  3. 用 OpenCV 光流估计降水区域速度场;
  4. 将 x 方向速度、y 方向速度和强度变化编码进图像通道下发;
  5. 每收到新雷达帧,把旧帧外推到当前时刻与实测对比,持续评估误差。

官方给出的处理耗时为:

it takes between 1 and 2 seconds to convert a raw radar data file to a cleaned image, and we dedicate 16 cores worth of virtual servers to the task.

第 3 步不使用多普勒径向速度,Grossman 在 2011 年的 HN 回复 里给的理由是对流单体常常原地生消甚至逆风移动,径向风速对预测移动方向的参考价值有限。

How Dark Sky Works 说明外推和动画由客户端 GPU 执行,服务端只下发编码成图像的速度场。

从格点到点

Dark Sky 未公开完整的点查询代码。现有材料可以确认几个彼此独立的空间处理方法。

站点类数据使用 Delaunay 三角剖分加重心坐标插值。2012-05-31 的 How Dark Sky Calculates Temperature 说明先在站点之间连成三角网,查询点定位到所在三角形后,对三个顶点站点取加权平均;脚注说明使用重心坐标判断点是否落在三角形内,同时直接得到三个顶点的权重系数。对应的开源实现是 delaunay-fastdelaunay

站点近邻查询使用 sphere-knn。README 说明它源自 Dark Sky API 的需求,并自 2012 年 10 月起用于生产。

读取路径还包含一项前置判断。inhabited 的 README 说明它用于快速判断一个位置是否可能有人居住,从而跳过开销较大的 geocoding 查询。此类位置在产品中显示为 Middle of Nowhere。

格点类数据先做偏差订正再融合。2013-07-16 的 New Data Sources: SREF and RTMA 说明 2.5 km 的 RTMA 除了直接参与计算,还用于订正地面站观测:

if your location happens to be 5° warmer (on average) than the nearest ground station, we’ll adjust the station’s temperature upwards by 5° before factoring it into your particular forecast.

forecast.io/raw 页面同时提供 temperature (raw)temperature (bias corrected) 两条曲线,也允许输入任意经纬度和要素,分别查看各数据源的原始时间序列。这些功能说明系统在合成前可以取得各数据源的独立数值,未披露偏差订正结果和各源时间序列是否持久化。

多模式权重按区域动态确定。2013 年 Washington Post 的评测 中,Grossman 说明每个模式先做地理偏差订正,系统按区域持续监测各模式准确率并计算标准误差,请求到达时依据标准误差动态设置权重。权重函数的具体形式未披露。

公开材料支持以下逻辑步骤:

1
2
3
4
5
6
7
经纬度
  -> 确定覆盖域和可用源
  -> 通过未披露的存储或内部服务取得各来源数值
  -> 查询附近站点和局地订正量
  -> 读取各模式在该区域的误差统计
  -> 动态融合
  -> 生成 currently、minutely、hourly、daily 和 flags

其中偏差订正、区域误差统计和请求时动态加权有公开说明;模式轮次选择、格点读取、插值实现、文件命名和服务边界均未披露。

图像化格点的适用范围

2012-11-20 的 Open Source 是团队开发者 Jay LaPorte 对当时开源出来的五个 Node.js 模块(cache-helperspngparsesphere-knnstring-hashtz-lookup)的介绍,文中给出四条信息:

  • 当时的 Dark Sky API 基本完全由 Node.js 编写,选型理由是「so darned fast and lightweight, we can power the entire API off of a few teensy virtual machines」;
  • API 的速度主要来自大量缓存;
  • 格点数据存成图像,pngparse 是 API 读取这些图像的模块;
  • 站点近邻查询使用 sphere-knn,理由是大部分天气数据来自地基雷达站,查询请求位置附近有哪些站点是常见操作。

第三条的适用范围受发布时间和回指链接限制:

  • Open Source 发布于 2012-11-20;
  • 通用 Forecast Data API 发布于 2013-03-26,官方明确称新 API 在原专用 API 基础上从头重建;
  • 文中「We’ve mentioned before」的链接指向 2011 年的 How Dark Sky Works,该文完整讨论 NEXRAD 雷达图像、运动场和客户端外推,没有讨论数值模式预报格点。

因此,现有证据支持将图像存储和 pngparse 归入早期雷达临近预报链路,无法据此判断后来 Forecast Data API 的数值模式格点是否使用 PNG。

其余几个仓库提供的证据及其边界如下:

仓库能确认的内容边界
pngparse从 PNG 文件或 Buffer 读取像素数组,源于早期 Dark Sky API 需求上下文指向雷达 API
pbj自定义单色位图格式,uint16 LE 宽高加逐 bit 位图,支持 gzip,项目说明称曾代替 PNG 用作存储格式承载的业务对象未披露
elevation全球高程格点放在本地二进制文件 elev.bin,按 (y * width + x) * 2 计算偏移做定长随机读取,文件描述符由 cache-helpers.once 只打开一次静态高程层,不随轮次更新
storage-cookbook挂载全部 EC2 临时卷并把挂载点写入 node attributes,仅支持 EC2与气象数据路径的关系未披露
zqueueRedis sorted set 实现的优先队列队列承载的任务类型未披露

2013-08-28 的 Project Quicksilver 提供了数值模式与图像之间唯一的直接联系,也是唯一被详细描述过的完整格点生产流程:

  1. 输入包括 GFS(当时 0.5°)等低分辨率模式、ISD 地面站、MODIS(Terra/Aqua 的地表温度月均高低值、植被覆盖、发射率、反照率)和 USGS GMTED 30 弧秒高程;
  2. 用神经网络做非线性回归,学习静态地理量到日最高、最低气温偏离量的映射,得到微气候扰动场;官方明确说明该网络的输出不能直接使用,可用的是相对温度差;
  3. 把扰动场叠加到低分辨率模式场上完成降尺度;
  4. 用实时地面站观测做整体偏差订正;
  5. 对每个源重复上述步骤,再做加权平均;
  6. 输出为 16 bit 灰度 GeoTIFF,每像素最大边长约 3.5 mile,每小时重算一次,交给 MapBox 切瓦片。

Quicksilver 说明模式数据经过了图像化派生处理,输出为文件。文章同时明确说明:

we aren’t using it to provide current temperature data for forecast.io

因此,现有材料无法确认点 API 的温度数据是否来自这条处理流程,输入模式格点在在线 API 中的存储格式也未披露。

另一篇 RGB Temperature 说明不同时刻的天气数值分别编码到 RGB 三个通道,一次传输三个时刻的数据。这条证据属于地图传输层。

不保存历史预报

Dark Sky API FAQ 包含一条直接问答:

Can I get your previous forecasts instead of weather station observations? Unfortunately, we do not have the storage capacity to store previous forecasts. Sorry!

FAQ 对历史数据来源还有一条说明:

Several of our data sources aren’t real-time, and flow into our system after the fact. As a result, our historical data may continue changing for up to two weeks after a time has occurred.

由此可以确认,Time Machine 不返回历史预报归档。FAQ 说明历史报告通常基于观测数据,缺少观测时会由其他来源补齐。这两条 FAQ 未披露当前有效预报的具体保留期限。

缓存

cache-helpers 的 README 明确称它是从 Dark Sky API 中反复出现的缓存模式抽取出来的模块,公开实现包含三种:

组件语义
once首次调用加载并缓存,并发调用合并为一次后端加载
timeBasedWithGrace软过期未到直接返回缓存;软过期已过硬过期未到,先返回旧值并在后台刷新;硬过期已过则阻塞到新值可用
sizeBasedKeyValue按容量淘汰的进程内键值缓存

README 对两个阈值的解释是「the time until data is out of date」和「the time until data is unacceptably out of date」。timeBasedWithGrace 的语义与 stale-while-revalidate 一致。2017 年公开的 cache 是 Promise 支持的进程内 TTL 缓存,会缓存正在执行的 Promise,使同一进程内的并发请求共享一次后端加载。

这些组件的源码未包含业务对象信息,无法确认数值模式 API 实际缓存了哪些对象。

公开材料可以确认以下对外缓存语义:

  • API 文档说明响应返回保守设置的 Cache-Control,取值依据响应中包含的数据确定;
  • FAQ 说明 minutely 数据每 5 分钟更新,hourly 和 daily 每小时更新,灾害预警实时更新;
  • exclude= 参数文档说明用途是「reducing latency and saving cache space」,说明排除 blocks 可以减少响应缓存占用;
  • FAQ 说明服务端禁用 CORS,并要求调用方自建代理,调用方可以在代理上缓存响应。

基础设施演进

时期形态依据
2011 至 2015Linode,约 100 台虚机,自运维Grossman Dark Sky Has A New Owner(2015-01-12):「keeping a hundred Linode servers up and running」
2015与 Applied Invention 合并,团队扩张同一官方公告
2016 至 2020EC2,Chef 管理配置Chef cookbook 仓库的创建时间线1
2020-03-31被 Apple 收购Dark Sky 官方公告
2023-03-31API 关停Apple 支持文档

公开材料显示,2011 年到 2020 年的基础设施主要由长期运行的虚机构成。2016 年后的 cookbook 显示配置由 Chef 管理;本文检查的 darkskyapp 公开仓库中未见容器或 Serverless 相关材料。

公开材料支持的系统边界

  1. 数值模式来自政府机构,Dark Sky 负责统计后处理和融合。数据源文档列出各政府机构提供的模式;Grossman 说明团队成员没有气象学背景,Jack Turner 将短临方法描述为 numerical & statistical approach,多模式融合方式则见 Washington Post 评测存档
  2. 系统不保存历史预报归档,FAQ 直接说明存储容量不足。
  3. 本文检查的 API 文档和公开仓库未提及专用的地理或时序数据库(PostGIS、InfluxDB、Cassandra 等)。
  4. flags.sources 随位置变化、forecast.io/raw 支持任意点抽取、sphere-knninhabited 服务于查询路径,这四条证据共同支持请求时按点合成的判断。
  5. 分钟级外推和动画由客户端 GPU 执行,流程见 How Dark Sky Works
  6. 截至 2026-08-09,在 Google Patents 中以「Dark Sky Company」检索未得到结果。

证据分级

高置信度结论

  1. 后端组件。 后端包括 nginx、Node.js、Redis storage、flat files、Ruby/MySQL 和 C 计算程序。依据:创始人在上线次日的直接说明
  2. 历史预报归档。 Dark Sky 不保存历史预报归档。依据:官方 FAQ
  3. 早期雷达格点。 格点以图像形式存储。依据:Open Source 的回指链接和发布时间线。
  4. 早期雷达 API。 API 主要由 Node.js 实现。依据:2012 年官方说明
  5. Quicksilver 输出。 输出采用灰度图和 GeoTIFF。依据:官方技术文章
  6. 多模式加权。 系统在请求阶段按区域误差动态设置权重。依据:创始人技术说明
  7. 站点近邻查询。 查询使用内存空间索引。依据:sphere-knn 的生产说明
  8. 缓存组件。 组件支持后台刷新和并发请求合并。依据:cache-helperscache 源码。

间接证据支持的结论

  1. 请求时按点合成。 确认程度为中高。API 文档raw 页面sphere-knninhabited 四条间接证据一致。
  2. 2016 年后从 Linode 迁到 AWS。 确认程度为中,结论由 cookbook 时间线推断。

未披露

  1. EC2 临时卷的业务用途。 storage-cookbook 未披露临时卷是否保存气象格点文件。
  2. pbj 承载的业务对象。 pbj 未披露它是否用于数值模式格点存储。
  3. Redis 优先队列的生产用途。 zqueue 未披露承载的任务类型。
  4. 数值模式格点的存储介质。 创始人说明没有给出数据对象与 Redis、flat files、MySQL 之间的映射。
  5. Forecast Data API 模式格点的存储格式。 现有材料没有支持图像存储的直接证据。
  6. GRIB 解码工具。 「C programs for number crunching」是唯一线索。
  7. Redis 的 key 设计和数据结构。 公开材料没有相关信息。
  8. 多源加权的权重函数。 创始人说明提及基于历史准确率调整,没有给出具体形式。

可复现项目对照

Dark Sky 的模式格点存储未披露。以下两个可复现项目展示了模式格点存储的具体实现。

Pirate Weather 复刻了 Dark Sky 的 API 语法,公开了完整架构:

flowchart LR A["NOAA S3 GRIB"] --> B["EventBridge 和 Step Functions"] B --> C["Fargate 和 wgrib2"] C --> D["EFS NetCDF4 压缩分块"] E["API Gateway"] --> F["Lambda"] F --> D F --> G["空间取点和时间插值"] G --> H["Dark Sky 兼容 JSON"]

其中几个数字和选择:原始 GRIB 直接查询约需 20 秒,无法用于在线请求;预处理把 GRIB 合并转成 NetCDF4,按时间维分块并限制有效位数;wgrib2 运行在 Fargate 上,Lambda 的临时存储上限无法满足要求;GFS 和 GEFS 对最近 9 个格点按距离倒数加权;单次请求 1 到 3 秒;历史数据直接读取 S3 上的 ERA5 云原生 Zarr。文档明确说明这套方案未使用传统数据库。

Open-Meteo 每天处理超过 2 TB 模式数据,保存为本地自定义 .om 二进制文件,README 说明文件格式和压缩针对 14 天时间序列这类访问模式优化,公开 API 声称响应时间低于 10 ms。

两个项目均按查询形状设计文件布局,由文件格式和分块承担主要数据访问职责。天气点 API 的典型请求是读取一个空间位置上的多个变量和多个时次。按时间序列方向分块有利于利用页缓存和顺序读取提高吞吐量。

另一种实现可以参照 Tomorrow.io(原 ClimaCell)的专利 US10962680B2,其中描述了四类服务器和「tile layer + cadence」的瓦片化格点存储,支持按瓦片增量重算。这套方案的工程量高于上述两个项目,可作为设计取舍的参照。

仍然未查明的部分

  • 最新模式轮次采用原始 GRIB、转换后的数组文件、数据库记录还是内部服务;
  • 在线查询是直接读文件、访问共享存储、调用独立格点服务还是查询数据库;
  • 最新模式轮次位于本地磁盘、共享文件系统还是对象存储;
  • pbj 是否用于模式格点,还是仅用于雷达掩膜;
  • 是否有 CDN 前置;
  • Apple 收购后 WeatherKit 是否沿用该架构。

上述问题均缺少公开材料。后续可继续检查 Applied Invention 的技术材料,以及 darkskyapp 各仓库的 commit 历史和 issue。

Dark Sky 原团队于 2026-02 发布了新产品 Acme Weather。官方介绍称数据来自数值模式、卫星、地面站和雷达,预报为自研。基础设施细节未披露。