分布式系统架构

最后更新:2026-03-26

客户端与接入层

入口 Web / App / 小程序

统一认证与鉴权,灰度策略与地域就近接入。

边缘 CDN / WAF / RateLimit

静态加速、DDoS 防护、限流熔断;把“攻击”和“尖峰”拦在系统外。

网关 API Gateway / Ingress

路由、鉴权、统一日志、请求追踪;把复杂性从业务服务挪到治理层。

服务与计算层

计算 微服务 / BFF / GraphQL

按领域拆分,服务间 RPC/HTTP;BFF 面向端做聚合以降低前端复杂度。

异步 消息队列 / 事件流

削峰填谷、解耦;用幂等与去重保障“至少一次”语义下的正确性。

治理 服务发现 / 配置中心

注册与健康检查、动态配置;配合熔断/限流实现弹性与自愈。

数据与存储层

缓存 Redis Cluster / KV

热点数据加速;分布式锁与限流器常落在这里(需要续租与防误删)。

数据库 分库分表 / 读写分离

主从复制、分片路由;跨分片事务复杂,常用最终一致/补偿替代强一致。

对象/文件 Distributed Storage

图片/音视频/附件;多副本/纠删码,冷热分层与生命周期策略降低成本。

基础设施与运维

部署 Kubernetes / 多 AZ

滚动发布、蓝绿/金丝雀;多可用区容灾,业务无感切流。

可观测 日志 / 指标 / 链路追踪

统一链路追踪(TraceId),以 SLO 驱动告警与容量规划。

治理底座 etcd / Consul(配置 / 注册 / 租约)

配置中心、服务注册发现、租约与 Watch 通知等能力的稳定支撑。

