X.COM 架构白皮书 v15.0:系统设计与技术架构全景

IT 技术 90 阅读 更新于 2026-09-04 07:48

系统设计与技术架构全景 —— 从客户端到数据中心的完整拆解:前端、设计系统、网关、微服务、时间线排序、存储、流计算、边缘网络与安全;七大专题深挖:搜索、通知、DM 消息、媒体管线、ML 平台、实验与变现;五大扩展专题:内容治理、多区域容灾、计数系统、国际化与开放平台;并附 19 个可复现的实战教程(T1–T19),每篇按「原理 → 代码 → 要点 → 动手练习」组织。读多写少、缓存优先、优雅降级,是贯穿全文的三条主线。

文档信息

项目内容
文档版本v15.0
维护团队Core Infra
章节 / 教程27 章 + 19 教程
密级INTERNAL
上次评审2026-07-28

卷首指标(原页为数字滚动动画,此处静态化)

  • 日活跃用户(可货币化 DAU):2.52 亿
  • 峰值推文 / 秒:9400
  • 在线微服务:1200+
  • 边缘 POP 城市:40+
  • 近 12 个月可用性:99.96%

01 · 总体架构

SYSTEM MAP

五层模型 · 读写分离 · 多区域

X 是典型的读多写少社交系统,读写比约 100:1。整体采用五层模型:请求自客户端经边缘 POP 进入,由统一网关完成鉴权与路由后进入领域服务层;数据按访问模式拆分至事务库、KV、缓存与对象存储;ML 平台横向贯穿,为召回与排序提供特征和推理能力。

分层系统图

  • L1 · 客户端:iOS · Swift · Android · Kotlin · Web SPA · React · 第三方 API
  • L2 · 边缘层:Anycast POP · WAF · Bot 评分 · TLS 1.3 · HTTP/3 · 边缘缓存 · 速率限制
  • L3 · 接入层:GraphQL 网关 · WebSocket 通道 · REST v2 · OAuth2 · JWT
  • L4 · 领域服务:推文 tweetypedia · 时间线 mixer · 社交图谱 · 推荐 cr-mixer · 通知 · DM · 媒体 · 搜索
  • L5 · 数据层:MySQL 分片 · Manhattan KV · Pelikan 缓存 · Blobstore · Kafka · Elasticsearch
  • ◈ ML 平台横向贯穿 L3–L5:特征仓库 · GPU 训练集群 · 在线推理(<10ms / 样本)

五条设计原则

  • CACHE-FIRST · 缓存优先:读路径默认命中缓存;一切可重建的数据皆可丢弃。
  • EVENTUAL · 最终一致:观众视角容忍秒级延迟;作者视角保证 read-your-writes。
  • HYBRID FANOUT · 混合扇出:普通用户写扩散、超大 V 读扩散,兼顾延迟与成本。
  • GRACEFUL · 优雅降级:推荐失效回退关注流;依赖故障自动熔断与限流。
  • MOBILE-FIRST · 移动优先:面向弱网与高丢包网络设计,预取与请求合并为默认能力。

关键负载画像

接口 / 场景峰值吞吐P95 目标备注
时间线拉取420K QPS250ms缓存命中率 >95%
发布推文9.4K /s150ms触发扇出与索引双写
媒体上传2.3K /s400ms异步转码矩阵
搜索查询38K QPS300ms查询理解 + 检索分离
DM 消息96K /s200ms端到端加密路径
通知扇出1.2M 事件/sKafka 削峰

02 · Web 前端架构

WEB CLIENT

React · GraphQL · PWA

Web 端为React 单页应用:首屏由服务端预渲染 + 客户端水合,后续路由完全客户端渲染。数据层统一走GraphQL 持久化查询,只传查询哈希与变量,显著压缩弱网载荷;Service Worker 负责离线壳、静态资源缓存与 Web Push。状态管理采用轻量自研 store,配合乐观更新保证交互零等待。

TTI < 3s · 首屏 JS ≤ 180KB gz · i18n 40+ 语言 · A/B 实验 SDK · WebSocket 增量时间线

模块划分

模块技术职责
渲染框架React 18+ Concurrent流式渲染、可中断更新
数据层GraphQL / Relay持久化查询、字段订阅、缓存归一化
状态管理自研 store(不可变快照)乐观更新、失败回滚
构建Vite + SWC路由级代码分割、Tree-shaking
实时通道WebSocket时间线增量、通知、DM 打字态
离线 / 推送Service Worker + Web Push离线壳、后台同步、通知唤起

GRAPHQL · 首页时间线持久化查询

query HomeTimeline($cursor: String) {
  home { timeline(count: 20, cursor: $cursor) {
    entries { id sortIndex content { ... on Tweet {
      id text lang createdAt favoriteCount retweetCount
      author { id name verified } media { url type }
    } } }
    nextCursor
  } }
}
# 持久化查询 sha256=3f2a…c9 —— 服务端按哈希命中,仅传输变量
乐观更新:点赞 / 转推先写入本地 store 即时反馈,服务端失败则回滚并提示;弱网下写操作进入离线队列,恢复后按序重放(幂等键去重)。对应教程见T6 GraphQL 网关;长列表渲染优化见T14 虚拟长列表。

03 · 移动客户端

iOS · ANDROID

Swift · Kotlin · 模块化

双端均为模块化原生工程:feed / compose / media / dm 独立编译、独立发布。iOS 全面 Swift 并渐进迁移 SwiftUI;Android 以 Kotlin 为主,Compose 已覆盖约 60% 界面。共享层包括 Protobuf 协议模型、实验 SDK、图片管线(降采样 + 渐进式解码)与崩溃防护框架。发布采用每周版本火车 + 配置中心热修。

双端质量指标

指标iOSAndroid口径
冷启动 P751.6s2.1s中端机型实测
崩溃率0.03%0.05%会话级
下载包体积112MB78MB商店分发尺寸
帧率基准60fps60fps高端机型 120fps
启动内存320MB380MBP75

弱网策略

请求合并 Batching · 图片自适应降质 · 首屏 20 条预取 · 2G 纯文本降级 · 失败指数退避重试


04 · 设计系统

DESIGN SYSTEM

Design Token · 暗黑模式 · 无障碍

X 的界面语言以「内容即界面」为核心:极简黑白基调、单一品牌蓝点缀、信息密度高而秩序清晰。所有视觉决策沉淀为Design Token,由设计工具同步到三端(iOS / Android / Web),任何组件不允许硬编码色值与间距。

色彩 Token(亮色主题)

Token色值用途
accent#1d9bf0主行动按钮、链接、选中态(X Blue)
accentHover#1a8cd8悬停 / 按压
textPrimary#0f1419正文与标题
textSecondary#536471次要文字、时间戳
bgSecondary#f7f9f9次级背景、悬停行
divider#eff3f4分割线、边框
like#f91880点赞(品红)
retweet#00ba7c转推 / 成功(绿)
danger#f4212e删除、错误、录制中
warning#ffd400警示、订阅高亮

暗黑模式(双档)

主题背景主文本说明
Dim 暗灰#15202b#f7f9f9默认深色,低对比刺激
Lights out 纯黑#000000#e7e9eaOLED 省电,分割线 #2f3336

字体 / 间距 / 圆角

  • 品牌字体 Chirp(自研,覆盖多语言字重 12 档),Web 端按语言回退系统字体栈;正文基准15px / 1.5,字号阶梯 13 / 15 / 17 / 20 / 24 / 31。
  • 4pt 间距网格:所有留白取自 0 · 4 · 8 · 12 · 16 · 24 · 32 · 48,禁止奇数值。
  • 圆角语义:头像全圆、主按钮胶囊(9999px)、卡片 16px、输入框 8px。
  • 图标:24px 网格、2px 描边、选中态填充实心;全局统一一套线性图标库。

DESIGN TOKEN · JSON 同步源(节选)

{
  "color": {
    "accent": "#1d9bf0", "accentHover": "#1a8cd8",
    "textPrimary": "#0f1419", "textSecondary": "#536471",
    "bgPrimary": "#ffffff", "bgSecondary": "#f7f9f9",
    "divider": "#eff3f4", "like": "#f91880",
    "retweet": "#00ba7c", "danger": "#f4212e"
  },
  "space": [0, 4, 8, 12, 16, 24, 32, 48],
  "radius": { "full": "9999px", "card": "16px", "input": "8px" },
  "font": { "brand": "Chirp", "body": "15px/1.5", "scale": [13, 15, 17, 20, 24, 31] },
  "motion": { "duration": "200ms", "easing": "cubic-bezier(.2,.8,.4,1)" }
}

信息架构(IA)

页面移动端入口Web 端入口核心能力
首页 Home底部 Tab 1左侧导航 1关注流 / 推荐流切换,无限滚动
探索 Explore底部 Tab 2左侧导航 2趋势、话题、搜索一体
通知 Notifications底部 Tab 3左侧导航 3全部 / 提及 / 已验证 过滤
私信 Messages底部 Tab 4左侧导航 4DM 会话列表,E2E 加密入口
AI 助手 Grok底部 Tab 5左侧导航 5对话、图像理解、实时资讯
个人主页 Profile头像 / 侧滑左侧导航底部推文 / 回复 / 媒体 / 点赞 四页签

动效与无障碍

  • 动效:统一 200ms + ease-out 曲线;列表进入、点赞爆炸粒子、底部弹层三套标准动效;骨架屏代替转圈。
  • 对比度:正文对背景 ≥ 4.5:1(WCAG AA),大字号放宽至 3:1。
  • 触控目标:最小 44×44pt;图标按钮扩大命中热区。
  • 读屏:图片支持 ALT 文本(AI 自动建议);全部控件带语义标签;支持系统「减弱动态效果」。
组件清单(节选):Primary / Secondary / Negative 按钮、Tab 页签、Toast、底部弹层 BottomSheet、模态 Modal、骨架屏 Skeleton、无限滚动 Sentinel —— 每个组件在三端同名、同 Token、同交互规范。

