Files
flykhan 2536c937e3 feat: 完整中文翻译 maths-cs-ai-compendium(数学·计算机科学·AI 知识大全)
翻译自英文原版 maths-cs-ai-compendium,共 20 章全部完成。

第01章 向量 | 第02章 矩阵 | 第03章 微积分
第04章 统计学 | 第05章 概率论 | 第06章 机器学习
第07章 计算语言学 | 第08章 计算机视觉 | 第09章 音频与语音
第10章 多模态学习 | 第11章 自主系统 | 第12章 图神经网络
第13章 计算与操作系统 | 第14章 数据结构与算法
第15章 生产级软件工程 | 第16章 SIMD与GPU编程
第17章 AI推理 | 第18章 ML系统设计
第19章 应用人工智能 | 第20章 前沿人工智能

翻译说明:
- 所有数学公式 $...$ / $$...$$、代码块、图片引用完整保留
- mkdocs.yml 配置中文导航 + language: zh
- README.md 已翻译为中文(兼 docs/index.md)
- docs/ 目录包含指向各章文件的 symlink
- 约 29,000 行中文内容,排除 .cache/ 构建缓存
2026-05-03 10:23:20 +08:00

17 KiB
Raw Permalink Blame History

系统设计基础

系统设计是构建可在大规模下可靠运行的软件的方法。本文件涵盖客户端-服务器架构、网络协议、DNS、代理、负载均衡、缓存、数据库、消息队列、一致性模型和弹性模式

  • 生产环境中的每一个ML系统都是一个分布式系统。推荐引擎不仅仅是模型——它是一个API服务器、一个特征存储、一个模型注册表、一个缓存层、一个消息队列和一个监控栈,所有这些通过网络进行通信。理解系统设计是区分"我训练了一个模型"和"我构建了一个产品"的关键。
  • 顶级科技公司(Google、Meta、Amazon、OpenAI)的系统设计面试会测试你是否能设计这些系统。本章为你提供基础构建模块(本文件)、云基础设施(文件02)、扩展模式(文件03)、ML特定设计(文件04)和实操示例(文件05)。

客户端-服务器架构

  • 基本模式:客户端发送请求,服务器处理请求并返回响应。你的浏览器(客户端)向google.com(服务器)发送HTTP请求,服务器返回HTML。
  • 请求-响应模式:同步。客户端等待响应。简单但会产生瓶颈:客户端在等待时空闲,服务器必须在处理完当前请求后才能继续。
  • 无状态服务器:服务器不记住先前的请求。每个请求包含处理它所需的所有信息。这使得扩展变得容易:任何服务器都可以处理任何请求,因此你可以在负载均衡器后面添加更多服务器。
  • 有状态服务器:服务器在请求之间维护状态(例如,用户会话)。扩展更困难,因为来自同一用户的请求必须发送到同一台服务器(会话亲和性)。现代系统通过将状态存储在数据库或缓存(Redis)中来避免服务器端状态。

网络协议

  • 我们在第13章(TCP/IP层、套接字)中介绍了网络知识。这里我们关注系统设计中使用的应用层协议:
  • HTTP/HTTPS:Web和大多数API的协议。请求方法:GET(读取)、POST(创建/预测)、PUT(更新)、DELETE(删除)。HTTPS增加了TLS加密(第13章安全部分)。REST API(第15章文件03)基于HTTP构建。
  • WebSocket:客户端和服务器之间的持久双向连接。与HTTP(请求→响应→连接关闭)不同,WebSocket保持连接打开,用于实时流式传输。用于:LLM令牌流式传输(生成时发送令牌)、实时仪表盘、聊天应用。
  • gRPCGoogle的RPC框架。使用Protocol Buffers(二进制序列化,比JSON小约10倍且更快),基于HTTP/2。支持流式传输(服务端、客户端、双向)。用于注重性能的内部服务间通信。Triton推理服务器(第15章)和TensorFlow Serving使用gRPC。
  • Protocol Buffers:在.proto文件中定义消息模式:
message PredictRequest {
    repeated float features = 1;
    string model_version = 2;
}

message PredictResponse {
    float prediction = 1;
    float confidence = 2;
}

service ModelService {
    rpc Predict(PredictRequest) returns (PredictResponse);
}
  • 该模式被编译成任何语言(Python、C++、Go、Java)的客户端和服务端代码。类型安全、向后兼容性和高性能都自然具备。

