有一年我在东南亚旅行,住处的网络很不稳定。打开自己的博客要等三秒,明明只是一篇静态文章。那三秒里,浏览器先等待 DNS,再建立连接,再协商 TLS,最后才拿到一个几乎从不变化的 HTML 文件。我当时正在研究边缘部署,忽然意识到:我优化的是服务器响应时间,用户感受到的却是整段抵达过程。
边缘计算很迷人,因为它承诺把计算放得离用户更近。但「更近」本身不是目的。真正的问题是:在用户开始阅读之前,有多少不可避免的等待?以及,哪些等待其实是我们自己制造出来的?
延迟是一笔预算,不是一张排行榜
从浏览器发出请求到像素出现,时间大致花在这些地方:
- DNS 解析与连接建立:几十到几百毫秒;
- TLS 握手:一到两个往返;
- 网络传输:由物理距离、拥塞和协议决定;
- 服务端处理:从几毫秒到几秒不等;
- 下载与解析 HTML、CSS、字体和 JavaScript;
- 浏览器布局、绘制,以及主线程上的脚本执行。
边缘节点能显著影响其中的一部分,尤其是连接建立与服务端处理。它不能消除光速,也不能修复一个 2MB 的首屏 JavaScript 包。把数据库放到三十个区域,如果没有解决查询的 N+1 问题,用户依然可能在等同一张慢表。
所以我更愿意把性能当成预算来管理:
目标:4G 移动网络下 p75 LCP < 2.0s
连接与 TLS ~ 300ms
TTFB < 500ms
首屏 HTML 下载 < 150ms
关键 CSS 已内联
字体 1 个可变字重,swap
首屏 JS < 20KB(或不加载)
这些数字不是普适标准,而是一个团队对「足够快」的共同定义。没有预算,优化就会变成零散的技巧;有了预算,才知道下一步该删什么。
静态优先,不是技术保守
这个博客本身是纯静态的。文章在构建时生成,部署到 Cloudflare Pages,页面没有运行时数据库,也没有服务端渲染。唯一必须运行的客户端 JavaScript,是用户主动切换主题时的一小段逻辑。
这不是因为动态能力不好,而是因为内容站点的绝大部分请求,根本不需要在请求发生时计算。 同一篇文章在一天内被一千个人阅读,生成一次就够了。构建时的确定性换来了运行时极低的复杂度和极小的故障面。
缓存策略也可以因此变得非常简单:
Cache-Control: public, max-age=0, must-revalidate
对 HTML 使用较短的校验缓存,让新文章尽快出现;对带内容哈希的 CSS、JS 和字体使用长缓存:
Cache-Control: public, max-age=31536000, immutable
真正重要的不是某个响应头模板,而是资源是否拥有稳定的身份。文件名里没有内容哈希的脚本,一旦设置长缓存,就在为未来的事故埋线;而静态资源的不可变缓存,只有在构建产物可预测时才有意义。
边缘也有边界
边缘最容易卖给人的故事是「把服务器搬到用户旁边」。实际工程里,你还要面对:
- 数据一致性。 用户刚提交的评论,是否能在下一次请求里立刻看到?多区域写入会把这个简单问题变成分布式系统问题。
- 冷启动与运行时。 边缘函数的冷启动通常很短,但并不是零;当函数依赖较大的包或需要初始化数据库连接时,开销会重新出现。
- 可观测性。 请求散落在许多区域,日志、追踪和错误聚合必须跟上。否则你只是把问题分散到了更难排查的地方。
- 成本模型。 边缘请求、出口流量和计算时间可能分别计费。一个在单区域便宜的架构,不一定在边缘便宜。
- 安全边界。 把鉴权、限流和密钥管理分发到更多节点,意味着更多需要审计的表面。
这些不是反对边缘的理由,而是使用它的前提。边缘适合那些读多写少、状态轻、地理位置敏感的路径:静态资源、个性化但不敏感的重写、A/B 分流、图片变换、地理路由。至于需要强一致写入的核心事务,把它留在离数据最近的地方,通常更诚实。
| 场景 | 适合放在边缘吗 | 原因 |
|---|---|---|
| 静态文章与图片 | 是 | 内容不变,缓存收益高 |
| 地理重定向 | 是 | 决策轻,靠近用户 |
| 个性化推荐 | 视情况 | 延迟低,但依赖数据新鲜度 |
| 账户写入与支付 | 通常否 | 一致性与审计优先 |
| 大规模图像转码 | 视情况 | 算力与成本需要单独评估 |
测量用户真正经历的那一段
如果只看服务器指标,边缘会显得无所不能。可用户感知的是从点击到可读的时间。要接近这个真实体验,至少需要看:
- TTFB 的分布,而不是平均值。 p75 和 p95 暴露的是长尾,长尾才决定用户会不会离开。
- 分地区的 RUM 数据。 平均值会把网络条件截然不同的人混在一起。
- 实验室之外的设备。 一台中端安卓手机在 4G 网络上的表现,比办公室里的光纤 MacBook 更接近大多数访问。
- 首次与再次访问的差异。 缓存命中率、Service Worker、字体缓存都会改变结果。
在命令行里,我经常用一条简单的请求确认边缘是否按预期工作:
curl -s -o /dev/null \
-w 'dns=%{time_namelookup}s connect=%{time_connect}s tls=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n' \
https://caz-blog.pages.dev/
它不能替代真实用户监控,但能让抽象的「边缘加速」变成一个可以观察的数字。如果 DNS 仍然要 300 毫秒,那么把所有逻辑搬到三十二个区域也不会让首屏提前一秒。
更短的等待,而不是更快的机器
边缘最吸引人的地方,不是配置面板里不断增加的节点数量,而是它迫使我们重新画出请求的路径。哪些计算必须发生在请求时?哪些结果可以提前生成?哪些状态可以从关键路径上移走?哪些 JavaScript 根本不必发送?
这些问题在单区域应用里同样成立,只是边缘让它们变得更明显。真正的性能工作从来不只是换一个更快的服务器;它是删掉一段不必要的旅程,让内容更早地出现在用户眼前。
以后再看到「部署到边缘,延迟降低 80%」这样的标语,我会先问:降低的是哪一段?用户能感知到吗?为此多出来的复杂性,值得吗?
如果答案清楚,边缘就是一把好工具。如果答案含糊,那 80% 可能只是把等待从一个地方挪到了另一个地方。