05 · API 与统一网关

GATEWAY & GRAPHQL

GraphQL · 幂等 · 限流

所有流量在边缘完成 TLS 终止后进入统一 GraphQL 网关,聚合约 30 个领域服务。字段级鉴权:viewer context(登录态、屏蔽关系、敏感内容偏好)注入每个字段解析器;持久化查询白名单杜绝任意查询注入。写接口全部幂等——客户端生成 Idempotency-Key,服务端 24h 去重窗口。限流采用三级令牌桶:IP → 用户 → 设备,配额通过响应头透出(实现见教程T4)。

协议用途说明
GraphQL主站 / App 全量读路径持久化查询 + 字段级权限
REST v2开放平台、合规接口保持向后兼容与分页游标
WebSocket时间线增量 / 通知 / DM按频道订阅,断线自动补帧

幂等写入 MUTATION

mutation PostTweet($idk: ID!) {
  createTweet(input: {
    idempotencyKey: $idk,          # 客户端生成,服务端 24h 去重
    tweetText: "hello, world",
    replySettings: "everyone",
    media: { ids: ["195391…", "195392…"] }
  }) {
    tweet { id }
    ... on RateLimited { retryAfter }
  }
}

限流响应示例

HTTP/1.1 429 Too Many Requests
x-rate-limit-limit: 300
x-rate-limit-remaining: 0
x-rate-limit-reset: 1770480600
retry-after: 42
content-type: application/problem+json
{ "title": "RateLimited", "detail": "timeline.read" }

06 · 微服务拓扑

SERVICES

~1,200 服务 · 服务网格 · Snowflake

经 2023 年大规模整合后,线上约1,200 个服务:Go 主导无状态边缘与工具服务,JVM(Scala/Java)承载核心领域逻辑,Rust 用于高性能基础设施组件。服务间走自研 RPC + mTLS 服务网格,注册中心基于一致性哈希;无状态优先,一切会话态外置到 Pelikan 缓存层。

核心服务清单

服务职责关键特性
tweetypedia推文读写核心一致性裁决、双写索引与缓存
timeline-mixer时间线组装混排关注流与推荐流统一管线
fanout-service写扩散投递为每个粉丝写入收件箱
socialgraph关注 / 粉丝图谱边存储 + 实时计数
visibility可见性裁决屏蔽、限流、法律移除统一出口
cr-mixer推荐候选混合多路召回融合,已开源
reco-platform特征与模型推理GPU 推理,P99 < 10ms
notify-service通知聚合去重、合并、优先级降级(详见17)
dm-platform私信与群聊MLS 端到端加密(详见18)
media-service上传 / 转码图片即时裁剪、视频多码率(详见19)
search-service查询理解与检索热词缓存、30 天滚动索引(详见16)

Snowflake 全局 ID · 64 bit(实现见教程T2)

1

41

10

12

符号

毫秒时间戳 · 纪元 2010-11-04

机器 ID · 1024 节点

序列 · 4096/ms

单节点理论产能 4096 × 1000 ≈ 410 万 ID/秒;ID 单调递增且可反解生成时间,天然适合作为游标与分片键。部署流程:K8s + canary 1%→5%→25%→100%,指标门禁异常自动回滚。

07 · 时间线与排序引擎

TIMELINE & RANKING

重召回 · 神经排序 · 混合扇出

关注流采用混合扇出:普通用户发推后由 fanout-service 写扩散到每位粉丝的缓存收件箱;粉丝量超阈值的账号改为读扩散,拉取时实时合并。推荐流走「重召回 → 过滤 → 精排 → 混排」四级管线,端到端 P95 控制在 500ms 内。想亲手实现?见教程T1与T5。