1
Web / App / 小程序:
  • 一)认证体系:
    • ① 底层机制:
      • Cookie
        • 设置:服务端通过 HTTP 响应头 Set-Cookie 下发,例如 Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=3600
        • 浏览器:域名 / 路径 / 过期策略把这条键值对持久化在本地,之后访问同一域名/路径时会自动在请求头中附带 Cookie: session_id=abc123 回传给服务端。
        • 本质:一个容量较小、受作用域限制的“随请求自动发送的键值对存储”,可用 HttpOnly(JS 无法访问,防 XSS)、Secure(仅 HTTPS 发送)、SameSite(辅助防 CSRF)、Max-Age/Expires(控制有效期)等属性强化安全性。
        • 认证:Cookie 名称可以自定义,但在认证场景下应只存不可伪造的随机会话标识(如 session_id),不要直接存 user_id 这类业务身份字段,否则被篡改后会造成严重越权问题。
        • 时机:首次访问创建匿名会话、登录成功后建立/升级会话、Session 续期或刷新的时候,框架都会自动下发或更新携带 session_id 的 Cookie。
      • Session
        • 设置:当建立会话或登录成功时,服务器会在内存 / Redis / 数据库中为该浏览器创建一条 Session 记录,例如 { session_id: "abc123", user_id: 123|null, is_login: true|false, expire_at: 1710000000 },本质上是一个承载用户状态的容器
        • 浏览器:服务器生成随机 session_id,通过 Set-Cookie: session_id=xyz; HttpOnly; Secure 发给浏览器,之后每次请求浏览器都会自动带上 Cookie: session_id=xyz
        • 请求:服务器解析 Cookie 取出 session_id → 查 Redis / 内存 / 数据库 → 还原出“这是谁、是否登录、权限有哪些”,敏感数据始终只保留在后端受控环境中,不直接暴露给客户端。
        • 安全实践:推荐在登录成功后废弃旧的 session_id、颁发新的 session_id 绑定登录用户(即 Regenerate Session),以防止 Session Fixation(会话固定攻击)。
      • JWT
        • 本质:把用户标识与权限声明打包成一段自包含的 Token。JWT 字符串形如 xxxxx.yyyyy.zzzzz——用两个点切成三段 header.payload.signature,由服务器用密钥签名后发给前端;后续请求在请求头里带上 Authorization: Bearer <token>,后端校验签名即可确认用户身份。
          • 1)拆解三段:
            • Header(头部):描述这枚令牌的基本信息,常见如类型为 JWT、签名算法(例如 HS256)。通俗理解:像身份证上的“发证机关说明”——告诉大家:这是一张用什么算法做出来的证件。
            • Payload(负载):存放声明(Claims),例如用户 ID、用户名、角色、过期时间 exp 等。注意:常见实现里这部分往往只是 Base64 编码不是加密,任何人解码都能看到明文,因此绝对不能放密码等敏感机密。通俗理解:像身份证正面——姓名、角色、到期日都写在上面。
            • Signature(签名):服务器把 Header 与 Payload 按约定规则拼接,再混入只有服务端掌握的密钥(Secret Key)(非对称场景下则用私钥签名、公钥验签),用 Header 里声明的算法算出一串摘要。通俗理解:像证件背面的防伪镭射水印——没有密钥就伪造不出一致的水印。
          • 2)不再依赖 Session 存储?
            • Session:前端拿 session_id 这张编号来查询”;后端必须去 Redis/数据库里核对这张编号对应的是谁、是否仍有效。结论:服务端必须持久保存会话状态,否则认不出人。
            • JWT:前端拿整张 JWT 证件来办事;后端只用自己掌握的密钥,对收到的 Header+Payload 重算一遍签名,与 Token 上的 Signature 一致,则说明令牌真实且 Payload 未被篡改,再直接读取其中的 user_id 等字段即可。结论:服务端只需安全保管密钥/公钥配置,不必为每一次会话在库里常驻一条记录(仍可用黑名单、短过期、refresh 等做风控,见下文代价)。
          • 3)核心优势
            • Session 需要依赖中心化存储,多机部署时Session 模式常要共享 Redis,否则在 A 机登录、请求落到 B 机会“认不出”,每次请求都要“Cookie → session_id → 查 Redis”带来网络 IO 与性能损耗;在微服务中,每个服务都要访问同一 Session 存储,架构耦合度高;同时强依赖浏览器 Cookie,对 App、CLI、第三方调用和跨域场景都不够友好;JWT 模式下,各实例只要配置同一密钥或信任同一套公钥,同一枚令牌在App、CLI、第三方调用可统一用 Authorization: Bearer,到哪台机器都能本地验签
        • 同源策略与跨域:前端 Origin 和后端 Origin 不同(比如 Next.js 跑在 http://localhost:3000,FastAPI 跑在 http://localhost:8000;协议/域名/端口有一个不一样),浏览器的同源策略与跨域:就会把这次跨域请求当成“有风险”,如果后端没显式允许默认不让你的前端自由访问后端资源。
        • 解决方案:需要让后端(如 FastAPI)通过 CORS 中间件显式声明“哪些 Origin 可以带着 JWT 来访问我”,例如: allow_origins=['http://localhost:3000']allow_methods=['*']allow_headers=['*'];这相当于给来自指定源的浏览器请求发了一张“通行证”,告诉它“这是可信前端,可以带着 JWT 来调用接口”。
        • JWT 的代价与场景决策:Token 一旦签发,除非过期或配合黑名单,很难像 Session 那样立即撤销;一旦泄露,攻击者可在有效期内直接调用 API;payload 可读、体积也比 session_id 大;因此在单体、强安全、并发压力一般的后台里仍然适合用 Cookie + Session,而在前后端分离、微服务、高并发 API、移动端/第三方接入、需要跨域的系统里,JWT 带来的“无状态、高扩展、跨端统一”优势更大,通常会选用“短期 access_token(JWT)+ 较长期 refresh_token”的组合方案。
      • OAuth2
        • 总结:OAuth 2.0是主流的授权(Authorization)标准:在不把用户密码交给第三方的前提下,让第三方应用获得对用户资源的有限访问权限
        • 类比理解:你在柜台(授权服务器)出示身份证并确认入住 → 拿到房卡(access_token)→ 用房卡开门(访问资源)。门锁只认卡,不需要你反复出示身份证。
        • 核心角色:
          • 资源所有者:用户,本人拥有数据。
          • 客户端:想访问你数据的第三方应用。
          • 授权服务器:负责登录、征得同意、签发令牌(如微信/Google/GitHub)。
          • 资源服务器:托管受保护资源并验证令牌(如用户信息、头像、通讯录 API)。
        • 标准流程:
          • ① 发起授权:客户端把用户重定向到授权服务器
          • ② 用户登录并同意:用户在授权服务器完成身份验证,并确认授权范围。
          • ③ 返回授权码:授权服务器重定向回客户端回调地址,并附带一次性、短有效期的 code
          • ④ 后台换取令牌:客户端服务器端(不经浏览器)用 code + client_secret 发送给授权服务器,授权服务器 把 Access Token 直接塞给你的后端(在 OAuth2 的体系中,授权服务器通常拥有一个专门的处理端点,技术上称为 Token Endpoint令牌端点。)。
          • ⑤ 获得令牌:拿到 access_token(常配 refresh_token 用于续期)。
          • ⑥ 访问资源:调用资源接口时带 Authorization: Bearer <access_token>,资源服务器验证后返回数据。
        • Token 补充:OAuth2 不强制令牌格式,但工程里 access_token 常用 JWT
          • 好处:资源服务器可用公钥/共享密钥本地验签判断合法性,无需每次都回授权服务器询问。
          • 为什么要先给 code 再换 token?把令牌从“浏览器前台传递”挪到“服务端后台交换”:减少令牌泄露(URL、历史记录、Referer)风险,并用 client_secret 让授权服务器确认“真的是这个客户端”来换取令牌。
  • 二)灰度策略
    • 核心目标:在真实生产流量下测试新版本,但把出问题的范围控制在可接受的极小比例,并且能一键回滚。
    • 常见分流维度:
      • 按用户 ID / 设备 ID 取模:例如 uid % 10 < 1 的用户进入新版本,可以保证同一用户每次访问都稳定落在同一版本。
      • 按权重随机:例如 5% 流量走 v2,95% 走 v1;随着观测指标稳定,逐步把 5% → 20% → 50% → 100%。
      • 按特定属性:只对“内部员工”“测试用户”或某个地区/渠道开放新版本。
      • 按 Header:通过特定 Header把请求路由到新版本,方便自动化测试或灰度探针。
  • 三)地域就近接入(Geo-proximity Routing)
    • 核心目标:让“广州的用户请求广州的机房”,减少跨洲/跨国传输时间,同时满足数据合规与容灾需求。
    • 实现原理:
      • 智能 DNS:DNS 服务器根据客户端 IP 所属地区,返回最近 Region 的 IP,例如华北用户解析到北京集群,华南用户解析到深圳集群。
      • ECS:在 DNS 查询中携带客户端子网信息,让权威 DNS 更准确地感知终端位置。
      • Anycast IP:多个机房对外暴露相同 IP,由网络基础设施把流量路由到“就近”的节点。
    • 带来的价值:
      • 更低延迟:物理距离更近、网络跳数更少,首屏时间和接口响应显著改善。
      • 合规:配合 Region 级数据隔离,满足“数据不出境”等监管要求。
      • 容灾隔离:单个 Region 故障时,可通过 DNS/路由策略把流量切到其他健康 Region,实现跨地域容灾。
2
CDN / WAF / Rate Limit
CDN(内容分发)

将静态资源缓存到边缘节点,用户就近访问,显著降低首包时间与跨地域时延; 其中“边缘节点”是部署在离用户更近网络位置的 CDN 服务器,用于在源站之外提前响应内容请求; 源站是内容的权威来源与回源入口(负责生成/更新数据),CDN 服务器是其缓存与分发副本(命中则直接返回,未命中再回源拉取); 同时 CDN 还能吸收大规模突发流量,减少源站带宽与连接压力。

WAF(Web 应用防火墙)

基于规则与行为分析拦截 SQL 注入、XSS、命令注入、恶意爬虫等应用层攻击; 通过虚拟补丁与策略更新,在业务未发版时快速降低漏洞暴露风险。

Rate Limit(流量限速)

按 IP、用户、Token、接口维度施加 QPS/并发阈值( 令牌桶!、漏桶、 滑动窗口?); 抑制刷接口、暴力破解与突发洪峰,保障核心接口可用并防止下游雪崩。

3
API Gateway / Ingress(Nginx 反向代理)
  • 核心角色:
    • Nginx 在前后端分离架构中最重要的定位是反向代理,即系统的统一入口。浏览器只访问 example.com,由 Nginx 决定请求最终落到哪个服务。
  • 请求分流逻辑:
    • 访问 example.com/(或 /about/dashboard)→ 转发到 Next.js(如 127.0.0.1:3000)。
    • 访问 example.com/api/... → 转发到 FastAPI(如 127.0.0.1:8000)。
  • 为什么在 Next.js + FastAPI 中很关键:
    • 1)隐藏后端拓扑:用户无需感知 3000/8000 端口,内部服务位置被隐藏,暴露面更小。
    • 2)统一同源入口:浏览器视角下页面和 API 都来自同一域名与端口(80/443),线上可显著简化跨域问题。
    • 3)HTTPS 统一终止:证书安装与续期集中在 Nginx 层(SSL Termination),后端服务可专注业务处理。
    • 4)负载均衡与弹性:FastAPI 可横向扩容为多实例,由 Nginx 按策略分配流量。
    • 5)静态资源效率:Nginx 对静态文件传输和连接复用做了深度优化,可作为高效静态分发入口。
  • CORS 边界说明:
    • 线上经 Nginx 统一域名时:通常是同源访问,浏览器不再把 3000/8000 视为两个外部来源。
    • 仍需配置 CORS 的场景:本地开发直连不同端口(3000 ↔ 8000)。
    • 因此工程上常见做法是:本地保留 FastAPI 的 CORSMiddleware 以便开发联调,线上主路径通过 Nginx 统一入口降低跨域复杂度
