从天气系统初版看数据链路设计:可观测、可控制、可替换
发布时间:2026/9/5 17:04:42 锦皓数字建站

前阵子整理一个“天气系统初版”需求听起来很简单按城市查天气给一个页面展示出来。真正动手之后我发现被低估的并不是“请求天气接口”这一步而是从外部数据源到用户界面这一整条链路上的选择。城市代码怎么传原始字段是什么单位多久刷新一次数据没更新时前端怎么显示定时任务出错了怎么处理这些看似零散的问题最后都会变成初版能不能稳定使用的关键。我后来对“初版”有了一个更明确的判断它交付的不是一个能显示温度的页面而是一条“从天气数据源到用户页面之间可观测、可控制、可替换”的数据链路。接口只是入口真正复杂的是链路里的边界、状态和异常。1. 初版要回答的不是“怎么查天气”而是“谁会怎么用”先说一个常见的误区拿到一个天气系统初版的需求第一反应往往是去查有什么天气接口然后立刻开始写页面。这个顺序不是不能走但很容易让初版变成一个“查天气 demo”而不是一个能持续迭代的系统。所以我建议先花半天时间把产品边界画出来再考虑技术选型。这里的边界不是完整需求文档而是三个问题谁会使用、在什么场景下使用、他需要的最少信息是什么。1.1 先定义“初版”的核心场景如果做一个内部运维大屏上的天气模块那么用户可能希望快速看到当地未来几个小时有没有降雨因为运维值班时要判断室外活动是否适合进行。这种情况初版需要的是逐小时预报而不是过于复杂的生活指数。如果做一个给普通用户查周末出行天气的页面那用户关心的是周六周日的气温和天气现象需要的是未来 15 天预报中的某几项。如果做一个给测试环境自动生成异常天气数据的工具那重心甚至不在实时数据而在于能不能在本地构造高温、暴雨、大风等极端场景。这三种需求同样是查天气但输入、输出、刷新频率和关注字段完全不一样。初版必须为其中一个具体场景服务否则你会被大量选项拖住。1.2 我建议先砍掉哪些内容天气系统最容易越做越大因为天气相关的字段实在太多实时温度、体感温度、露点温度、气压、湿度、风向、风力、降水量、紫外线、空气质量、日出日落、生活指数每个字段看起来都值得展示。但从初版角度看我会砍掉绝大多数“锦上添花”的字段只保留一条用户能完成闭环的主链路。以“城市实时天气查询”为例可以先保留城市标识或城市代码天气现象当前温度湿度风力风向数据更新时间其他内容要么放到二期要么等用户真的提出了再看。砍掉字段本质上不是为了少写代码而是为了减少后续的数据质量问题和前端解释成本。天气系统里每一个多余字段背后都对应着上游格式解析、单位换算、显示逻辑和异常兜底。字段越多初版出问题的面就越大。2. 数据源接入把一场未知格式的 JSON 变成可控数据当核心场景确定之后第二个绕不开的问题是数据源。一个天气系统可以有很多种数据来源商业天气服务、公共实验数据、自建气象站、甚至爬取公开网页。无论哪种来源走完一遍接入后都会发现真正重要的工作不是“把 JSON 解析成对象”而是“把外部格式翻译成内部统一口径”。2.1 一眼看清字段约定命名、单位、时区天气接口的返回结构千差万别但接入时最容易踩的坑是单位口径。同样的温度有的接口返回的是摄氏温度有的返回的是华氏温度有的风速字段是公里每小时有的是米每秒降雨量可能以毫米为单位也可能是一个表示降水概率的百分比。在早期版本里我会把每个字段的“类型、单位、更新频率、可能范围”写清楚做成一张字段映射表。这张表不只是给后端看的也是给前端、给测试看的。例如上游字段含义项目内部字段单位口径常见边界说明当前气温temp_c摄氏度极端天气时可能出现负值也可能超过 40不能直接按固定区间判断体感温度feels_like_c摄氏度与气温差很大时通常是风或湿度造成的天气现象代码condition_code标准枚举字符串不要用中文作文档字段换行、空格都可能影响匹配更新时间update_timeUTC 字符串或时间戳前端展示前一定要转成本地时区否则会出现“明明刚更新页面却显示几小时前”的问题这个步骤看起来很枯燥但它决定了后续代码到底是在处理高质量数据还是在不停地猜字段。2.2 用适配层隔离“上游变化”接入第三方天气源时我有一个不会变的原则业务代码里不要直接依赖上游返回字段和原始结构中间必须加一层适配层。原因很简单上游字段名可能会变单位可能会变有些字段可能会在下一次版本中消失。如果业务代码中到处直接使用result.data.temp上游一旦调整结构所有用到的地方都要跟着改。而加上一层适配层后只改一个地方下游实体可以保持不变。# 伪代码示例把上游结构转换为内部标准结构 def convert_from_provider(payload): return { city_code: payload[cityid], temp_c: payload[temperature], condition: map_condition(payload[weather]), updated_at: normalize_time(payload[update_time]), }这里不需要一开始就做得很复杂一个纯函数、一张字段映射关系就足够。重要的是从第一行代码开始就建立“外部”和“内部”的边界。这也是大数据或复杂系统里常说的防腐层思路初版可能体会不到价值但等到替换天气源或升级接口时这层边界可以帮你节省大量时间。2.3 原始报文要不要落库建议保留初版系统里为了调试方便我通常会把天气源返回的原始 JSON 以日志或记录形式保留一段时间。不要一开始就只存解析后的字段因为排查问题时会发现很多问题是“解析错了”还是“上游本来就没返回值”导致的。有了原始报文排查时可以先回答一个关键问题上游返回的数据到底是缺失、畸形还是别的情况。如果没有原始报文你只能通过日志猜测效率很低。等系统运行稳定一段时候后再考虑是否清理这部分数据。3. 定时刷新先跑通再谈并发和频率天气系统初版里最容易被忽视的是刷新机制。很多人的第一反应是写一个定时任务每 30 分钟请求一次接口然后更新数据库。这个做法本身没有错但太简单了真正设计时还要考虑业务需要、上游限制和失败重试。3.1 刷新策略不要一拍脑袋定先看清数据源更新时间不同天气服务的更新频率差异很大。有的实时数据 10 分钟更新一次有的逐小时预报每小时更新一次有的 15 天预报一天只更新数次。在接入时要尽量按照数据源自身的更新时间再设置你自己的调度时间。如果数据源一小时才更新一次你内部 10 分钟刷新一次并不能让用户看到更多新数据只会白白提高请求量增加被限流或封禁的风险。初版阶段刷新频率保守一点更稳妥。可以先每 30 分钟或 1 小时刷新一次跑几天看看更新时间规律再去调参数。# cron 示例每 30 分钟执行一次天气刷新任务 */30 * * * * cd /opt/weather-system python task/refresh_city_weather.py这里引出一个很关键的提醒初版阶段调度任务应该允许“手动触发”而不是只能依赖 cron。手动触发是用来验证代码、单城刷新和排查问题的入口。如果你写完调度逻辑后才发现想手动跑一次某个城市很困难说明任务拆分方式不合理。3.2 刷新任务必须做失败隔离和重试上限天气刷新是典型的“外部依赖不稳定”的任务。网络抖动、上游接口超时、限流、数据格式突然变化都会导致刷新失败。初版如果没有失败处理第一次偶发异常就会造成数据空缺。失败处理不需要做得很重但至少要做到三点单个城市失败不能影响其他城市继续刷新。失败后要记录原因并允许手动或延时重跑。重试要有次数上限不能无限循环否则会造成请求堆积。一种常见的写法是先把要刷新的城市列表拉出来逐个处理每个城市请求失败后记录statusfailed和error_message然后继续下一个全部跑完后再统一汇总失败列表。这样即使有 10 个城市失败你也能知道是哪 10 个、为什么失败。如果一上来就设计复杂的分布式任务调度其实是过度设计。初版先把单机定时任务跑稳定把失败日志打清楚后续再拆分任务队列也不迟。3.3 刷新和数据入库要“幂等”天气数据刷新的实现方式很容易写成“每次插入一条新记录”。如果这样写一次重复任务会导致同一时刻出现多条数据查询时要额外按时间去重否则数据越多越乱。初版更适合用“城市 时间类型”作为幂等键。每次刷新时如果某个城市已经存在对应的实时数据记录就更新现有记录如果没有则插入一条新记录。这样重复执行不会造成数据重复任务重试也会安全很多。更直接的做法是刷新任务里先执行一次 upsert再执行查询返回值。不要一上来设计一个复杂的“历史天气表”先让当前数据在数据库里只有一条最新记录。需要历史趋势分析时再引入独立的历史表或数据归档。4. 做好“能展示什么”的口径前端才不会反复返工初版中还有一个容易被忽视的问题后端和前端之间对于天气字段的“展示口径”没有对齐。很多时候后端返回的是体感温度页面却标成了当前气温后端返回的是降水概率0.0到1.0前端却直接当成百分比使用。这些问题不是代码难写而是接口契约不够清楚造成的。所以接口设计阶段我建议直接和前端约定清楚“能展示什么、显示成什么、异常时显示什么”。4.1 接口字段尽量返回“展示友好型”的数据天气这种领域换算和格式化最好不要让前端自己处理。后端至少做好几件事温度统一成摄氏度字段名直接体现单位比如temp_c避免歧义。天气状态统一成稳定枚举值而不是中文原文比如sunny、cloudy、rain展示文案由前端映射。时间字段最好返回带时区的时间戳或 ISO 字符串前端负责本地化显示。数值单位尽量直接返回页面要展示的数值不要在客户端再乘除 10 或换算风速。这样做不是因为前端能力不够而是因为同一个系统将来可能接入网页、App、大屏、语音等多种端。把格式化逻辑集中到后端适配层后续换前端时不需要重新踩一遍单位坑。4.2 数据为空、数据过期也要定义清楚天气系统必然会遇到异常情况某个城市暂无数据、上游接口超时、刷新任务失败导致数据停留在昨天。此时接口不能只是返回一个空对象否则前端无法区分“这个城市没有天气”和“城市代码传错了”。接口层最好返回一个状态对象至少包含当前使用的数据是否可用、数据产生时间、数据是否过期。前端拿到后可以根据状态决定展示正常内容、展示上次更新时间、还是显示“暂无天气数据”。{ code: 0, city_code: 101010100, data: { temp_c: 26, condition: sunny, updated_at: 2025-04-01T10:00:0008:00 }, data_status: ok }这样设计虽然看起来多了一点状态字段但它能避免很多“为什么页面是空的”“为什么显示的还是三天前天气”之类的重复沟通。接口稳定的初版才算真正能交付给前端联调。5. 初版跑完不等于稳定上线前自检清单值得过一遍一个天气系统跑到“能出数、页面能展示”的状态通常只完成了 60% 的工程。剩下的 40% 都是这些“不跑不知道一跑才发现”的问题刷新任务没人看数据质量没人校验报错了没有报警数据库连接泄漏了没人知道。所以初版是否完成不应该只看功能是否跑通还要看下面这套自检是否能通过。5.1 上线前我会检查的六个环节按顺序过一遍通常能发现很多隐患手动流程是否通畅能否手动跑通一个城市的查询、刷新、入库、展示并且日志里能看到每一步耗时和结果。输入边界是否清晰城市不存在、城市代码为空、城市列表中包含重复城市系统分别会怎么表现。上游异常是否可识别天气源接口返回非 200、超时、字段缺失、字段类型不对能否在日志中准确区分出是哪一种。刷新任务是否可重跑任务失败后重新运行会不会造成重复数据会不会导致数据库主键冲突或数据错乱。页面状态是否可控没有数据、数据过期、接口报错前端是否都有对应文案而不是白屏或一直转圈。日志是否足够定位问题只看日志能否说清楚某个城市最后一次刷新是什么时间、返回了什么内容、存储结果是什么。其中第 6 点最关键也最容易被忽略。很多初版系统上线后报了一个“今天某个城市天气没更新”结果你打开日志发现只有一行request failed没有任何上下文。你根本没法判断是上游问题、代码问题还是调度问题。所以日志里至少要带上城市代码、请求地址、返回状态、耗时和错误详情。5.2 排查问题时分清这四层上线后如果出现问题我一般不建议一上来就改代码。先按下面顺序定位第一层数据源上游接口本身是否正常是否返回了正确的天气信息。如果上游都没更新下游再怎么查也是旧数据。第二层刷新链路调度任务是否执行刷新脚本有没有跑完单个城市的刷新状态是成功还是失败。第三层存储数据库里这条城市记录是否更新成功更新时间是不是最新的有没有多写或漏写重复数据。第四层展示链路后端接口返回的数据是否来自最新记录页面端是否把字段映射正确有没有缓存了旧响应。这套排查顺序可以避免一个很常见的尴尬情况后端改了半天最后发现是页面浏览器缓存了昨天的返回值。先判断问题出在哪一层再决定要不要动代码。6. 初版之后还要补哪些能力才能算“能用”如果初版顺利跑过自检清单下一阶段不需要急着加新功能。我的建议是先补齐三块工程能力再考虑引入更多天气维度。6.1 建立数据质量监控天气数据本身变化很快光靠人工盯页面很难及时发现异常。比较务实的做法是加一个简单监控任务定期检查每个城市的数据更新时间如果某个城市超过一定时长没有更新就产生一条报警。初版不需要接很重的监控平台可以先用一个定时脚本检查所有城市的最新更新时间把超过阈值的城市列表输出到日志。如果公司内部已经有企业微信、钉钉或邮件通道再把报警接入对应 webhook。这个动作的收益远大于一次性多接几个天气字段。6.2 把配置和代码拆开天气接口的 key、城市列表、刷新频率、超时阈值这些配置最好不要写死在代码里至少放到环境变量或配置文件里。这样后续调整刷新频率时不需要重新部署代码。尤其是城市列表最好维护在一个单独文件中并支持两种更新方式一种是服务启动时加载另一种是刷新任务执行前重新读取。这样在运营层面增加一个城市不需要改代码、重新编译只更新配置即可。6.3 保留一条手工干预通道任何天气系统都不可能永远自动化运行。当出现颮线、极端天气时上游数据可能更新异常某些城市可能出现明显偏差。这时如果系统能支持“手工修正某个城市的天气值”或“临时屏蔽某个城市”会非常有用。初版阶段不一定需要做手工修正页面但至少要在数据库里预留一个字段表示这条天气数据是来自定时刷新还是来自人工修正。将来再扩展这个能力时会容易很多。所以我一直认为天气系统初版的真正交付物不是华丽页面而是一套能让数据稳定地进来、状态清晰可查、问题分层可控的骨架。天气源可以换页面可以重做但数据链路的治理习惯一旦建立起来后续的扩展都只是在这个骨架之上生长。如果你正在做一个天气系统初版可以先不要急着加功能。先让一个城市的天气数据能稳定跑三天每天看看日志、检查一下刷新时间、确认没有脏数据再继续往下做。后面你会发现这个看起来“很慢”的起步反而帮你省掉了很多返工时间。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。