一次首页请求的旅程(延迟预算)

  1. 8ms · 接入鉴权
  2. 22ms · 邮箱拉取
  3. 120ms · 6 路并行 · 多路召回
  4. 60ms · 可见性过滤
  5. 180ms · 48M 参数 · 神经精排
  6. 40ms · 启发式混排
  7. 60ms · 数据富化
  8. P95 ≤ 500ms · 响应返回
  9. 召回源(六路并行)

    召回路信号说明
    关注收件箱写扩散缓存基础盘,保证关注内容不缺席
    SimClusters兴趣社区相似度约 2 万个稀疏社区向量
    TwHIN异构图嵌入用户–推文–实体联合图
    RealGraph互动亲密度回复 / 点赞 / 点击加权
    Trends地域热点突发话题快速注入
    冷启动兜底热门 + 编辑精选新用户与低信号场景

    排序漏斗

    1. ~2,000 条 · 6 路并行 · 召回候选
    2. ~500 条 · 屏蔽 / 去重 / 法律 · 可见性过滤
    3. 48M 参数 · 多任务打分 · 神经精排
    4. 40 条 · 作者多样性启发式 · 首屏混排
    5. 排序模型为多任务结构,示例目标权重:点赞 1.0 / 回复 1.3 / 转推 1.2 / 关注 3.0 / 负反馈 −8.0。线上以「重度用户活跃天数」为北极星指标做 A/B 决策;混排层强制同作者连续不超过 2 条、按节奏穿插视频。

      08 · 存储体系

      DATA & STORAGE

      MySQL · Manhattan · Pelikan

      数据层按访问模式拆分:事务归 MySQL、低延迟 KV 归 Manhattan、热点归 Pelikan、二进制归 Blobstore。核心原则是「读路径必须命中缓存」,缓存整体命中率长期维持在 97% 以上。缓存层的工程细节(穿透 / 击穿 / 雪崩)见教程T3。

      存储矩阵

      系统类型承载数据关键特性
      MySQL分库分表 RDBMS推文主数据、用户资料user_id 哈希分片,3 副本半同步
      Manhattan自研分布式 KV收件箱、计数、会话SSD + RAM 分层,P99 < 5ms
      Pelikan统一缓存层热点推文、图谱边、会话Twemcache 演进,命中率 > 97%
      Blobstore对象存储图片 / 视频 / 语音纠删码 EC,跨区域复制
      Kafka事件主干行为流、审计日志3 副本,按用户哈希分区
      Elasticsearch检索索引推文全文、用户搜索30 天热索引 + 冷归档

      一致性矩阵

      操作一致性级别实现
      发推后作者刷新强一致(read-your-writes)写后读主库 / 旁路标记
      他人时间线可见最终一致 ≤ 3s异步扇出 + 缓存失效广播
      点赞 / 转推计数最终一致(±1s)增量合并写,Kafka 聚合
      DM 会话因果一致会话级序列号 + 确认回执
      备份与容灾:逻辑备份 + binlog 增量;跨区域异步复制 RPO < 30s;每季度切换演练,RTO < 15min。

      09 · 实时流式管道

      STREAMING PIPELINE

      Kafka · Flink · 事件驱动

      事件驱动是 X 的神经系统:所有行为(发推、互动、曝光、关注)以事件形式进入Kafka 主干,由 Flink 集群完成实时计数、趋势窗口与风控特征计算,同时在 5 分钟内把「曝光 ↔ 互动」按 request_id join 成训练样本入湖,供排序模型持续迭代。动手搭一条消费管道见教程T7。

      核心 Topic

      Topic峰值吞吐分区策略下游消费方
      tweet.create9.4K /s512 · user_id 哈希扇出、索引、风控
      engagement.events900K /s1024计数、样本拼接
      impression.stream3.5M /s2048样本拼接、指标
      social.follow12K /s128图谱、推荐
      abuse.signals260K /s512实时风控模型

      可靠性设计

      at-least-once + 幂等 sink · Flink checkpoint · Schema Registry 契约 · 反压与死信队列 · 消费滞后 SLO < 2s

      互动事件样例

      {
        "event": "tweet.engagement",
        "type": "favorite",
        "tweet_id": 1953921047311282176,
        "user_id": 88214526,
        "request_id": "r-7f3c…",        // 与曝光事件 join 成训练样本
        "ts_ms": 1770480600123,
        "surface": "home_timeline",
        "experiment": ["ranker_v42", "ui_density_b"]
      }

      10 · 基础设施与交付

      INFRASTRUCTURE

      多云 · Kubernetes · IaC

      基础设施已从自有机房迁移为多云架构:AWS 为主、GCP 为辅,媒体负载保留部分自持节点。Kubernetes 联邦按节点池(计算 / 内存 / GPU)调度,GPU 集群同时服务推荐推理与 AI 训练。全量环境 Terraform 化,漂移巡检每日执行。

      环境区域用途
      prod-usus-east / us-west主生产,全量服务
      prod-eueu-west数据驻留合规 + 就近服务
      prod-apap-northeast亚太只读副本 + 边缘回源
      stagingus-east预发,镜像生产拓扑
      edge40+ POP缓存 / 安全 / 媒体就近分发

      交付流水线

      Mono-repo + 增量构建 → 单元测试与契约测试 → 镜像构建与 SBOM 生成 →canary 1%→5%→25%→100%,每级由指标门禁自动裁决,异常 5 分钟内自动回滚。容量规划由历史负载预测模型驱动,批处理与训练任务使用 Spot 实例压低成本。

      成本治理:2023 年整合关闭大量冗余服务与机房,服务器规模显著收缩;此后持续做 right-sizing 与存储分层(热 / 温 / 冷)。下游故障时的自保机制见教程T15 熔断与降级。

      11 · 边缘网络与媒体

      EDGE & MEDIA

      Anycast · HTTP/3 · AVIF

      全球40+ Anycast POP承担 TLS 终止、WAF、Bot 评分与缓存。媒体是不可变内容,天然适合边缘缓存——命中率长期 >96%;动态 API 请求走优化专线回源。图片 URL 参数化,边缘按需即时裁剪转码;视频上传后进入转码矩阵(HLS/DASH,6 档码率),分片预热到各 POP;直播走 LL-HLS,端到端约 3s。媒体在源站侧的完整加工流程见19 媒体处理管线。

      URL 即转码指令

      原图  https://pbs.twimg.com/media/GBxK2…?format=jpg&name=4096x4096    3.8 MB
      边缘  https://pbs.twimg.com/media/GBxK2…?format=avif&name=900x900     38 KB
      视频  https://video.twimg.com/ext_tw_video/1953…/pu/vid/avc1/1280x720/seg-3.m4s  (HLS 分片)

      媒体缓存命中 > 96% · HTTP/3 · QUIC · 签名 URL 防盗链 · Origin Shield 二级回源 · 边缘 JS 挑战 · Bot 评分


      12 · 可观测性

      OBSERVABILITY

      Metrics · Tracing · SLO

      三支柱体系:Metrics(自研时序库,约 1,200 万时序,10s 粒度)、Tracing(全链路 1% 采样,错误与慢请求 100% 全采)、Logs(结构化 JSON,热存 7 天、冷归档 90 天)。告警按 P1/P2/P3 分级:P1 立即呼叫值班,P2 出工单,P3 仅观察。落地代码见教程T8。

      SLO 与错误预算(30 天窗口)

      服务SLO错误预算超限动作
      统一网关99.95%21.6 min冻结非必要发布
      时间线99.90%43 min降级为关注流兜底
      媒体分发99.95%21.6 min切换备用回源链路
      DM99.90%43 min只读模式预案

      P1 MTTA < 5min · P1 MTTR < 30min · 无责复盘周会 · Runbook 随告警附送 · RUM 10% 采样


      13 · 安全与合规

      SECURITY & COMPLIANCE

      2FA · E2E · 风控

      安全设计覆盖身份、传输、内容治理与数据合规四层。DM 采用MLS 协议端到端加密,密钥定期轮换,服务端零知识(协议细节见18);内容治理坚持「言论自由,触达受限」——高风险内容不删除但降权分发。

      威胁与对策矩阵

      威胁对策
      账号盗用2FA(TOTP / WebAuthn 通行密钥)+ 异地登录挑战
      接口滥用 / 爬虫设备指纹 + 行为评分 + 梯度限流(429 响应头透出配额)
      垃圾与操纵实时风控模型 + 可见性分级降级
      DM 窃听MLS 端到端加密 · 服务端零知识
      数据泄露PII 字段级加密 + KMS 密钥托管 + 最小权限
      合规要求GDPR / CCPA 数据导出与删除,SLA 30 天;审计日志不可篡改
      供应链攻击SBOM + SLSA L3 构建溯源 + 依赖准入
      Bug Bounty:关键漏洞设高额赏金,7×24 应急响应;白帽提交平均首次响应 < 24h。加密实践见教程T12。

      14 · 性能预算

      PERFORMANCE BUDGET

      P75/P95 · CI 门禁 · RUM

      所有体验指标以分位数而非均值治理,预算写入 CI 门禁:Lighthouse CI + bundle diff 超 5% 即阻断合并;线上 RUM 按地域 / 网络制式分位看板持续跟踪,性能 OKR 与团队发布节奏挂钩。

      预算总表

      指标预算口径
      FCP≤ 1.5sP75 · RUM
      LCP≤ 2.0sP75 · RUM
      TTI≤ 3.0s中端机(Moto G 档位)
      时间线 APIP95 ≤ 250ms网关出口
      首屏端到端P99 ≤ 800ms冷缓存最坏路径
      首屏 JS≤ 180KB gz路由首包
      图片 TTFBP75 ≤ 80ms边缘命中
      崩溃率≤ 0.05%双端会话级

      15 · 架构演进路线

      ROADMAP

      2006 → 2026
      • 2006:Ruby on Rails 单体上线,五个月完成 MVP。
      • 2008 – 2012:去 Rails 化:JVM + SOA,告别「Fail Whale」时代。
      • 2010:Snowflake 全局 ID 与大规模缓存体系上线。
      • 2013:自研 KV 存储 Manhattan 成为低延迟数据主干。
      • 2017:Web 全面 React + GraphQL 重写,首屏提速 3 倍。
      • 2019:推荐算法透明化,cr-mixer 等核心组件开源。
      • 2023:成本重构:服务与服务器大规模整合,迁移多云,关闭冗余机房。
      • 2024:长文 / 长视频 / 创作者分成上线,媒体管线重构。
      • 2025:支付牌照落地、X Money 试点,AI 深度融入时间线与搜索。
      • 2026:多云双活与「Everything App」:通讯 · 支付 · 内容 · AI 一体化。

      架构的终点不是某个技术栈,而是让数亿人同时说话时,系统依然稳定的那种韧性。


      16 · 搜索系统深挖

      SEARCH

      查询理解 · 滚动索引 · LTR

      搜索意图分为三类:用户、话题、实时新闻。写路径消费tweet.create事件,富化实体信息(用户、媒体、话题标签)后构建 30 天滚动热索引,更早内容转入冷归档;读路径经过查询理解、混合检索、多因子排序三级,热词与结果页均有二级缓存(命中率约 78%)。键入联想(typeahead)走独立集群,P95 < 80ms。

      搜索请求四级管线

      阶段组件延迟预算
      查询理解分词 / 拼写纠错 / 意图分类 / 改写15ms
      混合检索ES 热索引(BM25)+ 向量 ANN120ms
      排序LTR 模型:相关性 + 互动 + 时效 + 个性化80ms
      渲染高亮、媒体富化、作者批量回填40ms
      合计P95 ≤ 300ms

      查询改写示例

      输入:   怎么学习分布式
      改写:   +分布式 +系统 +入门 -广告 -营销号
      意图:   topic
      扩展:   同义词[分布式 → 分库分表, 多机]   语言: zh
      时效性在新闻意图下是一等公民:1 小时内的内容时效因子权重 ×3;用户意图则直接命中资料卡 + 近期推文。动手实现见教程T10 手写倒排索引。

      17 · 通知系统设计

      NOTIFICATIONS

      聚合窗口 · 优先级 · 多通道

      通知系统完全事件驱动:任何互动(点赞 / 关注 / 提及)成为事件,经「风控过滤 → 优先级打分 → 聚合窗口 → 渲染 → 通道路由」管线投递。核心设计是聚合与频控——「10 人赞了你的帖子」只产生一条通知;提及与回复永不聚合(高价值),点赞按 10 分钟窗口合并;全局频率上限与免打扰时段防止通知风暴。

      1. Kafka · 事件接入
      2. spam 拦截 · 风控过滤
      3. P0–P3 · 优先级打分
      4. 10min 合并 · 聚合窗口
      5. 文案 + 头像堆叠 · 渲染
      6. 推送/应用内/邮件 · 通道路由
      7. 失败降级重投 · 回执闭环
      8. 通知类型策略表

        类型触发聚合策略优先级通道
        提及 / 回复有人 @ 或回复我不聚合P0推送 + 应用内
        引用引用我的帖子不聚合P0推送 + 应用内
        转推转推我的帖子10 分钟窗口合并P1推送 + 应用内
        点赞赞了我的帖子10 分钟窗口合并P2应用内角标
        新关注有人关注我每小时聚合P2推送
        系统推荐运营 / 算法每日摘要P3仅邮件
        回执形成闭环:推送失败降级为应用内角标;角标 24h 未读可再降级为邮件摘要;用户可按类型配置开关——通知设置页本质是一台路由规则编辑器。聚合的实现简化版见教程T11。

        18 · DM 消息与端到端加密

        DM & MLS

        MLS · 密钥树 · 零知识

        会话式模型:会话列表 + 消息时间线两级结构。在线投递走 WebSocket 实时通道,离线转 APNs/FCM 推送;已读回执与「正在输入…」走短暂通道(不落库)。加密采用MLS(Messaging Layer Security)协议:每个会话维护一棵棘轮密钥树,成员变更时只更新受影响路径的密钥,同时满足前向安全与后向安全;服务器只存密文,零知识。

        能力矩阵

        能力状态说明
        文本 / 表情E2E 加密
        图片 / GIF媒体密钥单独加密
        群聊MLS 密钥树,成员变更路径更新
        多端同步每设备独立密钥对
        阅后即焚客户端定时销毁
        服务端内容审查无法解密,仅客户端举报通道

        加密消息信封

        {
          "conv_id": "c-9f2e…",
          "seq": 48213,                 // 会话级单调序号,用于排序与去重
          "sender_device": "d-77ab…",
          "key_epoch": 12,              // 密钥树代次,接收方据此取解密钥匙
          "ciphertext": "base64…",      // AES-GCM 加密的正文
          "mac": "…"                    // 完整性校验
        }
        E2E 的代价:服务端搜索、审查与多端漫游都变得更昂贵。X 的选择是「内容加密 + 元数据(会话关系、时间戳)可见」——投递优化与合规调查依赖元数据。简化实践见教程T12。

        19 · 媒体处理管线

        MEDIA PIPELINE

        转码矩阵 · 预热 · 直播

        一张图 / 一段视频从上传到被看见要经过五道工序:分片断点上传 → 校验与扫描 → 缩略图提取 → 转码矩阵 → 预热分发。视频在转码矩阵中产出 6 档码率的 HLS/DASH,客户端按带宽自动选档(ABR);GIF 统一转成 MP4 循环视频节省约 60% 带宽;图片在源站只保留 4096 原图与少数标准尺寸,其余规格由边缘按指令即时处理(见11 边缘与媒体)。

        1. 签名直传 S3 · 分片断点上传
        2. 格式/病毒/违规哈希 · 校验与扫描
        3. 主色 + 去 EXIF · 缩略图提取
        4. 6 档码率 + AVIF · 转码矩阵
        5. 按粉丝地域推 POP · 预热分发
        6. 视频码率阶梯

          分辨率编码码率场景
          1080pH.264 / HEVC6.0 MbpsWi-Fi 高清
          720pH.2643.0 Mbps默认档
          480pH.2641.2 Mbps4G
          288pH.2640.5 Mbps弱网兜底
          音轨AAC64 kbps独立分片

          LL-HLS 直播延迟 ~3s · WebRTC 连麦 · AVIF 体积 −35% · 预热命中率 92%


          20 · 机器学习平台与 AI

          ML PLATFORM & GROK

          特征仓库 · 训练 · 推理

          平台分三层:特征仓库(在线 / 离线共用一套定义,杜绝训练-推理不一致)、训练(样本拼接复用09 流式管道,GPU 集群日增量 + 周全量训练)、在线推理(模型注册表 → canary → 全量,P99 < 10ms)。Grok 大模型运行在独立推理集群上,承担帖子撰写建议、搜索答案生成、图像理解与审核辅助。

          模型矩阵

          模型 / 场景输入延迟 SLA更新频率
          时间线精排用户 × 推文特征(48M 参数)P99 10ms每日
          粗排双塔embedding 点积P99 3ms每日
          风控评分实时事件流P99 5ms每小时
          图像理解视觉编码器P99 400ms每周
          Grok 对话LLM 流式生成TTFT < 1.5s每周
          铁律:在线服务与离线训练必须命中同一份特征快照(point-in-time correctness),否则离线指标漂亮、上线即翻车——「训练-推理偏差」是推荐系统最常见的深坑。

          21 · 实验平台

          EXPERIMENTATION

          正交分层 · holdout · CUPED

          一切排序与 UI 变更必须经过 A/B。分桶用user_id + 实验 id的稳定哈希,保证同一用户全程看到同一版本;实验按正交层组织——排序层与 UI 层互不干扰;保留 5% 长期 holdout 组衡量长期效应。指标计算依赖曝光 × 互动的样本拼接(见09),并用 CUPED 方差缩减压缩实验周期。

          稳定分桶(简化版,完整实现见 T13)

          const bucket  = (userId, expId) => fnv1a(`${userId}:${expId}`) % 100;
          const variant = b => b < 50 ? 'control' : b < 95 ? 'treatment' : null;  // 5% holdout

          决策流程

          阶段动作产出
          假设明确改动与预期影响实验文档
          功效计算估算最小可检测效应与样本量预计时长
          放量1% → 10% → 50% 阶梯护栏指标监控
          决策显著性 + 北极星 + 护栏三查全量 / 回滚
          护栏指标(崩溃率、举报率、取关率)单侧报警——只要显著变差立即停实验,不看收益;北极星是「重度用户活跃天数」而非短期点击。分桶与指标计算见教程T13。

          22 · 变现与支付

          MONETIZATION & X MONEY

          订阅 · 分成 · 双式记账

          四条业务线:Premium 订阅(分层会员)、创作者广告分成(按回复区产生的可验证曝光结算,需过滤机器人)、广告系统(实时竞价 + 受众定向 + 频次控制)、X Money(P2P 转账与汇款,多地区牌照)。技术重点:计费幂等、账本与对账、反作弊、地区税务合规。

          业务收益模型技术要点
          Premium月费 / 年费订阅订阅生命周期、Apple/Google/Stripe 多渠道、区域定价
          创作者分成广告收益分成可验证曝光计数、反作弊过滤、T+30 结算
          广告CPM / CPC 竞价竞价 P99 50ms、定向隐私合规、频次上限
          X Money转账 / 汇兑手续费KYC/AML、双式记账、日终对账
          支付是强一致领域——与时间线的最终一致世界观截然不同:资金要求同步双式记账、事件 at-least-once + 对账任务兜底。同一个平台并存两种一致性策略,这是「架构服从业务」最直观的体现(对照08 一致性矩阵)。

          T1 · 教程:迷你时间线:扇出与收件箱

          FANOUT & INBOX

          写扩散 · 读扩散 · 游标分页

          难度 ★★☆ · 耗时 3–4h · 栈 Node.js + Redis

          原理:写扩散(fan-out on write)在发推时把推文 ID 推入每位粉丝的收件箱列表,读路径只需取自己的收件箱,复杂度 O(1);粉丝量巨大的账号改为读扩散,拉取时实时合并,避免一次写入百万级列表。分页使用 Snowflake ID 作游标,天然按时间倒序且无偏移漂移。

          NODE.JS · 发布 = 写扩散

          const BIG_V = 10_000;                          // 大 V 阈值(粉丝数)
          
          async function postTweet(userId, text) {
            const tweet = { id: snowflake(), text, author: userId, ts: Date.now() };
            await kv.set(`tweet:${tweet.id}`, tweet);            // ① 主数据落盘
            await redis.lpush(`user:${userId}:tweets`, tweet.id); // ② 个人主页列表
          
            const followers = await graph.followers(userId);      // ③ 粉丝列表
            if (followers.length > BIG_V) {
              await redis.sadd('bigv:active', userId);            // ④ 大 V:读扩散登记
            } else {
              await Promise.all(followers.map(f =>
                redis.lpush(`inbox:${f}`, tweet.id)
                  .then(() => redis.ltrim(`inbox:${f}`, 0, 799))  // ⑤ 邮箱封顶 800 条
              ));
            }
            bus.publish('tweet.create', tweet);                   // ⑥ 事件总线通知下游
            return tweet;
          }

          NODE.JS · 读取 = 组装时间线

          async function homeTimeline(userId, cursor = 0, count = 20) {
            let ids = await redis.lrange(`inbox:${userId}`, cursor, cursor + count - 1);
            const bigvs = await redis.smembers('bigv:active');    // 大 V 读扩散补偿
            ids = ids.concat(await recentTweetsOf(bigvs, count));
            ids = [...new Set(ids)].sort((a, b) => b - a);        // Snowflake 即时间序
            const page = ids.slice(0, count);
            return { entries: await hydrate(page), nextCursor: cursor + page.length };
          }
          
          async function hydrate(ids) {                            // 批量回填,杜绝 N+1
            const tweets = await kv.mget(ids.map(i => `tweet:${i}`));
            const authors = await kv.mget([...new Set(tweets.map(t => t.author))]
              .map(u => `user:${u}`));
            return tweets.map(t => ({ ...t, author: authors[t.author] }));
          }
          • 邮箱封顶:ltrim 保证单用户收件箱有界,控制内存与扇出成本。
          • 游标 ≠ 页码:以 ID 定位,插入新推文不会造成翻页重复。
          • hydrate 批量化:一次 mget 拉齐作者与计数,避免逐条查询。
          动手练习:① 把同步扇出改为消费 Kafka 的异步扇出(见T7),观察发推延迟变化;② 在 sort 之后插入一个简单 ranker(点赞数加权),体会排序对停留的影响;③ 压测 1 万粉丝用户发推,统计扇出耗时分布。

          T2 · 教程:Snowflake 分布式 ID

          DISTRIBUTED ID

          64bit · 位运算 · 时钟回拨

          难度 ★☆☆ · 耗时 1–2h · 栈 Node.js(BigInt)

          原理:64 位 = 1 符号 + 41 位毫秒时间戳(可用 69 年)+ 10 位机器号(1024 节点)+ 12 位序列号(每毫秒 4096 个)。无需数据库自增即可全局唯一、趋势递增,且可从 ID 反解生成时间。

          NODE.JS · 生成与反解

          const EPOCH = 1288834974657n;              // 2010-11-04,自定义纪元
          let seq = 0n, lastTs = -1n;
          
          function snowflake(machineId) {            // machineId ∈ [0, 1023]
            let ts = BigInt(Date.now());
            if (ts === lastTs) {
              seq = (seq + 1n) & 4095n;              // 同毫秒内序列号自增取模
              if (seq === 0n) {                      // 序列耗尽 → 自旋等下一毫秒
                while (ts <= lastTs) ts = BigInt(Date.now());
              }
            } else {
              seq = 0n;
            }
            if (ts < lastTs) throw new Error('clock moved backwards'); // 时钟回拨保护
            lastTs = ts;
            return ((ts - EPOCH) << 22n) | (BigInt(machineId) << 12n) | seq;
          }
          
          const parseTime = id => new Date(Number(id >> 22n) + Number(EPOCH));
          console.log(parseTime(1953921047311282176n));   // 从任意推文 ID 反解时间
          • 容量:单节点 4096 × 1000 ≈ 410 万 ID/秒,远超单机写入能力。
          • 时钟回拨:小回拨可等待追平,大回拨必须拒绝服务或切换机器号,绝不可静默生成。
          • 机器号分配:生产中由配置中心 / K8s StatefulSet 序号下发,避免冲突。
          动手练习:① 把 10 位机器号拆成 5 位机房 + 5 位 worker;② 写基准测试统计每秒生成量;③ 对比 UUID v7 的长度、索引友好度与隐私泄露风险。

          T3 · 教程:多级缓存与三大故障

          CACHING PATTERNS

          Cache-Aside · 击穿 · 雪崩

          难度 ★★☆ · 耗时 2–3h · 栈 Node.js + Redis

          原理:X 的读路径 97% 命中缓存。标准模式是 Cache-Aside:读未命中时回源并回填;写时「先更新库,再删缓存」。三个经典故障——穿透(查不存在的 key)、击穿(热点 key 过期瞬间)、雪崩(大批 key 同时过期)——分别用空值哨兵、singleflight 合并请求、TTL 抖动化解。

          NODE.JS · CACHE-ASIDE + 防护三件套

          const NULL = Symbol('NULL_SENTINEL');
          const inflight = new Map();
          
          async function getTweet(id) {
            const key = `tweet:${id}`;
            let v = cache.get(key);
            if (v === undefined) {                                 // 未命中
              v = await singleflight(key, async () => {           // ② 击穿:合并并发回源
                return (await db.getTweet(id)) ?? NULL;           // ① 穿透:空值哨兵
              });
              cache.set(key, v, 300_000 + Math.random() * 60_000); // ③ 雪崩:TTL 抖动
            }
            return v === NULL ? null : v;
          }
          
          function singleflight(key, fn) {                         // 同 key 只放一个请求回源
            if (!inflight.has(key)) {
              inflight.set(key, fn().finally(() => inflight.delete(key)));
            }
            return inflight.get(key);
          }
          
          async function updateTweet(id, patch) {
            await db.updateTweet(id, patch);     // 先写库
            cache.del(`tweet:${id}`);            // 再删缓存(可加延迟双删防脏读)
          }
          故障现象对策
          穿透大量查询不存在的 ID,直达数据库空值哨兵 + 布隆过滤器
          击穿热点 key 过期瞬间并发打爆 DBsingleflight / 互斥重建 / 逻辑过期
          雪崩大批 key 同时失效TTL 加随机抖动 + 多级缓存兜底
          动手练习:① 给 cache 加 LRU 淘汰并统计命中率曲线;② 模拟热点 key 过期,对比有无 singleflight 时 DB 的 QPS 峰值;③ 比较「删缓存」与「写缓存」两种更新策略的脏读窗口。

          T4 · 教程:令牌桶限流

          RATE LIMITING

          Token Bucket · 分级 Key · 429

          难度 ★☆☆ · 耗时 1–2h · 栈 Node.js(+Redis 进阶)

          原理:令牌以固定速率注入桶中,请求到来时取走令牌,桶空即拒。相比漏桶,令牌桶允许一定程度的突发,更贴近真实用户体验。网关按 IP → 用户 → 设备多级 Key 叠加限流,并把配额写进响应头。

          NODE.JS · 令牌桶与网关接入

          class TokenBucket {
            constructor(rate, capacity) {          // rate: 每秒补充数
              this.rate = rate; this.cap = capacity;
              this.tokens = capacity; this.ts = Date.now();
            }
            take(n = 1) {
              const now = Date.now();
              this.tokens = Math.min(this.cap, this.tokens + (now - this.ts) / 1000 * this.rate);
              this.ts = now;
              if (this.tokens >= n) { this.tokens -= n; return true; }
              return false;
            }
          }
          
          const buckets = new Map();
          const bucket = (key, rate, cap) =>
            buckets.get(key) ?? buckets.set(key, new TokenBucket(rate, cap)).get(key);
          
          app.use('/timeline', (req, res, next) => {
            const b = bucket(`u:${req.userId}`, 300, 300);     // 用户级
            const ok = b.take() && bucket(`ip:${req.ip}`, 100, 200).take(); // 叠加 IP 级
            res.set('x-rate-limit-remaining', Math.floor(b.tokens));
            if (!ok) { res.set('retry-after', 42); return res.status(429).end(); }
            next();
          });
          • 分布式:多实例场景把计数放进 Redis,用 Lua 脚本保证「读-改-写」原子性。
          • 分级:IP 防爬虫、用户保公平、设备防多开,三层独立又叠加。
          • 客户端义务:收到 429 必须按 retry-after 指数退避,禁止硬重试。
          动手练习:① 用 Redis + Lua 重写 take(),压测分布式一致性;② 实现滑动窗口日志算法并对比两者对突发流量的表现;③ 给客户端加退避重试,画出 QPS 恢复曲线。

          T5 · 教程:推荐召回与排序

          RECALL & RANKING

          多路召回 · 多目标打分 · 重排

          难度 ★★★ · 耗时 4–6h · 栈 Node.js / Python

          原理:推荐 = 漏斗。先用多路召回从亿级内容池捞出约 1,000 条候选,再用模型对候选打分排序,最后用启发式规则重排保证多样性。本节用一个「兴趣向量 + 余弦相似度」的简化模型复现这条管线。

          NODE.JS · 召回 → 打分 → 重排

          // ① 用户兴趣向量 = 历史互动推文的主题向量加权平均(简化版 SimClusters)
          function userVector(uid) {
            const hist = engagementHistory(uid);                // 点赞/转推记录
            return normalize(sum(hist.map(t => weight(t) * t.topicVec)));
          }
          const cos = (a, b) => dot(a, b) / (norm(a) * norm(b));
          
          // ② 多路召回(并行执行,互相兜底)
          async function recall(uid) {
            const [follow, interest, hot] = await Promise.all([
              inbox(uid, 400),                                  // 关注路
              topKByScore(candidatePool(), t => cos(userVector(uid), t.topicVec), 400), // 兴趣路
              trending(regionOf(uid), 200),                     // 热门路
            ]);
            return dedupe([...follow, ...interest, ...hot]);    // ~1000 候选
          }
          
          // ③ 打分:多目标加权(简化版 48M 参数模型)
          const W = { like: 1.0, reply: 1.3, retweet: 1.2, follow: 3.0, negative: -8.0 };
          const score = (u, t) => Object.entries(W)
            .reduce((s, [k, w]) => s + w * predict(u, t, k), 0);
          
          // ④ 重排启发式:同作者最多连续 2 条,每 4 条穿插一个视频
          function rerank(list) {
            const out = []; let streak = {};
            for (const t of list.sort((a, b) => score(u, b) - score(u, a))) {
              if ((streak[t.author] ?? 0) >= 2) continue;
              streak[t.author] = (streak[t.author] ?? 0) + 1;
              out.push(t);
            }
            return interleaveMedia(out);
          }
          • 负反馈权重最高:一次「不感兴趣」要抵消多次点赞,避免标题党绑架时间线。
          • 粗排-精排分离:候选多时用轻量模型粗筛,控制 GPU 成本。
          • 离线评估:用历史互动回放,算 precision@40 / NDCG,再上 A/B(见21 实验平台)。
          动手练习:① 把余弦相似度换成真实 ANN 索引(HNSW);② 加入「作者多样性」约束前后对比点击分布;③ 设计一个 1% 流量的 A/B 实验方案,明确北极星指标与护栏指标。

          T6 · 教程:GraphQL 网关与 DataLoader

          GRAPHQL GATEWAY

          Schema · N+1 · 持久化查询

          难度 ★★☆ · 耗时 3–4h · 栈 Node.js + graphql

          原理:GraphQL 让客户端按需取字段,一次请求聚合多个领域。网关层必须解决两件事:N+1 查询(用 DataLoader 把同一帧内的请求合并成批量查询)与查询滥用(持久化查询白名单,只接受注册过的查询哈希)。

          SCHEMA · 最小时间线模型

          type Tweet  { id: ID!  text: String!  author: User!  stats: Stats! }
          type User   { id: ID!  name: String!  verified: Boolean! }
          type Stats  { like: Int!  retweet: Int!  reply: Int! }
          type Timeline { entries: [Tweet!]!  nextCursor: String }
          type Query  { homeTimeline(count: Int = 20, cursor: String): Timeline! }

          NODE.JS · DATALOADER 消除 N+1

          import DataLoader from 'dataloader';
          
          // 每请求一个 loader:同一事件循环内的 load() 自动合并成一次批量查询
          const userLoader = new DataLoader(async ids => {
            const users = await db.users.findByIds(ids);        // 单条 IN 查询
            return ids.map(id => users.find(u => u.id === id)); // 顺序必须对齐
          });
          
          const resolvers = {
            Tweet: {
              author: t => userLoader.load(t.authorId),         // 20 条推文 → 1 次 DB 查询
              stats:  t => countLoader.load(t.id),
            },
            Query: {
              homeTimeline: async (_, { count, cursor }, ctx) => {
                const ids = await timelineSvc.get(ctx.viewer.id, cursor, count);
                return { entries: await hydrate(ids), nextCursor: ids.at(-1) ?? null };
              },
            },
          };
          
          // 持久化查询:只放行白名单哈希,拒绝任意 query 字符串
          app.post('/graphql', (req, res) => {
            const doc = registry.get(req.body.sha256);
            if (!doc) return res.status(400).json({ error: 'unknown persisted query' });
            return execute({ schema, document: doc, variableValues: req.body.variables });
          });
          • 游标分页:nextCursor 用最后一条推文 ID,天然抗并发插入。
          • 字段级权限:viewer context 注入 resolver,屏蔽关系在字段解析时裁决。
          • 深度限制:对查询做最大深度 / 复杂度分析,防止恶意嵌套。
          动手练习:① 给 homeTimeline 加一个 subscription,新推文实时推送;② 实现一个权限中间件,未登录用户隐藏 stats.reply;③ 对比 DataLoader 开 / 关时的 SQL 条数与总耗时。

          T7 · 教程:Kafka 事件管道

          EVENT PIPELINE

          分区 · 幂等消费 · 死信

          难度 ★★☆ · 耗时 2–3h · 栈 Node.js + KafkaJS

          原理:生产者按用户 ID 做分区键,保证同一用户的事件有序;消费者采用 at-least-once + 幂等 sink:处理成功后才提交位点,重放时靠去重表跳过已处理事件;反复失败的消息进死信队列人工介入。

          NODE.JS · 生产与消费

          // 生产者:key = 用户 ID → 同用户事件落入同一分区,保持顺序
          await producer.send({
            topic: 'tweet.create',
            messages: [{ key: String(tweet.authorId), value: JSON.stringify(tweet) }],
          });
          
          // 消费者:at-least-once + 幂等 sink
          for await (const batch of consumer.subscribe('tweet.create')) {
            for (const msg of batch) {
              const evt = JSON.parse(msg.value);
              if (await dedupe.seen(evt.id)) continue;          // ① 去重(Redis SETNX 24h)
              try {
                await Promise.all([                             // ② 多个下游并行消费
                  fanout(evt),                                   //    扇出服务
                  searchIndex(evt),                              //    搜索索引
                  riskScan(evt),                                 //    风控扫描
                ]);
                await dedupe.mark(evt.id);
              } catch (e) {
                if (msg.retries >= 3) await dlq.send(evt);      // ③ 三次失败进死信
                else throw e;                                    //    否则重试
              }
            }
            await consumer.commit(batch);                        // ④ 全部成功才提交位点
          }
          语义含义适用场景
          at-most-once可能丢,不重复采样指标、日志
          at-least-once不丢,可能重复主流场景 + 幂等 sink(X 默认)
          exactly-once不丢不重计费、事务型流处理(成本高)
          动手练习:① 用 Redis Streams 搭一个迷你版,理解 offset / 消费组概念;② 制造下游故障,观察死信队列与滞后(lag)变化;③ 给消费加背压:处理不过来时主动暂停拉取。

          T8 · 教程:可观测性:RED 指标与链路

          RED + TRACING

          Rate · Errors · Duration

          难度 ★☆☆ · 耗时 2h · 栈 Node.js + Prometheus

          原理:每个服务先回答三个问题——每秒多少请求(Rate)、多少失败(Errors)、耗时分布(Duration)。用一个中间件就能采集 RED 三件套;再用贯穿全链路的 trace_id 把跨服务调用串成一条链路。

          NODE.JS · RED 中间件 + TRACE_ID

          app.use((req, res, next) => {
            const t0 = process.hrtime.bigint();
            req.traceId = req.headers['x-trace-id'] ?? crypto.randomUUID(); // 入口生成/继承
            res.on('finish', () => {
              const ms = Number(process.hrtime.bigint() - t0) / 1e6;
              const labels = { route: req.path, status: res.statusCode };
              metrics.counter('api_requests_total').inc(labels);        // Rate + Errors
              metrics.histogram('api_latency_ms').observe(labels, ms);  // Duration 分布
            });
            next();
          });
          
          // 调用下游时透传 trace_id,即可拼出全链路
          await fetch('http://timeline-svc/get', {
            headers: { 'x-trace-id': req.traceId },
          });
          
          // 告警示例(PromQL):5xx 比例超 SLO 即触发
          // sum(rate(api_requests_total{status=~"5.."}[5m]))
          //   / sum(rate(api_requests_total[5m])) > 0.0005
          • 分位数优先:histogram 而不是 avg,P99 才代表真实痛感。
          • 采样策略:正常流量 1% 采样 trace,错误与慢请求 100% 全采。
          • 错误预算:30 天窗口内 (1 − SLO) × 总分钟数,烧完即冻结发布。
          动手练习:① 在 Grafana 画一张 P50/P95/P99 三曲线面板;② 给慢请求(>800ms)自动打日志并附 trace_id;③ 用 k6 制造流量尖峰,验证告警在 1 分钟内触发。

          T9 · 教程:学习路线图与书单

          LEARNING ROADMAP

          入门 · 进阶 · 专家

          把本文档当成一张地图:先跑通最小系统,再逐层替换成真实组件。三个阶段,每阶段都有明确产出物,避免「只看不练」。

          阶段周期目标行动产出物
          入门1–2 月跑通最小闭环HTTP / SQL / 缓存基础;完成 T2、T4可发推、可刷新的 CRUD 小站
          进阶3–6 月逼近真实架构完成 T1、T3、T6、T7;引入 Kafka 与 GraphQL带扇出、缓存、限流的迷你 Twitter + 压测报告
          专家6–12 月理解规模化取舍完成 T5、T8 与全部进阶 / 扩展教程(T10–T19);读论文;做分片与多区域复制系统设计博客 / 开源贡献 / 故障演练记录

          延伸阅读

          • 《Designing Data-Intensive Applications》— 分布式系统取舍的最佳入门框架。
          • Snowflake 官方博客 — 分布式 ID 的原始设计动机。
          • cr-mixer 开源仓库 — 真实的多路召回工程实现。
          • Zipkin 论文 — 分布式链路追踪的起源(诞生于 Twitter)。
          • Manhattan, our real-time, multi-tenant distributed database — 自研 KV 的工程细节。
          • RFC 9420(MLS)— 群聊端到端加密协议规范。
          建议节奏:每周至少 2 个晚上写代码 + 1 篇复盘笔记;每完成一个教程,回头对照本文档 01–27 章,标注「我现在理解了哪条设计决策、为什么」。

          T10 · 进阶:手写倒排索引与搜索

          INVERTED INDEX

          分词 · posting list · BM25

          难度 ★★☆ · 耗时 3h · 栈 Node.js

          原理:搜索的核心是倒排索引——把词元映射到包含它的文档 posting list。建索引时对每条推文分词入表,查询时取各词 posting list 求交集,再用 BM25 打分、叠加时效与互动权重。生产环境由 ES 完成这一切,但手写一遍才能理解分词器、posting list、打分三要素(对应16 搜索系统)。

          NODE.JS · 建索引与查询

          const index = new Map();                       // 词元 → posting list
          const docs  = new Map();                       // id → 文档
          
          function addDoc(doc) {
            docs.set(doc.id, doc);
            for (const [pos, w] of tokenize(doc.text).entries()) {
              if (!index.has(w)) index.set(w, []);
              index.get(w).push({ id: doc.id, pos });    // 记录位置,支持短语查询
            }
          }
          
          function search(q, k = 20) {
            const lists = tokenize(q).map(w => index.get(w) ?? []);
            const ids = intersect(lists.map(l => new Set(l.map(x => x.id)))); // 从最短表开始交
            return [...ids]
              .map(id => ({ id, score: bm25(docs.get(id), q) * freshBoost(docs.get(id)) }))
              .sort((a, b) => b.score - a.score)
              .slice(0, k);
          }
          • 中文分词:无空格语言需分词器(ngram 简单、词典法准确),直接影响召回。
          • 交集优化:从最短 posting list 起步,逐个过滤,避免大表全扫。
          • 时效衰减:freshBoost 用指数衰减,新闻意图下加大权重。
          动手练习:① 支持 @用户 与 #话题 前缀语法;② 加「你是不是想找」纠错(编辑距离 ≤ 1);③ 对比 ngram 与分词法的命中率与索引体积。

          T11 · 进阶:通知聚合与优先级

          NOTIFICATION FANOUT

          聚合窗口 · 去重 · 延迟投递

          难度 ★☆☆ · 耗时 2h · 栈 Node.js + Redis

          原理:高频低价值通知(点赞、关注)必须聚合,否则用户会关掉通知。通用做法是聚合窗口:窗口期内同一目标的事件累计在 Redis 集合里,窗口到期统一 flush,渲染成「某某和其他 9 人喜欢了你的帖子」;高价值通知(提及 / 回复)跳过窗口直接投递(对应17 通知系统)。

          NODE.JS · 窗口聚合

          const WINDOW = 10 * 60_000;                    // 10 分钟聚合窗口
          
          async function onEvent(evt) {
            if (evt.type === 'mention' || evt.type === 'reply') return deliver(evt); // P0 直发
            const key = `agg:${evt.targetId}:${evt.type}`;
            const isNew = await redis.sadd(key, evt.actorId);   // 参与者集合,天然去重
            await redis.pexpire(key, WINDOW);
            if (isNew === 1) scheduler.push({ key, at: Date.now() + WINDOW }); // 首事件定时
          }
          
          async function flush({ key }) {
            const actors = await redis.smembers(key);
            if (!actors.length) return;
            await redis.del(key);
            deliver({                                     // 渲染聚合文案
              text: actors.length === 1
                ? `${name(actors[0])} 赞了你的帖子`
                : `${name(actors[0])} 和其他 ${actors.length - 1} 人赞了你的帖子`,
              icons: avatars(actors.slice(0, 3)),         // 头像堆叠最多 3 个
            });
          }
          • 去重:用集合存 actorId,同一人重复点赞只计一次。
          • 幂等 flush:定时任务可能重放,flush 前先原子取走集合。
          • 最后一道闸:免打扰时段与按类型开关在投递前再做一次过滤。
          动手练习:① 加优先级队列,让 mention 抢占聚合任务;② 模拟一次 1 万点赞风暴,验证通知条数从 1 万压缩到 1;③ 实现「静默时段」并测试跨时区用户。

          T12 · 进阶:端到端加密简化实现

          E2E ENCRYPTION

          ECDH · 棘轮 · 前向安全

          难度 ★★★ · 耗时 4h · 栈 Node.js + tweetnacl

          原理:E2E 的核心是两件事——密钥协商(ECDH/X3DH 让两端算出共享密钥,服务器不参与)与密钥棘轮(每轮通信都推进密钥,即使某把密钥泄露也不影响之后的消息,即前向安全)。MLS 在群聊中用密钥树把成员变更的轮换成本从 O(N) 降到 O(log N)(对应18 DM 消息)。

          NODE.JS · 会话骨架(简化)

          // ① 握手:ECDH 派生会话根密钥(生产需 X3DH + 预密钥包)
          const root = HKDF(ECDH(alice.priv, bob.pub), salt);
          
          // ② 棘轮:每次收发都推进根密钥 → 前向安全
          function ratchet(state, theirNewPub) {
            const dh = ECDH(state.ephPriv, theirNewPub);
            state.root = HKDF(state.root, dh);
            state.ephPriv = keygen();                      // 自己的临时密钥也换新
            return {
              sendKey: HKDF(state.root, 'send'),           // 收发密钥分离
              recvKey: HKDF(state.root, 'recv'),
            };
          }
          
          // ③ 消息:AES-GCM 认证加密 = 保密 + 完整性
          const enc = (key, msg) => AES_GCM_seal(key, nonce(), msg);
          • 前向安全:任何一把密钥泄露,历史消息仍安全——这是 E2E 与 TLS 的本质区别。
          • 服务器零知识:只转发密文与元数据,无法解密内容。
          • 中间人防护:双方比对安全码(密钥指纹)确认身份。
          动手练习:① 用 tweetnacl 的 box/open 跑通真实加解密;② 模拟中间人替换公钥,理解签名的作用;③ 阅读 RFC 9420 中群聊密钥树章节,画出成员退出时的路径更新。

          T13 · 进阶:A/B 分桶与指标计算

          EXPERIMENTATION

          稳定哈希 · 正交层 · holdout

          难度 ★☆☆ · 耗时 1–2h · 栈 Node.js

          原理:分桶只有一个要求——稳定:同一用户每次请求、每台设备都必须落在同一组,所以不能用随机数,而要把 user_id 与实验 id 一起哈希取桶;多实验用正交层组织,层 id 参与哈希,保证实验组合均匀分布;再预留 holdout 组做长期观察(对应21 实验平台)。

          NODE.JS · 稳定分桶

          function fnv1a(str) {                              // 快速非加密哈希
            let h = 0x811c9dc5;
            for (let i = 0; i < str.length; i++) {
              h ^= str.charCodeAt(i);
              h = Math.imul(h, 0x01000193) >>> 0;
            }
            return h;
          }
          
          function variant(userId, exp) {                    // exp: { layer, id, control: 50, treat: 45 }
            const b = fnv1a(`${exp.layer}:${exp.id}:${userId}`) % 100;
            if (b < exp.control) return 'control';
            if (b < exp.control + exp.treat) return 'treatment';
            return null;                                     // holdout,不参与实验
          }
          
          // 指标:互动率提升 + 置信区间(简化)
          const lift = (t, c) => (t.mean - c.mean) / c.mean;
          • 不要按请求分桶:否则用户体验闪烁,指标也失去意义。
          • AA 空跑校验:实验未开启时两组指标应无显著差异,否则分桶有偏。
          • 护栏单侧报警:崩溃率、举报率只要显著变差立即停,不与收益对冲。
          动手练习:① 用 10 万模拟用户验证分桶均匀性(卡方检验);② 加层正交校验,统计层间组合分布;③ 用 bootstrap 给点击率差值算 95% 置信区间。

          T14 · 进阶:虚拟长列表:时间线渲染

          VIRTUAL LIST

          Windowing · 缓冲 · 回收

          难度 ★★☆ · 耗时 2–3h · 栈 React

          原理:时间线可能有上千条,但一屏只看得见 5–10 条。全量渲染会让 DOM 爆炸、滚动掉帧。虚拟列表只渲染「可视区 ± 缓冲」的窗口:容器用总高度撑开保证滚动条真实,条目绝对定位按序摆放,滚动时重算窗口起止下标——DOM 节点数从 N 降到 ~20。

          REACT · 定高虚拟列表

          function VirtualTimeline({ items, itemH = 140, viewH = 800 }) {
            const [scrollTop, setScrollTop] = useState(0);
            const BUFFER = 5;                                             // 上下各缓冲 5 条
            const start = Math.max(0, Math.floor(scrollTop / itemH) - BUFFER);
            const end   = Math.min(items.length, start + Math.ceil(viewH / itemH) + BUFFER * 2);
          
            return (
              <div className="viewport" style={{ height: viewH, overflow: 'auto' }}
                   onScroll={e => setScrollTop(e.currentTarget.scrollTop)}>
                <div style={{ height: items.length * itemH, position: 'relative' }}>
                  {items.slice(start, end).map((it, i) => (
                    <TweetCard key={it.id} data={it}
                      style={{ position: 'absolute', top: (start + i) * itemH, width: '100%' }} />
                  ))}
                </div>
              </div>
            );
          }
          • 动态高度:先用估计值占位,渲染后用 ResizeObserver 实测回填修正。
          • 缓冲防白屏:快速滚动时预留窗口外的若干条,牺牲少量节点换流畅。
          • 资源回收:滑出窗口的图片释放解码内存;key 稳定避免整段重建。
          动手练习:① 对比虚拟化前后的 DOM 节点数与滚动帧率;② 实现动态高度版(测量回写 + 位置缓存);③ 结合 IntersectionObserver 做提前 3 屏的下一页预取。

          T15 · 进阶:熔断器与服务降级

          CIRCUIT BREAKER

          三态机 · 快速失败 · 降级阶梯

          难度 ★★☆ · 耗时 2h · 栈 Node.js

          原理:下游故障不该拖垮上游。熔断器三态:CLOSED正常放行;连续失败达到阈值切换OPEN,后续请求快速失败并走降级;冷却期后进入HALF_OPEN试探放行一次,成功则关闭、失败重开。时间线场景下推荐服务熔断即降级为关注流——用户几乎无感(对应设计原则「优雅降级」)。

          NODE.JS · 三态熔断器

          class CircuitBreaker {
            constructor(fn, { threshold = 5, cooldown = 10_000 } = {}) {
              this.fn = fn; this.threshold = threshold; this.cooldown = cooldown;
              this.fails = 0; this.state = 'CLOSED'; this.openAt = 0;
            }
            async call(fallback) {
              if (this.state === 'OPEN') {
                if (Date.now() - this.openAt > this.cooldown) this.state = 'HALF_OPEN';
                else return fallback?.();                    // 开路期间:快速失败 + 降级
              }
              try {
                const r = await this.fn();
                if (this.state === 'HALF_OPEN') { this.fails = 0; this.state = 'CLOSED'; }
                return r;
              } catch (e) {
                if (this.state === 'HALF_OPEN' || ++this.fails >= this.threshold) {
                  this.state = 'OPEN'; this.openAt = Date.now();
                }
                return fallback?.();
              }
            }
          }
          
          // 时间线场景:推荐熔断 → 降级关注流
          const recTimeline = new CircuitBreaker(() => svc.recommended(uid));
          const timeline = await recTimeline.call(() => svc.followingOnly(uid));
          • 降级阶梯预先设计:推荐 → 关注 → 热门缓存 → 骨架页,逐级兜底。
          • 舱壁隔离:每个下游独立连接池,防止一个慢服务耗尽全部资源。
          • 分布式状态:多实例各自熔断可接受短暂不一致,必要时广播状态。
          动手练习:① 注入故障,画出三态切换时间线;② 为时间线设计完整四级降级预案并演练;③ 结合T1验证「关注流兜底」在推荐全挂时真的可用。

          23 · 反滥用与内容治理

          TRUST & SAFETY

          可见性分级 · 风险评分 · 申诉

          治理理念是「言论自由,而非触达自由」:高风险内容不一刀切删除,而是按可见性分级限制触达。防线共四层:边缘 Bot 评分在入口拦截机器流量;abuse.signals实时流(见09)喂给风险评分模型;visibility 服务统一执行分级裁决(与13威胁矩阵呼应);人工复核 + 申诉通道完成最后纠偏。媒体完整性依赖哈希指纹库(CSAM / 恐袭内容),上传即比对拦截已知违规。

          可见性分级表

          层级动作典型触发用户感知
          T0 正常低风险
          T1 降权减少推荐分发风险评分灰区无感知,可申诉查状态
          T2 回复警示回复需点开、默认折叠中度攻击性回复前弹提示
          T3 内容警示敏感媒体覆盖层媒体分类器点开才可见
          T4 功能限制限发帖 / 限关注高置信违规站内信告知理由
          T5 移除与封禁内容删除、账号停用法律要求 / 严重违规正式通知
          所有分级都可申诉,申诉队列 SLO 48h;规则条款公开、执行记录可审计;透明度报告按季度披露政府数据请求与内容处置量。模型误伤的兜底永远是人工通道——治理系统的最后一道防线是人。

          24 · 多区域容灾与双活

          MULTI-REGION DR

          RPO/RTO · 切换演练 · 混沌工程

          演进路径:单数据中心 → 三地备份 → 多云双活。写路径仍以主区域为主——资金、ID 生成等强一致域永远保持单写者原则;读路径与可重建数据全球分布;欧盟数据驻留合规要求独立存储集群。RPO/RTO 按领域分级承诺,每季度真实切换演练,混沌工程常态化(见教程T19)。

          分领域 RPO / RTO 承诺

          领域RPORTO恢复策略
          核心写入(发推)≈0(半同步)< 15min区域级主从切换
          时间线读缓存可重建< 5min降级关注流(见T15)
          媒体 Blob跨区异步复制< 30s多云回源自动切换
          搜索索引可重建< 2hKafka 重放重建
          DM 密文3 副本< 15min密钥服务优先恢复

          演练日历

          1. 随机杀 pod / 缓存分区 · 每月
          2. 区域 DNS 切换演练 · 每季
          3. 整云撤离演习 · 每年
          4. 假设文档 + 结论复盘 · 每次演练
          5. 双活 ≠ 双写:只有读域与可重建数据做多区域 active,强一致域保持单写者 + 同步复制。把「所有东西都双活」是多云架构最常见的误解,也是事故的主要来源。

            25 · 计数系统深挖

            COUNTING AT SCALE

            合并写 · 近似算法 · 展示分层

            点赞、转推、浏览量是写入频率最高的场景(合计 >900K/s),直接更新数据库会立即形成热点。方案是三件事:事件流合并写——1 秒窗口微批求和后批量增量,源头是09的 engagement.events;展示分层——全局计数接受 ±1s 最终一致,个人「我是否点过赞」走强一致 read-your-writes(对照08一致性矩阵);近似算法——浏览量去重统计用 HyperLogLog。展示层统一压缩大数字(1.2万 / 1.2M)。

            四类计数的路径与承诺

            计数写入路径一致性展示策略
            点赞 / 转推数Kafka 合并写(1s 微批)±1s 最终一致压缩显示 1.2万
            浏览量HyperLogLog 近似去重误差 ±5%超过阈值才显示
            粉丝数缓存 + 异步校正自己强一致、他人最终主页实时、列表缓存
            我是否点赞本地状态 + DB 双写强一致乐观渲染即时反馈
            工程实现见教程T16 分布式计数器。计数校正:夜间对账任务以事件流重放为准重算全量计数,纠正合并写丢失或重放重复造成的漂移——「最终一致」的「最终」靠对账保证,而不是靠祈祷。

            26 · 国际化与本地化

            I18N & L10N

            RTL · ICU · 加权字数

            支持40+ 语言,含右起书写文字(阿拉伯语、希伯来语)。四大挑战:布局镜像——设计系统(见04)整体支持 RTL,图标选择性翻转;复数与格式——ICU MessageFormat + CLDR 日期数字格式;字数加权——280 上限按权重计:拉丁字符 1、CJK 计 2;翻译——帖子由 LLM 机器翻译 + 社区校对。本地化流水线:字符串提取 → 翻译平台 → 伪本地化测试 → 发布。

            加权字数与 ICU 复数

            // 加权文本长度:CJK 计 2(X 280 上限的真实规则)
            const weight = c => /[\u2E80-\u9FFF\uFF66-\uFFEF]/.test(c) ? 2 : 1;
            const weightedLen = s => [...s].reduce((n, c) => n + weight(c), 0);
            
            // ICU MessageFormat 处理复数(每种语言的复数规则都不同)
            "{count, plural, =0 {还没有赞} other {# 个赞}}"
            • 伪本地化:文本膨胀 40% + 加重音字符,在 CI 阶段提前暴露截断问题。
            • 时区:存储永远 UTC,展示按观众 locale 转换——绝不在库里存本地时间。
            • 字体:Chirp 按文字系统回退系统字体;阿拉伯语需整形引擎(shaping)。
            RTL 不只是镜像翻转:数字方向、时间线排序、动画方向、滑动返回手势都要按语言翻转——设计 Token 层提供 dir 全局开关,业务代码禁止硬编码 left/right。

            27 · 开放平台与开发者 API

            PLATFORM API

            分级订阅 · OAuth · Webhook

            对外接口以REST v2为主,按分级订阅:Free / Basic / Pro / Enterprise,各级有不同的读写配额与端点范围;OAuth 2.0 PKCE 用于第三方登录与应用授权管理;Webhook(Account Activity API)把事件推给开发者服务器,HMAC 签名防伪造;合规数据接口面向学术机构。外部接口复用与内部相同的网关(05)限流(T4)与幂等机制——对外 API 本质是内部网关能力的出口。

            API 分级

            层级读取配额写入配额典型场景
            Free抽样端点500 帖 / 月学习试用
            Basic1 万读 / 月3 千帖 / 月小型机器人
            Pro100 万读 / 月30 万帖 / 月商业产品
            Enterprise定制 · Firehose定制研究 / 舆情监测
            废弃策略:接口退役提前 6 个月公告 + sunset HTTP 头 + changelog 双周更新;限流头完整透出配额让开发者自适应(429 示例见05)。Webhook 重试采用指数退避,签名校验失败的推送直接丢弃并告警。

            T16 · 扩展:分布式计数器

            DISTRIBUTED COUNTERS

            合并写 · HLL · 读写分离

            难度 ★★☆ · 耗时 2h · 栈 Node.js + Redis

            原理:高频计数直接写库会形成热点。两个关键手段:①合并写——1 秒窗口内的事件先求和,一次 INCRBY 顶上千次单写;②读写分层——全局计数最终一致即可,个人状态(我是否点赞)必须强一致。浏览量这类「去重统计」用 HyperLogLog 近似,误差不到 1%(对应25 计数系统)。

            NODE.JS · 合并写 + 近似去重

            // ① 合并写:1s 窗口微批,千次事件变成一次 INCRBY
            const buf = new Map();
            function onLike(tweetId) { buf.set(tweetId, (buf.get(tweetId) ?? 0) + 1); }
            setInterval(async () => {
              for (const [id, n] of buf) await redis.incrby(`cnt:${id}`, n);
              buf.clear();
            }, 1000);
            
            // ② 读:全局计数与个人状态分离
            const likeCount = await redis.get(`cnt:${id}`);            // 最终一致 ±1s
            const iLiked    = await redis.sismember(`liked:${uid}`, id); // 强一致
            
            // ③ 浏览量去重:HyperLogLog 近似基数(标准误差 ≈ 0.81%)
            await redis.pfadd(`views:${id}`, uid);
            const views = await redis.pfcount(`views:${id}`);
            • 合并写的代价:进程崩溃丢失最多 1s 窗口——计数可容忍,资金绝不可容忍。
            • 展示压缩Intl.NumberFormat(locale, {notation:'compact'})输出 1.2万 / 1.2M。
            • 对账兜底:夜间任务重放事件流重算,纠正漂移。
            动手练习:① 模拟 1 万 QPS 点赞,对比合并写与单写的 Redis 压力;② 对比 HLL、位图、精确集合三者的内存占用与误差;③ 写一个「计数校正」对账 job 并故意制造丢失验证它能修复。

            T17 · 扩展:趋势热点:Count-Min Sketch

            TRENDING / HEAVY HITTERS

            滑动窗口 · Top-K · 反操纵

            难度 ★★★ · 耗时 3h · 栈 Node.js

            原理:趋势要在 3.5M/s 的事件流里找出滑动窗口内最热的词。全量排序内存会爆炸,工业界用 Heavy Hitters 近似算法:Count-Min Sketch用多行哈希表估计频率(只会高估不会低估),候选再用精确计数复核;配合增速与质量分产出趋势榜,并按地域 / 语言分片独立计算(对应09流式管道)。

            NODE.JS · CMS + 滑动窗口

            class CountMinSketch {
              constructor(w = 2048, d = 4) {
                this.t = Array.from({ length: d }, () => new Int32Array(w));
                this.w = w;
              }
              hash(s, i) { return fnv1a(i + s) % this.w; }
              add(key, n = 1) { this.t.forEach((row, i) => row[this.hash(key, i)] += n); }
              est(key) {                                             // 取多行最小值压低高估
                return Math.min(...this.t.map((row, i) => row[this.hash(key, i)]));
              }
            }
            
            // 双桶滑动窗口:两个 30min 桶轮换,过期桶清零后复用
            // 每 5min 对候选重打分:score = z(频率)*0.6 + z(增速)*0.3 − 垃圾风险
            • 高估特性:CMS 只保证「不低于真值」,上榜前必须精确复核,防止误报。
            • 反操纵:剔除机器人账号贡献、打压单一来源突增(刷榜检测)。
            • 人工闸门:敏感词进入趋势前经过审核队列,避免热点被恶意绑架。
            动手练习:① 用 Zipf 分布数据验证估计误差随宽度的变化;② 实现双桶轮换的 1 小时滑动窗口;③ 加地域分片,再做跨片聚合出全球榜。

            T18 · 扩展:特性开关与 Kill Switch

            FEATURE FLAGS

            发布解耦 · 灰度 · 生命周期

            难度 ★☆☆ · 耗时 2h · 栈 Node.js

            原理:特性开关把「部署」和「发布」解耦——代码可以合并上线但默认关闭,再按百分比 / 用户标签 / 地域灰度打开;事故时 Kill Switch 秒级全关,无需回滚构建。开关有生命周期:全量后 2 周内必须清理,否则开关债务会让组合状态爆炸(配合10 交付流水线的 canary 机制)。

            NODE.JS · 开关求值与 Kill Switch

            const flags = { darkTimeline: { pct: 20, tags: ['beta'], kill: false } };
            
            function isEnabled(flag, user) {
              const f = flags[flag];
              if (!f || f.kill) return false;                       // Kill Switch:一键全关
              if (user.tags.some(t => f.tags.includes(t))) return true; // 白名单优先
              return bucket(user.id, flag) < f.pct;                 // 百分比用稳定分桶(见 T13)
            }
            
            // 用法:代码分支收敛在开关处,而非发布分支
            render(isEnabled('darkTimeline', user) ? <NewTimeline/> : <ClassicTimeline/>);
            • 开关 × 实验:开关 + 指标采集即成 A/B(见T13),一套分桶两处复用。
            • 客户端开关:配置中心推送热更新,冷启动拉取 + 本地缓存兜底。
            • 组合管理:多开关并存的测试矩阵要显式维护,别让它隐式爆炸。
            动手练习:① 加配置中心推送实现秒级热更新;② 写生命周期 lint:全量超 14 天未清理自动告警;③ 设计一条「开关 → 实验 → 全量 → 清理」的完整流水线。

            T19 · 扩展:混沌工程演练

            CHAOS ENGINEERING

            故障注入 · 爆炸半径 · 假设验证

            难度 ★★★ · 耗时 4h · 栈 Node.js + K8s

            原理:可靠性不是设计出来的,是演练出来的。混沌工程 = 在生产环境主动注入受控故障,验证系统假设:杀死单 pod、切断缓存、切换区域 DNS。核心循环是「假设 → 最小爆炸半径 → 观察 → 复盘」;先在 staging 跑,再到生产低峰窗口,全程可一键中止(配合24 多区域容灾的演练日历)。

            NODE.JS · 简易故障注入框架

            const chaos = {
              enabled: env === 'chaos',
              async maybe(name, fault, normal) {
                if (this.enabled && injectRate(name) > Math.random()) {
                  log(`💥 inject: ${name}`);
                  return fault();                     // 模拟延迟 / 异常 / 断连
                }
                return normal();                      // 正常路径
              },
            };
            
            // 演练假设:缓存全挂时 SLO 不破(用 T8 的指标做裁判)
            const v = await chaos.maybe('cache-down',
              () => { throw new Error('cache unreachable'); },
              () => redis.get(key));
            expect(p99('api_latency_ms')).toBeLessThan(800);   // 假设成立?
            • 爆炸半径:从 1% 灰度时段开始,任何异常一键中止,绝不在高峰期首演。
            • 假设先行:每次演练必须先写下「我们认为会发生什么」,结束后对账。
            • Runbook 闭环:演练暴露的每个问题都产出 Runbook,挂进告警附件(见T8)。
            动手练习:① 设计三个假设:单实例死亡 / Redis 断连 / 下游延迟 +500ms,逐一注入验证;② 画出注入前后的 P99 对比图;③ 把演练流程写成季度 gameday 剧本,包含角色分工与中止条件。

            X.COM ARCHITECTURE SPEC · v15.0 · 27 章 + 19 教程 · 2026-08-09 本文为架构研究示例文档,非 X Corp 官方资料;数据为公开信息与行业惯例的整理演示 ↑ 返回顶部 · 打印 / 导出 PDF
← 返回IT 技术 yicool 百科 · X.COM 架构白皮书 v15.0:系统设计与技术架构全景

评论 0