4
微服务 / BFF / GraphQL
  • 核心定义:把“大而全的单体系统”拆成“按业务能力分工的小团队服务”,再用 BFF/GraphQL 给前端一个更好用的统一取数入口。
  • 通俗类比:像餐厅后厨分工——炒菜、甜品、饮品各有工位(微服务);前台服务员按“堂食/外卖/自提”整理菜品再一次端给顾客(BFF);顾客只点自己要的菜,不必把整本菜单全买走(GraphQL 按需取数)。
  • 分工:
    • 微服务:按领域拆分(用户、订单、支付、内容等),各自独立开发、部署和扩容,避免“一个模块改动牵一发动全身”。
    • BFF:面向不同终端(Web / App / 小程序)做接口聚合与裁剪,减少前端多次请求和数据拼装成本。
    • GraphQL:由客户端声明“要哪些字段”,后端按需返回,减少过度返回与接口版本膨胀,适合复杂页面聚合查询。
5
消息队列 / 事件流
  • 核心定义:把“同步调用”改成“异步投递”,让生产者只负责发消息,消费者按自己的节奏处理。
  • 典型场景:
    • 削峰填谷:秒杀下单先写队列,后端慢慢处理,避免峰值把数据库打爆。
    • 系统解耦:下单后发“订单创建”事件,支付、库存、物流各自订阅处理,彼此不强绑定。
    • 异步通知:短信、邮件、日志落库、风控计算等非强实时任务异步化。
6
服务发现 / 配置中心
  • 核心:
    • 服务发现:实例启动后注册到注册中心,并定期心跳续约:注册中心无法“主动感知”实例是否还活着,必须靠心跳来证明存活;一旦实例宕机、网络断连或进程卡死导致心跳超时,租约会自动过期并被摘除,调用方就不会继续把流量打到失效节点。调用方先查可用实例,再做负载均衡,从而保障高可用与故障自愈。
    • 配置中心:把数据库连接、开关阈值、灰度比例、限流参数等从代码中抽离,统一托管并按环境/应用/版本下发,支持变更审计与一键回滚。
  • 核心功能:
    • 1)动态感知:服务扩缩容、重启、迁移后,调用方无需改代码即可获取最新地址。
    • 2)健康治理:基于心跳与探针自动摘除不健康实例,提升调用成功率。
7
Redis Cluster / KV
  • 核心定义:Redis 是高性能内存型 KV 存储,用来“把高频数据放到离 CPU 最近的地方”,用空间换时间,降低数据库压力并提升系统吞吐。
  • Redis Cluster:
    • 1)数据分片:16384 个槽位(Hash Slots)
      • 通俗理解:先固定好 16384 个“储物格”,再把这些格子分给不同机器管理,而不是直接“按机器分数据”。
      • 为什么这样设计:如果直接按机器分(A/B/C 三台),新增第 4 台时往往要大规模重算和搬迁;而按槽位分时,新增机器只需从原机器各迁一部分槽位给新机器,迁移更平滑、影响更小。
      • 示例:A 管 0-5000,B 管 5001-10000,C 管 10001-16383。扩容到 D 时,只迁部分槽位给 D,而不是全量洗牌。
    • 2)主从复制与读写分离
      • Replication 的本质:
        • 主节点(Master):负责写请求,同时也具备读能力。
        • 从节点(Replica):不接受写,通过复制链路持续同步主节点数据(复制偏移、复制积压缓冲区等机制保障同步连续性)。
      • 为什么很多系统仍是“主读 + 主写”:
        • 一致性优先:复制通常存在延迟,若刚写完立刻读从,可能读到旧值;强一致读应走主节点。
        • 复杂度更低:小中型系统把所有请求打主库,架构简单、排障直接。
        • 兜底能力:当从节点异常或延迟过大时,主节点必须能独立承接全部读写。
      • 读写分离是扩展策略:当读压力升高时,常采用“写进主、读走从(可负载均衡)”来扩展查询吞吐;但需配套处理复制延迟带来的读旧问题(如读主兜底、读你所写策略、关键查询强制主读)。
      • 故障转移怎么发生:主节点故障后,Redis Cluster 基于Raft算法把某个从节点提升为新主节点,并接管对应槽位服务。业务通常表现为短暂抖动后恢复。
    • 3)客户端路由与同槽策略
      • 路由公式:客户端先算 slot = CRC16(key) % 16384,再把请求直接发到负责该槽位的节点。
      • 同槽策略:多 Key 操作必须在同一槽位上执行,若落在不同槽位会触发跨槽限制,无法直接执行。
      • 破解方式:{} 指定同一哈希标签,例如 {user100}:order{user100}:balance,Redis 只对 {user100} 计算槽位,因此两者必然落到同一槽位,便于多 Key 操作。
  • 核心使用场景:
    • 1)缓存加速:缓存热点读,降低 DB QPS。
    • 2)共享状态:会话、验证码、幂等标记、任务进度等短时状态集中存放。
    • 3)流量治理:令牌桶/滑动窗口限流,保护下游接口。
    • 4)分布式协作:分布式锁、延迟队列、计数器、排行榜等。
  • 工程风险:
    • 缓存穿透:大量请求访问根本不存在的 Key,缓存未命中后每次都打到数据库,最终把 DB 拖慢。
      • 完整流程:请求到来 → Redis 未命中 → DB 返回 Null → 回写“空记录”(如 value=empty)→ 设置短 TTL。
      • 短 TTL:短期内可拦截重复无效请求、保护数据库;同时又能在数据后续被创建时尽快过期,避免用户长时间读到“假空值”,兼顾防打库与一致性。
      • 两道防线:先用布隆过滤器做低成本粗筛(判不存在就直接拦截),再用空值缓存兜底处理误判或漏网请求,在性能、准确性与一致性之间取得平衡。
    • 缓存击穿:某个超级热点 Key在过期瞬间被高并发同时访问,所有请求同时回源,DB 压力瞬间飙升。
      • 方案1:互斥锁 / SingleFlight:第一个请求发现缓存失效后先抢锁;抢到锁的人负责查库并回填缓存,没抢到锁的人短暂等待后重试。
        优点:数据库保护最稳、拿到的数据通常最新;缺点:高并发下部分请求会有等待感。
      • 方案2:逻辑过期 + 异步刷新:不给 Redis 设置真实 TTL,而是在 Value 里存 expire_at;请求发现逻辑过期时,先返回旧值保证秒回,再异步线程/任务刷新缓存。
        优点:用户几乎零等待、数据库压力小;缺点:短时间内可能读到旧值(弱一致)。
      • 如何选型:强一致场景(支付、余额、库存)优先互斥锁;可容忍短暂旧数据的场景(新闻详情、社交动态、统计报表)优先逻辑过期 + 异步刷新。
    • 缓存雪崩:大量请求在同一时刻“穿过缓存”直打数据库,导致数据库瞬时过载甚至雪崩。
      • 触发原因:
        • 大规模 Key 同时过期:例如活动场景里 10 万个缓存统一设置 2 小时 TTL,到点一起失效,请求同时回源。
        • Redis 集群故障:断网、内存溢出、节点宕机等导致缓存层不可用,请求被迫全部打到 DB。
      • 预防层:过期时间加随机抖动:不要固定 60 分钟,可用 60 ± rand(1,10) 分钟,把失效时间打散,避免同一秒集中回源。
      • 分流层:多级缓存(L1 + L2):应用本地缓存(L1)保存最热数据,Redis(L2)保存海量数据;即使 Redis 抖动,L1 也能扛住第一波冲击。
      • 保命层:限流 + 降级 + 兜底:当 DB 承载上限为 1000 QPS,就限制放行速率;超出部分返回“系统繁忙”或默认数据(Fallback),通过“丢卒保车”保证核心链路不宕机。
    • 一致性问题:数据库(DB)和缓存(Redis)是两个独立系统,更新无法天然原子化,链路里任一步骤失败或乱序都可能导致脏读/旧读。
      • 成因:DB 成功不代表缓存一定成功(网络抖动、超时、重试失败都可能发生),并发更新时还会出现后发先至,导致“DB 新、缓存旧”或相反。
      • 风险:先更 DB 再更缓存时,若缓存更新失败或发生乱序回写,会残留旧值;先删缓存再更 DB 时,在 DB 更新完成前的窗口期,读请求可能回源旧值并脏回填。
      • 基线策略:工程上通常采用先更新数据库,再删除缓存(而非更新缓存),并配合 TTL、重试/补偿,降低不一致概率与影响窗口。
      • 手段与选型:可结合延时双删、Binlog 异步清理、版本号校验等机制做增强;强一致场景关键读走主库并配合锁/版本控制,弱一致场景优先性能与吞吐。
