资讯详情

资讯详情

wttr.in 的 4 层缓存怎么挡住上亿次天气请求:一次 curl 的完整旅程

wttr.in 的 4 层缓存怎么挡住上亿次天气请求一次 curl 的完整旅程【免费下载链接】wttr.in:partly_sunny: The right way to check the weather项目地址: https://gitcode.com/gh_mirrors/wt/wttr.in每天curl wttr.in/Beijing这样的天气查询会被敲上亿次。最容易被忽略的细节是同一个 IP、同一个 URL第二次请求的响应时间会从 200~500ms 直接掉到 1~5ms。这不是网络抖动——是缓存。当前仓库是 Go 重写的 wttr.in它的缓存不是一层而是四道闸门叠在一起。请求进门先定位你再解析参数computeResponse是整个管线的心脏入口在 internal/weather/weather.go。第一步不是查天气而是查你是谁拿到客户端 IP优先读X-Forwarded-For因为前面通常挂着反代按配置顺序过 IP 定位器——本地 GeoLite2 数据库优先查不到再走在线服务定位结果落进 SQLite。接着QueryParser.Parse把?formatj1、?T这类 query 参数解析成一个强类型的Options结构体。顺带一个细节IP 落在美国的客户端默认单位自动切成华氏度——不是硬编码是定位结果驱动的。设计权衡定位放在管线最前面意味着后面所有步骤geocode、渲染、单位都能共享这份结果代价是多一次本地 DB 查询换来的是不写城市名也能出天气的兜底能力。城市名到经纬度geocode 也被本地挡了一道拿到位置字符串比如Beijing后走Locator.GetLocation。城市名到坐标的映射存在本地 SQLite 里命中直接返回miss 才调 OpenCage 之类的 geocoding API再把结果写回 DB。设计权衡geocoding 是按次计费的昂贵操作本地挡一道之后真正打到 API 的只有第一次查询——热门城市的 geocoding 请求量从每天上亿降到接近零。第一层缓存上游天气数据TTL 45 分钟geocode 出经纬度去拉上游天气数据。这里包了一个CachedWeatherClient见 internal/weather/weatherer_wwo_cached.go外层是 disk 缓存TTL 45 分钟容量上限 2GB。缓存 key 的构造只有一句话lat math.Round(lat*10000) / 10000 lon math.Round(lon*10000) / 10000 return fmt.Sprintf(weather:wwo:%.4f:%.4f:%s, lat, lon, lang)上面这几行干的事是把经纬度四舍五入到 4 位小数约 11 米精度拼上语言生成缓存 key。天气 API 的数据分辨率本来就只有公里级11 米的精度已经过细——但 4 位小数能让同一区域的请求自然共享同一个 key避免差 0.0001 度就 cache miss的碎片化。设计权衡牺牲一点点坐标精确度换来缓存命中率的显著提升。并发洪峰1000 个人同时查北京上游只来 1 次请求缓存 miss 的瞬间最容易出事同一时刻 1000 个并发请求都 miss难道要向上游发 1000 次请求Cacher接口给每个 key 提供了一个原子标记internal/weather/cache.goif entry : s.Cacher.Get(cacheKey); entry ! nil { s.serveFromCache(w, entry) return } if !s.Cacher.SetInProgressIfNotExists(cacheKey) { entry, _ : s.Cacher.WaitForCompletion(cacheKey, 12*time.Second) s.serveFromCache(w, entry) return }上面这几行干的事是第一个拿到标记的 goroutine 当 leader 真去拉数据其余 999 个当 follower阻塞等 leader 写回结果最多等 12 秒。这是经典的请求合并request coalescing上游 API 实际只收到 1 次请求1000 个并发拿到的是同一份数据。设计权衡follower 必须等 leaderleader 慢了大家一起超时但比起 1000 个请求同时打爆上游 API 配额集中等待的代价小得多。第二层缓存响应直接进内存key 里有你 IP 和小时桶第一层缓存的是原料上游 JSON第二层缓存的是成品渲染完的终稿。响应缓存是内存 LRUTTL 10 分钟key 长这样bucket : time.Now().Unix() / int64(DefaultCacheInterval.Seconds()) return fmt.Sprintf(%s:%s%s:%s:%s:%d, ua, host, uri, ip, lang, bucket)上面这几行干的事是把 user agent、host、URI、客户端 IP、语言和小时桶当前时间戳除以 3600拼成 key——同一小时内、同一 IP、同一 URI 的请求key 完全相同。命中时直接把缓存的 body 和 header 原样写回去耗时是微秒级。这就是二次请求 1~5ms 的来源。小时桶的粒度是权衡点太短比如 5 分钟key 变太快命中率掉太长比如 1 小时以上用户拿到的是陈旧的渲染结果。1 小时卡在天气更新频率和体验之间。另外注意 key 里有 IP——两个不同 IP 的用户查同一个城市可能因为单位偏好不同而各自命中各自的缓存看起来像 bug其实是设计。还有一个后门URL 加debug参数会强制跳过缓存走完整流程。网关分流部分视图转给后端 worker还有一层容易被忽略的架构Go 版其实是网关。internal/uplink/uplink.go 会把部分请求类型按 view 分流到 4 个后端 worker——PNG 图片走 client4v2 视图走 client2其余走 client1。worker 返回的响应在网关侧缓存 17~25 分钟的随机 TTL设计权衡随机 TTL 是防雪崩的经典手法——如果所有 key 用统一 TTL它们会在同一秒集中过期瞬间打爆下游随机 17~25 分钟把过期时间打散。把渲染逻辑放在后端 worker换来的是新格式可以灰度推进上游格式变了改 worker 就行网关不用动。代价是多一跳内网转发排查问题时要看两边日志。5 分钟验证两层缓存自己跑一遍curl -s wttr.in/Beijing?formatj1 | head time curl -s wttr.in/Beijing?formatj1 /dev/null time curl -s wttr.in/Beijing?formatj1debug /dev/null上面这几行干的事是第一次请求走完整链路第二次命中响应缓存第三次用debug强制绕过缓存。拿time输出的 real 值对比——正常应该是几百毫秒 → 1~5ms → 回到几百毫秒。如果你看到的差距在百倍量级四层缓存里至少两层在你眼前真实工作过。缓存不是玄学是可测量、可绕过的工程层——wttr.in 把这一点做到了每个请求都能验证。【免费下载链接】wttr.in:partly_sunny: The right way to check the weather项目地址: https://gitcode.com/gh_mirrors/wt/wttr.in创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →