33.5 边缘计算
一句话
边缘计算省的不是“计算时间”,是用户到中心的那一次往返;收益只有两个来源——数据就近与少一次往返。把计算下沉到边缘、数据仍回源中心,等于白搬:省下的一跳被回源的一跳抵消。
三层部署位置
| 层 | 距用户 RTT(量级) | 容量 | 数据 | 典型用途 |
|---|---|---|---|---|
| 边缘(CDN 节点 / 边缘函数) | 10–30 ms | 小(单节点几十到几百请求/秒) | 只读、可下发、可容忍陈旧 | 静态资源、AB 配置、名单校验、个性化缓存命中 |
| 区域(Region) | 40–80 ms | 中 | 主副本或只读副本 | 主业务服务、区域级读模型 |
| 中心(中心机房) | 100–200 ms | 大 | 权威数据 | 写路径、强一致事务、跨区域汇总 |
(RTT 是量级估算,实际取决于物理距离与运营商互联,用你自己的拨测数据替换。)
延迟是“可加”的:一个矩阵看清真相
设延迟模型为:
有效延迟 = 用户到计算层 RTT + |计算层 RTT − 数据层 RTT| + 处理时间
(第二项是“计算层回源取数据”的那一跳,用 RTT 差近似。)
package main
import (
"fmt"
"math"
)
type stage struct {
name string
rtt float64
}
// 从用户视角的三层 RTT(量级)
var stages = []stage{{"边缘", 20}, {"区域", 60}, {"中心", 150}}
type feature struct {
name string
budget float64 // 延迟预算(ms)
data string // 数据所在层
}
var features = []feature{
{"静态内容 / AB 配置", 50, "边缘"},
{"商品列表(区域缓存)", 80, "区域"},
{"个性化推荐(区域画像)", 120, "区域"},
{"风控名单校验", 100, "边缘"},
{"下单(中心库存/订单)", 300, "中心"},
{"支付回调(中心账务)", 500, "中心"},
}
func rttOf(name string) float64 {
for _, s := range stages {
if s.name == name {
return s.rtt
}
}
return 0
}
// 计算层 compute、数据层 data 时的有效延迟
func effective(compute, data string) float64 {
c, d := rttOf(compute), rttOf(data)
return c + math.Abs(c-d) + 2 // +2ms 处理时间
}
func main() {
fmt.Println("=== 计算层 × 数据层:有效延迟(ms),✓=满足预算 ✗=超预算 ===")
fmt.Printf("%-24s %-6s", "功能", "数据")
for _, s := range stages {
fmt.Printf(" %-14s", s.name)
}
fmt.Println()
for _, f := range features {
fmt.Printf("%-24s %-6s", f.name, f.data)
for _, s := range stages {
v := effective(s.name, f.data)
mark := "✗"
if v <= f.budget {
mark = "✓"
}
fmt.Printf(" %8.0f%s ", v, mark)
}
fmt.Println()
}
fmt.Println("\n=== 数据下沉到边缘,能省多少 ===")
fmt.Printf("%-24s %-10s %-10s %-8s %s\n", "功能", "当前最优(ms)", "下沉后(ms)", "收益", "前提")
for _, f := range features {
cur := effective(f.data, f.data)
edgeData := effective("边缘", "边缘")
switch f.data {
case "边缘":
fmt.Printf("%-24s %-12.0f %-12.0f %-8s %s\n", f.name, cur, edgeData, "—", "已经下沉")
case "中心":
fmt.Printf("%-24s %-12.0f %-12s %-8s %s\n", f.name, cur, "—", "—", "不可行:写路径必须回中心")
default:
fmt.Printf("%-24s %-12.0f %-12.0f %-8.0f %s\n", f.name, cur, edgeData, cur-edgeData,
"数据可下发且能容忍陈旧")
}
}
}
本机实测(macOS,Apple M1,Go 1.27):
=== 计算层 × 数据层:有效延迟(ms),✓=满足预算 ✗=超预算 ===
功能 数据 边缘 区域 中心
静态内容 / AB 配置 边缘 22✓ 102✗ 282✗
商品列表(区域缓存) 区域 62✓ 62✓ 242✗
个性化推荐(区域画像) 区域 62✓ 62✓ 242✗
风控名单校验 边缘 22✓ 102✗ 282✗
下单(中心库存/订单) 中心 152✓ 152✓ 152✓
支付回调(中心账务) 中心 152✓ 152✓ 152✓
=== 数据下沉到边缘,能省多少 ===
功能 当前最优(ms) 下沉后(ms) 收益 前提
静态内容 / AB 配置 22 22 — 已经下沉
商品列表(区域缓存) 62 22 40 数据可下发且能容忍陈旧
个性化推荐(区域画像) 62 22 40 数据可下发且能容忍陈旧
风控名单校验 22 22 — 已经下沉
下单(中心库存/订单) 152 — — 不可行:写路径必须回中心
支付回调(中心账务) 152 — — 不可行:写路径必须回中心
读数:
| 现象 | 含义 |
|---|---|
| 数据在中心时,计算放边缘/区域/中心的有效延迟都是 152ms | 计算下沉但数据回源,省下的一跳被回源抵消,白搬 |
| 数据在区域时,计算放边缘 62ms = 放区域 62ms | 边缘只打平,不赢;因为还是要回区域取数据 |
| 数据在边缘时(静态内容、名单),计算放边缘 22ms,放中心 282ms | 只有“数据也在边缘”时,边缘才真正赢 |
| 收益列:只有“数据可下沉”的功能有收益 | 下沉数据要付一致性代价(下一节) |
🔑 结论:边缘计算的决策不是“这个函数要不要放边缘”,而是“这份数据能不能放到边缘”。数据能下沉(静态资源、配置、名单、可容忍陈旧的读模型)→ 放边缘有 2–3 倍延迟收益(62ms → 22ms);数据必须回中心(写路径、强一致读)→ 放边缘毫无收益,只多了运维复杂度。
数据下沉的收益与代价
| 数据类型 | 能下沉吗 | 收益 | 代价 |
|---|---|---|---|
| 静态资源(图片/JS/CSS) | ✅ 完全 | 命中即 20ms 级 | 缓存失效传播(9.1 边缘缓存原理) |
| 配置 / AB 实验 | ✅ | 决策在边缘完成 | 变更要下发到所有节点 |
| 名单(黑名单/白名单) | ✅ | 校验本地化 | 名单更新有延迟窗口,需要版本号 |
| 商品详情(读多写少) | ⚠️ 可缓存 | 命中率高时收益大 | 价格/库存不能缓存,否则会卖超 |
| 用户画像(读多写少) | ⚠️ 按需 | 推荐结果可预计算 | 隐私与合规(数据出境、地域限制) |
| 库存 / 订单 / 账务 | ❌ | 无 | 强一致,写必须回中心 |
⚠️ 一个常见的坑:把“个性化推荐”的判断放到边缘,但推荐所需的行为数据在中心——每次都要回源,结果比放中心更慢。正确做法是“预计算推荐结果 → 下发到边缘 → 边缘只做选取”,把“计算”换成“数据下发”。
边缘部署的四个硬约束
| 约束 | 说明 | 应对 |
|---|---|---|
| 无状态、实例短命 | 边缘实例随时回收,本地磁盘不可靠 | 状态外置或纯函数 |
| 冷启动 | 边缘函数同样有冷启动,且节点更分散 | 见 33.3 Serverless / FaaS 的概率模型 |
| 发布与回滚难 | 几百个节点,灰度要按 POP 分批 | 版本化配置 + 按节点标签灰度 |
| 观测难 | 日志分散在边缘,采样与聚合成本高 | 边缘只上报聚合指标,必要时按 trace 抽样 |
反模式
| 反模式 | 后果 |
|---|---|
| 把业务函数搬到边缘,数据仍回中心 | 延迟没降,多了一套边缘部署与排障 |
| 在边缘做写操作(下单、扣库存) | 多节点并发写、冲突只能靠中心仲裁,延迟反而更差 |
| 边缘缓存价格/库存且不设 TTL | 卖超、价格错误,且用户看到的是过期数据 |
| 所有功能都上边缘 | 运维面按节点数放大,收益只集中在少数读多写少的功能 |
| 边缘只做静态缓存,不做“数据下发” | 错过了真正的大头收益(配置/名单/预计算) |
| 用边缘绕开区域的数据合规要求 | 数据落到不允许的地域,合规风险 |
自检
Q1 一个功能预算 100ms,数据在中心(RTT 150ms)。把它部署到边缘(RTT 20ms)后,有效延迟是多少?为什么“部署到边缘”没解决问题?
▸ 答案:20 + |20−150| + 2 = 152ms,和部署在中心(150 + 0 + 2 = 152)一样。因为计算层回源取数据要走“边缘→中心”的往返(约 130ms),把省下的 130ms 又花掉了。要真正解决,只有两条路:① 把数据下沉到边缘(缓存/下发),使 |计算−数据| 趋近 0;② 改业务,让这个功能不需要实时读中心数据(预计算、异步)。
Q2 为什么“个性化推荐”放在边缘常常不划算,而“推荐结果下发到边缘”却划算?
▸ 答案:因为前者每次请求仍要回中心取行为数据(一跳 130ms 左右),后者把“重计算”放在中心离线完成、只把“结果”(几十 KB 的候选列表)下发到边缘。边缘只做“按用户选取 + 排序截断”,数据在本地,|计算−数据| ≈ 0。这正是本节的核心判据:边缘适合“数据已在本地”的计算,不适合“需要回源取数”的计算。
Q3 库存查询能放边缘吗?如果业务要求“显示实时库存”,该怎么办?
▸ 答案:不能把可写的库存放边缘(多节点并发写、超卖),也尽量不要把“实时库存”原样缓存。可行做法:① 边缘只缓存“是否有货”这种粗粒度状态(TTL 几秒),下单时以中心为准,前端把“有货”当参考;② 用预占(reserve)代替“先查后扣”,把一致性压力放到中心的原子操作上(29.3 的库存聚合);③ 接受“边缘显示略有延迟 + 下单时校验”,并在 UI 上明确“以结算页为准”。
Q4 一个团队把 20 个边缘函数部署到 300 个 POP,结果发布一次要 40 分钟,故障排查靠人肉登录节点。问题出在哪?
▸ 答案:出在把边缘当成“小号中心”来运维。边缘的运维模型必须是“声明式 + 版本化 + 分批灰度”:配置与函数版本都进 git,按 POP 标签分批推送,每批观察错误率与延迟再继续;观测上只上报聚合指标(每 POP 的 p99、错误率、回源率),排障靠 trace 抽样而不是登录节点。否则节点数一多,发布与排障的成本会超过边缘带来的延迟收益。
关联
- 前置:33.3 Serverless / FaaS、9.1 边缘缓存原理
- 并列:33.2 微服务部署、16.5 最终一致性
- 下游:33.6 BFF 与 API 组合
- 待补:69.4 边缘计算与断网续传、72.4 多地域部署与就近接入(未写,无链接)