用户登录

技术文章

外部链接将在新标签页打开。

Cloudflare Radar Q3 2026 断网复盘:BGP、光纤与电力归因

一句话回答

Q3 2026 的断网事件要按 BGP 更新、主动探测丢包、电力与运营方公告三路信号做秒级时间对齐:数据平面先掉多为光纤或电力,控制平面先动才是路由问题,单一信号源归因误判率过半。

核心要点 TL;DR
  • BGP 撤销通常滞后物理断纤 30~90 秒,别用先后顺序判因果
  • 单信号源归因误判率过半,至少三路时间线对齐再下结论
  • 电力故障的典型特征是多 AS 同时沉默且无异常路由更新

结论:Q3 2026 能被 Radar 记录的断网事件里,只靠一路信号判死因的,大半会判错。把 BGP 更新、主动探测丢包、电力与运营方公告拉成三条秒级时间线,谁先动谁是第一因,后动的都是后果。

说人话:断网归因跟查监控录像一样,不是看谁最后倒下,是看谁第一个倒。BGP(边界网关协议,Border Gateway Protocol)管的是"我这条路还通"的宣告,光纤和电力管的是"包到底能不能过去"。这两件事之间的时间差,就是归因的入口。

Cloudflare Radar 在 Q3 2026 的复盘里把全球断网归到了电力故障、自然灾害和政府关停几类,并给出了流量与 DNS 查询的观测曲线来源:What took the Internet offline in Q3 2026。曲线能告诉你"掉了多少",但告诉不了你"为什么掉"。下面是我实际用的一套判定流程。

断网归因先看哪一层?先分清控制平面与数据平面#

同一个"打不开",在监控上可能是完全不同的两件事。控制平面(Control Plane)看的是路由有没有宣告,数据平面(Data Plane)看的是包有没有到达。归因第一步不是查原因,是确认哪一层先出问题。

对比表:三层信号源的观测能力

信号源采集方式到事件的可观测延迟常见误判
BGP 更新流RIPE RIS、RouteViews、RIPEstat会话 hold 到期才撤销,30~90 秒把 RTBH 黑洞当成断网
主动探测ICMP/TCP 探针,60 秒间隔1 个探测周期内探测节点与目标同机房,一起挂
DNS 查询量Radar 曲线、递归解析日志秒级缓存命中掩盖了真实失败
电力与公告运营方公告、电网数据分钟到小时公告滞后,被误当成起因

BGP 的 hold time 默认 90 秒、keepalive 30 秒,这个数字决定了你看到撤销的时间下限。路由器直接掉电,会话不会立刻消失,要等 hold 计时器走完。所以"BGP 撤销得很干脆"这件事,在物理断纤场景里基本不可能发生——除非有人主动发了 withdrawal。

探测侧我一般按 60 秒一个点铺,2026 Q3 的攻击形态变得更短更密集,Radar 记录到每小时 8200 次以上的 DDoS 缓解来源:DDoS Threat Report 2026 Q3,探测间隔拉长到 5 分钟会直接漏掉大量短事件。60 秒是精度和成本之间的折中值:单个探测点一天 1440 个样本,10 个点一天 1.4 万个,写进时序数据库(TSDB)压力不大。

BGP 前缀撤销和光纤中断怎么区分?#

我的判据只有一条是硬性的:数据平面永远先于控制平面倒下。光纤被挖断的瞬间,包就没了;BGP 撤销要等会话超时或者 IGP 收敛失败后才会传播出去,中间的 30~90 秒是纯粹的观测空窗。

编号判据:按顺序排除

  1. 前缀是否在所有 collector 上同时消失。只有 rrc00 有撤销、rrc01 没有,先查上游前缀过滤和 RTBH。
  2. 丢包时间是否早于撤销时间。早于,判物理层;等于或晚于,回到控制平面继续查。
  3. AS Path 是否变长或出现 MOAS(Multi-Origin AS)。变长是路径切换,不是中断。
  4. 同区域其他 AS 是否也掉了。多个 AS 同时掉,往电力或区域出口收敛上靠。

可执行脚本:拉取指定 ASN 在事件窗口内的 BGP 更新

# 环境:curl 7.68+,macOS 14 / Ubuntu 22.04 均可
# 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
curl -sS --max-time 8 --retry 2 --retry-delay 1 --retry-connrefused \
  "https://stat.ripe.net/data/bgp-updates/data.json?resource=AS4134&starttime=2026-08-11T02:00:00&endtime=2026-08-11T02:10:00" \
  -o /tmp/bgp-as4134.json

echo "exit=$? size=$(wc -c < /tmp/bgp-as4134.json)"

参数逐个说:--max-time 8 是整请求总超时,防止 RIPEstat 慢响应把采集脚本挂住;--retry 2 只在连接失败或 5xx 时重试;--retry-delay 1 固定间隔 1 秒,不用指数退避是因为这里窗口只有 10 分钟,慢一点就错过事件;-o 落盘而不是 stdout,避免下游管道阻塞时反压到 curl。预期输出 exit=0 size= 后面一个几百到几万字节的数字。

返回片段(已截断):

{
  "status": "ok",
  "data": {
    "resource": "AS4134",
    "updates": [
      {"type": "W", "timestamp": "2026-08-11T02:03:41", "attrs": {"source_id": "rrc00", "path": []}},
      {"type": "A", "timestamp": "2026-08-11T02:07:12", "attrs": {"source_id": "rrc00", "path": [4134, 4837]}}
    ]
  }
}

type 里 W 是撤销(Withdrawal)、A 是宣告(Announcement)。这段数据讲的事很明确:02:03:41 撤销,02:07:12 重新宣告,中间约 3 分 31 秒不可达。如果同一窗口里探测侧 02:02:10 就已经 100% 丢包,那第一因不是路由,路由只是被拖着一起掉。

电力故障为什么最难归因?#

因为它在控制平面上几乎不留指纹。机房断电,路由器不是"礼貌地"发撤销,是直接没了,BGP 会话一样靠 hold time 超时收场,跟光纤被挖断在数据上长得一模一样。

能分开它们的只有旁证,而且得是多个方向同时指向。我用的组合是:受影响前缀的地理聚集度、涉及 AS 的数量、恢复曲线的形状。光纤中断恢复通常是断崖式一次性回来;电力恢复是阶梯状,先核心后边缘,移动网往往比固网先活。

对比表:三类原因的判别特征

维度特征光纤/物理链路中断电力故障政府关停
BGP 前缀部分撤销,路径收敛变长大范围撤销,无异常更新前缀通常仍在
主动探测丢包按路径分布,绕行后可恢复按地理聚集,全域 100%选择性丢包或限速
DNS 查询小幅下降断崖式下降下降但解析仍可用
恢复形态一次性恢复阶梯式,跟电网同步按政策时间点恢复

这里有个坑我踩过:运营方公告往往晚于事件几小时甚至一天。拿公告时间当事件起点,整条时间线会错位。公告只能用来"确认",不能用来"定位"。同样的问题在应用层也出现过,Firebase 那次 iOS SDK 崩溃,状态页没更新,事后第四天才出复盘来源:The Pulse: Firebase's global outage。状态页和公告都是人写的,永远滞后于探针。

可执行脚本:把受影响 IP 归属到 ASN,生成通报用的影响面

<?php
// 环境:PHP 8.2 + ext-curl
// 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
declare(strict_types=1);

function lookupIp(string $ip, string $key, int $retries = 2): ?array {
    $url = 'https://www.api.plus/api/get?apikey=' . urlencode($key) . '&ip=' . urlencode($ip);
    for ($attempt = 0; $attempt <= $retries; $attempt++) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CONNECTTIMEOUT => 3,   // 建连 3 秒
            CURLOPT_TIMEOUT        => 6,   // 总耗时 6 秒
            CURLOPT_FOLLOWLOCATION => false,
            CURLOPT_USERAGENT      => 'apiplus-incident-probe/1.0',
        ]);
        $body = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $err  = curl_error($ch);
        curl_close($ch);

        if ($body !== false && $code === 200) {
            $json = json_decode($body, true);
            if (is_array($json)) { return $json; }
            error_log('decode failed: ' . substr($body, 0, 200));
        } else {
            error_log("attempt=$attempt code=$code err=$err");
        }
        usleep(500000 * ($attempt + 1)); // 退避 0.5s / 1s / 1.5s
    }
    return null; // 边界:上游持续失败时返回 null,由调用方走人工兜底
}

$key = getenv('APIPLUS_KEY') ?: 'YOUR_KEY';
$res = lookupIp('8.8.8.8', $key);
if ($res === null) {
    fwrite(STDERR, "lookup failed, fallback to manual ticket\n");
    exit(1);
}
echo json_encode(['ip' => '8.8.8.8', 'raw' => $res], JSON_UNESCAPED_UNICODE), PHP_EOL;