DNS

  • DNS(域名系统)将人类可读的名称转换为IP地址(第13章)。对于系统设计,DNS还提供:
  • 通过DNS的负载均衡:为同一域名返回不同的IP地址,将流量分布到多个服务器。简单但粒度较粗(DNS结果会被缓存数分钟到数小时,因此流量不会快速重新平衡)。
  • 地理路由:根据客户端位置返回最近数据中心的IP。东京的用户获得日本数据中心;伦敦的用户获得欧洲数据中心。
  • 故障转移:如果服务器宕机,DNS停止返回其IP。新客户端连接到健康的服务器。但缓存的DNS条目意味着某些客户端会继续访问已宕机的服务器持续数分钟(TTL问题)。

代理

  • 代理是客户端和服务器之间的中介:
  • 反向代理(在服务器前面):客户端连接到代理,代理将请求转发给后端服务器。客户端不知道哪个服务器处理了请求。NginxHAProxy是标准的反向代理。它们提供:负载均衡(分发请求)、SSL终止(在代理处解密HTTPS,向后端发送明文HTTP)、缓存、速率限制和压缩。
  • API网关:一种专门用于API的反向代理。处理身份验证、速率限制、请求路由(不同路径→不同服务)和API版本管理。KongAWS API GatewayEnvoy是常见选择。
  • 对于ML服务:API网关位于模型服务器前面。它验证API密钥、对免费用户进行速率限制、将/v1/predict路由到模型服务器A、将/v2/predict路由到模型服务器B,并收集使用指标。

负载均衡

  • 当你拥有多台服务器时,负载均衡器将传入请求分布到它们之间。

负载均衡器将传入请求分布到多个后端服务器

  • 算法
    • 轮询:按顺序发送请求到服务器(1, 2, 3, 1, 2, 3...)。简单、公平,但不考虑服务器负载。
    • 最少连接:发送到活动连接最少的服务器。适用于处理时间可变的请求(有些LLM请求生成10个令牌,有些生成1000个)。
    • 加权轮询:容量更大的服务器获得更多请求。拥有80 GB GPU内存的服务器处理的请求量是40 GB的两倍。
    • 一致性哈希:对请求键进行哈希运算,映射到特定服务器。相同的键始终发送到相同的服务器。适用于:缓存(同一用户的请求命中同一缓存)、会话亲和性和前缀缓存(第17章:具有相同系统提示词的请求发送到具有该提示词KV缓存的服务器)。
  • L4 vs L7负载均衡
    • L4(传输层):基于IP和端口路由。快速但无法检查请求内容。
    • L7(应用层):基于HTTP路径、标头或正文内容路由。可以将/api/chat路由到聊天服务器,将/api/embed路由到嵌入服务器。较慢但更灵活。

缓存

  • 缓存将频繁访问的数据存储在快速存储层(内存)中,以避免重新计算或重新获取。

缓存旁路模式:先检查缓存,未命中时从数据库获取并存储到缓存中以备下次使用

  • 缓存模式
    • 缓存旁路(惰性加载):应用程序先检查缓存。未命中时,从数据库获取、存储到缓存并返回。最常见的模式。
    • 直写:每次写入同时写入缓存和数据库。确保缓存始终是最新的,但会减慢写入速度。
    • 回写:写入只进入缓存;缓存异步刷新到数据库。写入最快,但若缓存刷新前崩溃则有数据丢失风险。
  • 驱逐策略(当缓存满时):
    • LRU(最近最少使用):驱逐最长时间未被访问的条目。最常见的策略。
    • LFU(最不频繁使用):驱逐访问次数最少的条目。当某些条目持续受欢迎时效果更好。
    • TTL(生存时间):条目在固定时长后过期。用于会过时的数据(模型预测缓存5分钟,特征值缓存1小时)。
  • CDN(内容分发网络):用于静态内容(图片、JavaScript、CSS)的全球分布式缓存。遍布100多个地点的服务器从离用户最近的位置提供缓存内容。对于ML:模型权重可以缓存在CDN上以实现快速下载。
  • Redis:标准的键值内存缓存/数据库。支持字符串、列表、集合、有序集合、哈希和流。亚毫秒级延迟。用于:缓存模型预测、存储会话数据、速率限制(统计每个用户每分钟的请求数)和实时特征服务。
  • 对于ML服务:缓存重复输入的预测结果。如果很多用户问"法国的首都是什么?",计算一次答案然后提供缓存结果。对于聊天机器人工作负载,缓存命中率通常为20-40%,按比例降低GPU成本。

数据库

SQL(关系型)

  • SQL数据库PostgreSQL、MySQL)以包含行和列的形式存储数据。表之间的关系通过外键表示。查询使用SQL。ACID保证:
    • 原子性:事务要么完全完成,要么完全回滚。没有部分更新。
    • 一致性:数据库从一个有效状态转换到另一个有效状态。约束条件(唯一键、外键)始终得到满足。
    • 隔离性:并发事务不互相干扰。
    • 持久性:已提交的数据在崩溃后仍然存在(在确认前写入磁盘)。
  • SQL数据库擅长:具有关系的有结构数据、复杂查询(联接、聚合)、严格的一致性要求和数据完整性。

NoSQL

  • NoSQL数据库为了可扩展性和灵活性而牺牲了一些ACID保证:
    • 键值存储Redis、DynamoDB):最简单的模型。按键快速查找。用于缓存、会话存储和特征存储。
    • 文档存储MongoDB、Firestore):存储类似JSON的文档。灵活的模式(每个文档可以有不同字段)。用于用户资料、产品目录和配置。
    • 列族存储Cassandra、HBase):针对写入密集型工作负载和时间序列数据进行了优化。用于事件日志、指标和分析。
    • 图数据库(Neo4j):存储节点和边。针对遍历查询进行了优化。用于社交网络、知识图谱和推荐系统。
    • 向量数据库Pinecone、Milvus、Weaviate、FAISS):存储高维嵌入并支持近似最近邻(ANN)搜索。对于语义搜索、RAG(检索增强生成)和推荐系统至关重要。

CAP定理

  • 在分布式数据库中,最多只能满足三个属性中的两个:
    • 一致性:每次读取都返回最新的写入。
    • 可用性:每个请求都会收到响应(即使某些节点宕机)。
    • 分区容忍性:系统在网络分区(节点无法通信)时仍能继续运行。

CAP定理:由于网络分区不可避免,需要在CP(一致)或AP(可用)之间选择

  • 由于分布式系统中网络分区不可避免,真正的选择是CP(一致但在分区期间可能不可用——如PostgreSQL vs AP(可用但在分区期间可能返回过期数据——如Cassandra、DynamoDB)。
  • 对于ML:特征存储通常选择AP(稍微过期的特征值也比无法预测要好)。模型注册表选择CP(提供错误的模型版本是灾难性的)。

分片

  • 分片将数据库拆分到多台机器上。每个分片持有数据的一个子集。
  • 哈希分片:对键进行哈希运算以确定分片。shard = hash(user_id) % num_shards。分布均匀但不支持范围查询。
  • 范围分片:每个分片持有一个键范围(用户A-G在分片1,H-N在分片2)。支持范围查询但可能产生热点(如果很多用户名字以"S"开头)。
  • 重新分片问题:添加分片会使哈希映射失效。一致性哈希最小化数据移动:添加第n个分片时,只有约1/n的键需要移动。

数据库索引

  • 索引是一种加速查询的数据结构,代价是额外的存储空间和较慢的写入速度。没有索引时,查询会扫描每一行($O(n)$)。有索引时,可以在$O(\log n)$时间内找到目标。
  • B树索引(默认):一种平衡树(第13章、第14章),其中每个节点包含多个键和指针。B树对缓存友好(宽节点适合缓存行)并支持范围查询(WHERE age BETWEEN 20 AND 30)。大多数SQL数据库使用B树。
  • 哈希索引:使用哈希函数将键映射到行位置。$O(1)$查找但不支持范围查询。用于精确匹配查找(WHERE id = 12345)。
  • 复合索引:对多个列的索引。CREATE INDEX ON users(country, city) 加速按国家或按国家+城市筛选的查询,但不能加速仅按城市的查询(最左边的列必须在查询中)。
  • 权衡:每个索引都会加速读取但减慢写入(每次插入/更新/删除都必须更新索引)并占用存储空间(每个索引约占表大小的10-30%)。不要索引所有内容——只索引你经常查询的列。
  • 对于ML系统:特征存储的在线数据库需要在实体键(user_id、item_id)上建立索引以实现快速特征查找。实验跟踪数据库需要在(experiment_id、metric_name)上建立索引以实现仪表盘查询。

API设计

  • 系统通过API进行通信。良好的API设计使系统可用、可进化和可调试:
  • REST约定:使用名词表示资源(/users/models),HTTP方法表示操作(GET=读取、POST=创建、PUT=更新、DELETE=删除),状态码表示结果(200=OK、201=已创建、400=错误请求、404=未找到、429=被限流、500=服务器错误)。
  • 分页:对于返回列表的端点,永远不要一次返回所有结果。使用基于游标的分页(GET /items?cursor=abc&limit=50)或基于偏移量的分页(GET /items?offset=100&limit=50)。对于大数据集,基于游标的分页更高效(基于偏移量的分页需要跳过行)。
  • 版本管理:在API路径前加上版本前缀(/v1/predict/v2/predict)。这样可以在不破坏现有客户端的情况下演进API。客户端按照自己的节奏迁移到v2;v1被弃用但在流量下降之前不会删除。
  • 错误响应:返回结构化的错误信息:
{
    "error": {
        "code": "INVALID_INPUT",
        "message": "特征'user_age'必须为正整数",
        "details": {"field": "user_age", "value": -5}
    }
}

消息队列

  • 消息队列将生产者(生成工作的服务)与消费者(处理工作的服务)解耦。生产者将消息发送到队列;消费者在就绪时拉取消息。
  • 队列为什么重要:没有队列时,如果消费者慢或宕机,生产者会被阻塞。有了队列,生产者发送后就无需等待;队列缓冲消息,直到消费者准备好。
  • Apache Kafka:一个分布式、持久化、高吞吐量的消息队列。消息存储在主题中,每个主题跨多个代理分区。消费者从分区读取,跟踪其位置(偏移量)。Kafka保证分区内的顺序,并可重播消息(日志是持久化的)。
  • 发布/订阅:发布者将消息发送到主题;该主题的所有订阅者都会收到一份副本。用于事件驱动架构:"新模型已部署"触发监控服务、A/B测试服务和日志服务同时响应。
  • 对于ML:预测请求通过HTTP到达,放入Kafka队列,由GPU工作线程处理,结果通过回调或WebSocket返回。队列缓冲突发的流量,并确保即使GPU工作线程崩溃也不会丢失请求。

一致性模型

  • 在分布式系统中,不同节点可能对数据有不同的视图。一致性模型定义了系统提供的保证:
  • 强一致性:写操作之后,所有后续读取(从任何节点)都能看到新值。易于推理但速度慢(需要在节点之间协调)。
  • 最终一致性:写操作之后,读取可能在某段时间内看到过期数据,但最终会看到新值。速度快(无需协调)但需要应用程序处理过期读取。
  • 因果一致性:如果操作A因果上先于B(例如,"写入X然后读取X"),系统保证B能看到A的结果。但不相关的操作可能以任何顺序被看到。
  • 读写一致性:用户始终能立即看到自己的写入,即使其他用户看到的是过期数据。大多数应用程序所需的最小一致性。

弹性模式

  • 速率限制:限制每个用户在时间窗口内的请求数。防止滥用并确保公平访问。使用Redis中的令牌桶或滑动窗口计数器实现。
  • 断路器:如果下游服务开始失败(错误率超过阈值),断路器"断开"并停止向其发送请求(立即返回回退响应)。超时后,"半开"并发送测试请求。如果测试成功,则"闭合"(恢复正常操作)。这防止了级联故障:如果特征存储宕机,模型服务器返回无特征的预测,而不是每次请求都超时。
  • 背压:当系统过载时,它向上游发出信号要求减速。与其接受请求然后失败,不如尽早拒绝多余的请求(返回429或503状态码)。客户端以指数退避重试。
  • 指数退避重试:如果请求失败,等待1秒后重试。如果再次失败,等待2秒。然后是4秒、8秒,依此类推。加入随机抖动以防止所有客户端同时重试(惊群问题)。
  • 幂等性:如果执行两次的效果与执行一次相同,则该操作是幂等的。PUT /user/123 {"name": "Alice"}是幂等的(将名称设置为"Alice"两次没问题)。POST /payments不是(支付两次很糟糕)。使操作幂等可确保重试是安全的。