8
分库分表 / 读写分离!
  • 核心定义:当单库在容量、QPS、并发连接上接近上限时,把数据横向拆开(分库分表)并把读流量分摊出去(读写分离),以换取更高吞吐与可用性。
  • 为什么要拆:单库上堆机器(纵向扩容)成本高且有天花板;横向拆分能把写入压力、存储容量和索引体积分散到多节点。
  • 分库分表:先在一台 PostgreSQL 里做分区(例如订单按月拆分),如果机器还是扛不住,再把数据拆到多台库;这时需要路由层,例如规则设为 user_id % 4,查 user_id=1001 时会直接命中第 1 号分片,只查一个库,不会 4 个库全扫。
  • 分片键:好的分片键是每个库都差不多忙且符合查询习惯。实操上优先选 user_id/tenant_id 这类高基数字段;如果业务大多按用户查询,就优先按用户分片;时间字段更适合做分区裁剪,通常不单独作为唯一分片键。
  • 读写分离(PostgreSQL):
    • WAL:WAL(预写日志)。一次 INSERT/UPDATE/DELETE 发生时,系统先把变更顺序追加到 WAL 并落盘,然后才允许事务提交。
    • 脏页:脏页是“内存中已被修改、但磁盘文件里仍是旧版本”的数据页;它会由后台进程稍后异步刷回表文件。
    • 为什么异步落脏页:先写 WAL 是顺序追加,磁盘处理顺序写通常比随机写更高效;事务提交时只需确认 WAL 已落盘,不必等待所有数据页写回,所以响应更快;脏页交给后台进程按批次刷盘,能把零散写合并、减少 I/O 抖动;如果机器突然宕机,数据库还能根据 WAL 把已提交事务重放回来,即使当时部分脏页还没落盘,也能恢复到一致状态。
    • checkpoint / bgwriter:bgwriter 会持续地把共享缓冲区里的部分脏页提前刷盘,目的是在业务高峰时减少后端进程自己刷盘的概率,降低抖动;checkpoint 会周期性(或 WAL 达到阈值)触发,把某个时间点之前必须持久化的脏页推进到磁盘,并写入 checkpoint 记录。恢复时数据库从最近 checkpoint 开始回放 WAL,恢复窗口更短。简单说:bgwriter 负责平时分摊写压力,checkpoint 负责定期收口与缩短崩溃恢复时间。
    • 主从复制怎么来的:pg流复制下,主库持续产生日志、从库持续回放日志,因此从库是“追日志”的,天然会有复制延迟。
9
对象/文件存储
  • 核心定义:对象/文件存储就是把图片、音视频、附件这类“非结构化数据”放到专门的存储系统里,而不是直接塞进数据库表。数据库更擅长存“记录”,对象存储更擅长存“大文件”。
  • 各司其职:如果把大量图片/视频直接放进数据库,库会迅速膨胀,备份恢复慢、索引效率下降、成本也高;分离后,数据库压力更小,文件访问也能走 CDN 做就近加速。
  • 对象存储能配 CDN、数据库通常不适合:CDN 适合缓存“可通过 URL 直接访问、内容相对稳定、基于 HTTP/HTTPS 传输”的静态资源。对象存储天然满足这些条件:每个对象有稳定路径、协议兼容 CDN、图片/视频上传后通常变化不频繁;而数据库查询结果往往是动态生成(同一接口对不同用户返回不同数据)、数据库协议也不是 HTTP(如 PostgreSQL 二进制协议),再加上数据高频变更带来一致性要求,因此不适合直接用 CDN 全局缓存。实践上让 PostgreSQL 只保存文件元数据与 URL,对象存储保存文件本体,CDN 负责就近分发,是性能、成本和一致性的最优平衡。
  • 治理:通过冷热分层和生命周期策略控制成本——热数据放高性能层,冷数据自动迁移到低成本层;过期文件自动归档或删除,避免“垃圾文件”长期占用存储。
  • 工程实践要点:上传通常走“预签名 URL 直传”;下载走 CDN;文件访问权限用短期签名控制;元数据(owner、content_type、hash、状态)落库,便于审计与防重复上传。
10
Kubernetes / 多 AZ

负责部署弹性与跨 AZ 容灾。

11
日志 / 指标 / 链路追踪

统一观测系统健康与故障定位。

12
etcd / Consul(配置 / 注册 / 租约)

作为配置、注册与租约治理底座。

分布式系统

1
分布式锁(Distributed Lock)