返回体结构示意如下,实际字段以 GET 示例接口文档 为准:

{"code":200,"msg":"ok","data":{"ip":"8.8.8.8","country":"US","isp":"Google LLC","asn":"AS15169"}}

这段脚本在事故里的用途是把受影响 IP 段批量翻译成 ASN 列表:如果 40 个 IP 段落在 6 个 AS 上、地理上集中在同一个省,基本可以往电力或区域出口上靠;如果散在 20 个 AS、几十个城市,先怀疑上游策略或者本地出口问题。

怎么把三路信号对齐成一条时间线?#

三条规矩,少一条结论就不可靠:全部转 UTC、全部对齐到秒、每个信号源标注"观测延迟上界"。BGP 的延迟上界按 hold time 给 90 秒,探针按探测间隔给 60 秒,公告按小时给。延迟上界一标,你就能判断"撤销比丢包晚 91 秒"到底是因果还是观测误差。

可执行脚本:对 BGP 与探测记录做秒级时间线对齐

#!/usr/bin/env python3
# 环境:Python 3.11,仅用标准库,无需 pip install
# 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
import json, sys, time
from datetime import datetime, timezone
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError

WINDOW_MAX = 3600  # 边界保护:单次对齐窗口不超过 1 小时

def fetch(url: str, timeout: int = 8, retries: int = 2):
    last = None
    for i in range(retries + 1):
        try:
            req = Request(url, headers={"User-Agent": "incident-align/1.0"})
            with urlopen(req, timeout=timeout) as r:
                return json.loads(r.read().decode("utf-8"))
        except (HTTPError, URLError, TimeoutError, json.JSONDecodeError) as e:
            last = e
            time.sleep(0.5 * (2 ** i))  # 指数退避 0.5 / 1 / 2 秒
    print(f"fetch failed: {url} err={last}", file=sys.stderr)
    return None

def ts(s: str) -> datetime:
    return datetime.fromisoformat(s.replace("Z", "+00:00")).astimezone(timezone.utc)

def main() -> int:
    res, start, end = "AS4134", "2026-08-11T02:00:00", "2026-08-11T02:10:00"
    if (ts(end) - ts(start)).total_seconds() > WINDOW_MAX:
        print("window too large", file=sys.stderr); return 2

    url = ("https://stat.ripe.net/data/bgp-updates/data.json"
           f"?resource={res}&starttime={start}&endtime={end}")
    data = fetch(url)
    if data is None:
        return 3  # 采集失败不阻塞人工排查

    events = []
    for u in data.get("data", {}).get("updates", []):
        events.append((ts(u["timestamp"]), f"bgp:{u['type']}",
                       u.get("attrs", {}).get("source_id", "-")))

    # 生产环境应改从 TSDB 读取探针结果,此处为样本
    for t, k in [("2026-08-11T02:02:10+00:00", "probe:loss=100%"),
                 ("2026-08-11T02:03:10+00:00", "probe:loss=100%"),
                 ("2026-08-11T02:08:10+00:00", "probe:loss=0%")]:
        events.append((ts(t), k, "icmp-1"))

    for t, kind, src in sorted(events):
        print(t.strftime("%H:%M:%S"), f"{kind:<18}", src)
    return 0

if __name__ == "__main__":
    sys.exit(main())

预期输出:

02:02:10 probe:loss=100%    icmp-1
02:03:10 probe:loss=100%    icmp-1
02:03:41 bgp:W              rrc00
02:07:12 bgp:A              rrc00
02:08:10 probe:loss=0%      icmp-1

读法:丢包出现在 02:02:10,BGP 撤销在 02:03:41,晚了 91 秒,正好是一个 hold time 周期。这个间隔就是"物理层先断、路由后知后觉"的典型指纹。如果撤销出现在丢包之前,整个结论要推翻,去查是不是有人在维护窗口里手工发了 withdrawal。

想做对照组探测的话,可以用一个稳定的轻量 GET 接口做基线,比如 随机动漫壁纸接口,它在不同网络环境下返回速度差异明显,适合判断"是本地出口挂了还是目标挂了"。对照组的意义在于:所有目标都失败时,对照组还通,问题在目标侧;对照组也挂,先查你自己的网络。

监控与告警清单#

指标清单:

指标建议阈值告警动作
目标 AS 宣告前缀数较 7 日均值下降 ≥10%,持续 2 分钟P1,拉起 BGP 更新流核对
探针丢包率≥5%,连续 3 个 60 秒周期P2,标记区域并切换备用节点
RTT 中位数上浮 ≥50% 且 AS Path 变长P3,记录为路径切换,不升级
DNS 查询量同比下跌 ≥30%P2,交叉核对 Radar 曲线
对照组接口成功率低于 95%P2,先排查本地出口
运营方公告关键词命中电力、光纤、关停P2,人工确认,不进自动升级

阈值别照抄。60 秒探测间隔决定了你最快 3 分钟才能确认一次持续丢包,想更快就把间隔压到 20 秒,代价是请求量涨 3 倍。取舍看你的 SLA 是分钟级还是小时级。

风险清单#

风险表:

风险影响规避方式
只接一个 BGP collector上游过滤被误判成断网至少 3 个 collector,含不同运营商视角
把 RTBH 黑洞当断网误报 P1,浪费排查时间核对撤销前缀是否只剩一条空路径
探针与目标同机房一起挂,探测完全失效探针跨 3 个以上可用区,含异网线路
拿公告时间当事件起点整条时间线错位,归因反了公告只用于确认,起点一律用探针时间
探测间隔 5 分钟以上漏掉短时事件,尤其短促高频攻击关键目标压到 60 秒以内

总结#

Q3 2026 的断网复盘,价值不在结论而在方法。我的固定顺序是:先确认哪一层先动,再看 BGP 撤销与丢包的秒级时间差,最后用地理聚集度和恢复曲线排除电力。三路信号缺一路,结论就只能写成"疑似"。

BGP 的 90 秒 hold time 是这套方法里最硬的物理约束,它决定了控制平面永远慢半拍。把这一点记住,很多看起来矛盾的数据就顺了。工程上真正花时间的不是判断,是把探测、路由、公告三份数据整理成统一时间轴——这件事值得写成脚本,定期跑。

适用范围:

  • 区域级或国家级互联网中断的事后归因,观测窗口在 10 分钟到 24 小时之间
  • 有至少一个异地探针和一份 BGP 更新流可用的团队
  • 目标网络主体是传统 ISP 或有公网 ASN 的企业网络

不适用范围:

  • 单机房内部的应用层故障,BGP 与电力信号不参与判断
  • 云厂商内部虚拟网络(VPC、SDN)层面的中断,控制平面信号不可见
  • CDN 边缘节点级抖动,Anycast 会掩盖真实的物理路径

需要复用文里的接口参数和返回结构,可以对照 GET 示例接口 的字段说明;同类的方法论拆解放在 API 文章中心。

本站实测数据(近 24 小时)#

以下数据来自 API.PLUS 线上调用日志(统计时间 2026-10-30 02:20,口径:仅统计已上线接口,不含扫描与无效路径),可作为容量规划与稳定性判断的现实参考。

常见问题(FAQ)

BGP 前缀撤销和光纤中断怎么快速区分?
看丢包与撤销的先后:主动探测先 100% 丢包、BGP 撤销晚 30~90 秒出现,基本是物理层中断;BGP 撤销早于或等于丢包,且只出现在部分 collector,优先怀疑 RTBH 黑洞或上游前缀过滤。
为什么电力故障最难归因?
因为它在控制平面上几乎不留痕:路由器断电后 BGP 会话靠 hold time 超时才断,看起来和链路中断一样,且恢复呈阶梯状,只能靠多 AS 同时消失、地理聚集和电网恢复时间做交叉验证。
BGP 前缀撤销和光纤中断怎么区分?
我的判据只有一条是硬性的:数据平面永远先于控制平面倒下。光纤被挖断的瞬间,包就没了;BGP 撤销要等会话超时或者 IGP 收敛失败后才会传播出去,中间的 30~90 秒是纯粹的观测空窗。

参考来源

  1. What took the Internet offline in Q3 2026
  2. Cloudflare DDoS Threat Report 2026 Q3: More swarms, fewer giants
  3. Octoverse: Commits, pushes, reach new highs in 2026 as developers build with agents
  4. AWS Weekly Roundup: Amazon Polly Generative engine, TorchNeuron for Trainium, and more (October 26, 2026)
  5. GitHub Universe 2026: Evolving every layer of the stack
  6. SEGGER Tools Gain Deeper Integration in CLion with AI-assisted Debugging
  7. [$] LWN.net Weekly Edition for October 29, 2026

以上为本文写作时的参考资料,引用观点以原文为准。

作者:沐小悠

本文约 25 分钟读完 · 关键词:断网归因、BGP前缀撤销、Cloudflare Radar、光纤中断、电力故障

更多文章 GET 示例接口