把分布式锁想成“连锁店里洗手间的唯一钥匙”:同一时间只能有一个人拿到钥匙进入。

  • 本质:互斥(同一时刻只有一个持有者)+ 可恢复(持有者宕机能释放)+ 可证明(能证明“我是锁的主人”)。
  • 常见坑:
    • 锁过期:任务做太久,锁自动失效,第二个人也进来了(并发进入)。
    • 误删他人锁:A 以为自己是主人,结果把 B 的锁删了(必须用 token 校验)。
  • 实现方案:Redis(SET NX PX + owner token + Lua 原子删锁)、etcd(lease + txn 原子比较交换)、ZK(临时顺序节点)。
2
分布式事务(Distributed Transaction)

把分布式事务想成“点餐”:收银(服务 A)收了钱,后厨(服务 B)必须出餐;钱收了餐没出就是事故。跨服务要做到“要么都成功,要么都失败”,代价通常很高。

  • 2PC / XA:领班两轮确认“都准备好了再一起提交”。优点强一致;缺点阻塞、超时复杂、吞吐差。
  • Saga(补偿):失败就“退钱/撤销”。注意补偿不一定可逆(比如库存扣减好补,发券/发货就更难)。
  • TCC:Try 预留资源 → Confirm 确认 → Cancel 取消;一致性更强但对业务改造大。
  • Outbox / CDC:先在本地库“写账单+写发件箱”,再异步投递到下游;靠幂等去重实现最终一致。
3
分布式计算(Distributed Compute)

把分布式计算想成“切土豆”:100 斤土豆一个人切很久,10 个人并行能快很多。系统要做的不止是并行,还要能容错(有人中途停工怎么办)与控流(别让订单堆成山)。

  • 在线计算:像前台接待,追求低延迟;靠缓存、限流、熔断、降级兜底。
  • 离线 / 流式:像流水线,追求稳定吞吐;关键是 checkpoint(断点续做)与一致处理语义(at-least-once / exactly-once 的成本边界)。
  • 反压(Backpressure):后厨忙不过来,就要让前台少接单:网关 → 队列 → 消费者 → 下游依赖要“层层控速”。
4
分布式数据库(Distributed Database)

把数据库想成“菜单”:一张纸写不下就分成多本(分片),怕丢就复印多份(复制)。你获得的是更高可用与更大容量,但要接受更复杂的一致性与查询成本。

  • 分库分表(Sharding):写入与容量横向扩展,但跨分片 join/事务很贵(通常需要在业务层改写)。
  • 读写分离(Replication):主库写、从库读;代价是复制延迟导致“读到旧数据”,需要读一致策略(读你所写/会话一致)。
  • NewSQL:通过共识复制在扩展性与强一致之间取得更好平衡,但工程与运维门槛更高。
5
分布式部署(Distributed Deployment)

把发布想成“新菜试卖”:不可能一夜之间全城门店全部换菜单。生产发布的核心是控制风险半径:先小范围试、观察指标、随时回滚。

  • 金丝雀 / 灰度:先让少量门店试卖(按用户/地域/比例切流),指标稳再扩大。
  • 蓝绿发布:两套环境并存,切流几乎“一键”;回滚也最快。
  • 多 AZ:门店分布在不同供电区,一个区故障,另一个区继续营业(容灾)。
6
分布式存储(Distributed Storage)

把分布式存储想成“海量食材仓库”:不止要存得下,还要取用快、坏了不丢、成本可控。不同存储形态各司其职:对象存储更适合大文件,块存储更适合数据库,分布式文件系统更适合共享读写。

  • 副本 vs 纠删码:副本=多买几份备货(简单快但占空间);纠删码=拆分+校验恢复(省空间但重建复杂)。
  • 一致性取舍:强一致更“准”,但写放大与延迟更高;最终一致更“快”,但可能短时间读旧。
  • 热点问题:爆款食材窗口排队很长;要靠分片键、哈希打散、热点隔离与缓存分层来“分流”。