# 电商平台 架构模板 > **代表产品**:Amazon、Shopify、淘宝、各类在线零售与交易平台 > **一句话定位**:在海量并发下,让「钱货两清」这件事**绝不出错**,还要扛得住大促那一波洪峰。 --- ## 1. 一句话定位 一个电商平台 = **一台「读多写少、读可凑合、写必须分毫不差」的交易机器** + **一套专门用来扛大促洪峰的削峰装置**。 架构上最反直觉的一点:它和普通网站最大的不同,不是页面多,而是**这里同时存在两种截然相反的数据**。逛商品、看推荐、读评价——这些是海量的读,慢一点、旧一点都没人追究;但**扣库存、下订单、付钱**——这些写一旦错了,就是少卖了钱、超卖了货、重复扣了款,是要赔钱、要打官司的。整套架构的核心命题,就是**「把这两种数据用完全不同的强度去对待」**:对钱和库存锱铢必较,对浏览和推荐网开一面。 ## 2. 业务本质:它在解决什么问题 平台要做的事,说白了就一句:**让买家能放心地把钱交出来、让卖家能准确地把货发出去,中间一分钱、一件货都不能错。** 它的业务现实带来三条铁律: - **钱货必须两清,且能对账**:用户付了钱就必须有货、有订单;平台收了钱就必须能和支付方对得上账。这里没有「最终大概一致」的余地。 - **库存是有限的、会争抢的**:最后一件商品不能同时卖给两个人(超卖),否则就是必然的客诉和赔付。 - **流量极不均匀,大促是常态**:平时风平浪静,一到大促/秒杀,瞬时流量可以涨几十上百倍,系统要么扛住、要么崩盘上新闻。 **关键事实:在电商里,「读错了」用户骂两句,「写错了」平台真金白银地赔。** 这一条决定了后面几乎所有架构取舍——尤其是「**为什么不能用一个大事务把整个下单流程串起来**」。 ## 3. 核心需求与约束 把需求拆成两类。**区分「功能」和「质量」是架构师的第一基本功。** **功能性需求(系统要能做什么):** - [ ] 浏览与搜索:逛商品目录、搜关键词、看推荐和评价。 - [ ] 购物车:加购、改数量、结算。 - [ ] 下单:校验、扣库存、生成订单。 - [ ] 支付:发起支付、确认收款、失败回滚。 - [ ] 库存管理:扣减、回补、防超卖。 - [ ] 履约/物流:发货、跟踪、签收。 - [ ] 价格与促销:定价、优惠券、满减、秒杀价。 - [ ] 用户与账户:登录、地址、订单历史。 **非功能性需求 / 质量属性(这才是架构的主战场):** | 质量属性 | 目标 | 为什么对这类系统重要 | |---|---|---| | **资金强一致** | 100% 正确、可对账 | 钱算错一分都是事故;支付与账务必须经得起逐笔核对。 | | **库存正确性** | 绝不超卖 | 卖出去发不出货,是必然的客诉和赔偿。 | | **浏览延迟** | 商品/搜索页快速返回 | 逛得不顺手,用户直接关页走人,转化率下降。 | | **峰值吞吐** | 扛住大促数十倍洪峰 | 大促当天崩了,是直接的销售损失和品牌事故。 | | **可用性** | 核心交易链路高可用 | 下单付款挂了,等于停业。 | **关键约束(不可逾越的边界):** - 🔴 **不同数据要用不同强度的一致性**。这是头号设计原则:钱和库存要强一致,浏览和推荐可以最终一致。混为一谈,要么慢死,要么出错。 - 🔴 **库存的「最后一件」是天然的争抢点**。热点商品的单行库存会被无数请求同时抢,这是绕不开的物理瓶颈。 - 🔴 **大促洪峰不可预测又极端**,系统必须能削峰、能限流、能降级,而不是硬扛到崩。 - 🔴 **一切涉及钱的操作必须幂等**:网络会重试、用户会狂点按钮,绝不能因此重复下单、重复扣款。 ## 4. 架构全景图 ``` 用户(Web / App) │ ┌─────────────▼─────────────┐ │ 接入层:网关 / CDN │ 鉴权、限流、削峰、降级 └─────────────┬─────────────┘ │ ┌──────────────── 读路径(读多,重缓存)─┴─ 写路径(写少,要强一致)──────────┐ │ │ ▼ ▼ ┌────────────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────────┐ │ 商品目录服务 │ │ 搜索 │ │ 推荐 │ │ 购物车服务 │ │ (海量读,缓存) │ │ (专用引擎)│ │ (最终一致)│ └─────────┬──────────┘ └───────┬────────┘ └──────────┘ └──────────┘ │ 结算 │ 多级缓存 ▼ ▼ ┌──────────────────────────┐ ┌────────────────┐ │ 下单编排(Saga 协调者) │ │ 内存级 KV 缓存 │ ◀── 缓存预热 │ 一步步推进,失败按步回滚 │ └────────────────┘ └───┬────────┬─────────┬───┘ │ │ │ ┌──── 削峰填谷:异步下单队列 ─────────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │ 订单服务 │ │ 支付服务 │ │ 库存服务 │ │ 价格/促销 │ │(状态机) │ │(强一致) │ │ 防超卖/分段库存│ └──────────────┘ └────┬─────┘ └────┬─────┘ └──────────────┘ │ │ ▼ ▼ ┌──────────────────┐ ┌──────────────┐ │ 履约 / 物流 │ │ 对账系统 │ │ (发货、跟踪) │ │ (逐笔核对) │ └──────────────────┘ └──────────────┘ ``` > 灵魂部件是右侧那条**写路径**:下单编排 → 库存 → 订单 → 支付。左边的浏览/搜索/推荐再大,本质都是「读」,可以靠缓存横向堆机器解决;真正难、真正不能错的,是这条**又要正确、又要扛洪峰**的交易链路。 ## 5. 组件职责 逐个说明上图里每个关键部件**做什么 + 为什么需要它**(没有「为什么」的部件就是过度设计)。 - **接入层(网关 / CDN)**:鉴权、限流、把静态资源和图片推到边缘,大促时承担**削峰和降级**的第一道闸。*为什么需要*:洪峰必须在最外层就被整形和拦截,别让它直接砸到核心服务。 - **商品目录服务**:管理商品信息,**读极多、写极少**,靠多级缓存扛量。*为什么需要*:逛商品是流量最大的入口,必须用缓存把数据库护在后面。 - **搜索**:用专用的搜索系统支持关键词、筛选、排序。*为什么需要*:商品搜索是「按内容找」,这是通用数据库做不好的,需要专门的倒排索引能力。 - **推荐**:基于行为算「你可能想买」,可以**最终一致、可以略旧**。*为什么需要*:提升转化,但它本质是「锦上添花」,慢一点、旧一点不影响钱货。 - **购物车服务**:暂存用户想买的东西,直到结算。*为什么需要*:加购到下单之间有时间差,需要一个轻量、可快速读写的暂存区。 - **下单编排(Saga 协调者)**:把「扣库存 → 创建订单 → 调支付 → 确认/回滚」这一串步骤**按顺序推进,任一步失败就按相反顺序补偿回滚**。*为什么需要*:下单跨多个服务,又不能用一个大事务锁住全部(太重、扛不住量),所以用 Saga 把它拆成「可逐步推进、可逐步回滚」的长流程。 - **库存服务**:扣减、回补库存,**死守不超卖**;对热点商品用分段库存或排队化解争抢。*为什么需要*:库存是钱货两清的物理前提,超卖直接等于赔付。 - **订单服务**:维护订单的**状态机**(待支付 → 已支付 → 已发货 → 已完成 / 已取消 / 已退款),保证状态只能合法流转。*为什么需要*:订单是交易的「事实凭证」,它的每一次状态变化都要可追溯、不可乱跳。 - **支付服务**:对接资金,**强一致 + 幂等**,确保「钱只扣一次、且和订单对得上」。*为什么需要*:这是整条链路上最不能错的环节,任何重复或丢失都是资损。 - **价格 / 促销**:计算最终成交价(原价、优惠券、满减、秒杀价)。*为什么需要*:价格涉及钱,且促销规则多变,需要集中、可审计地算。 - **履约 / 物流**:订单确认后安排发货、提供物流跟踪。*为什么需要*:把「线上成交」落地成「线下到货」,且这部分天然是异步的。 - **对账系统**:逐笔核对订单、支付、账务,发现并修复不一致。*为什么需要*:既然交易链路用了「最终一致」(Saga),就必须有一套兜底机制来**保证最终真的一致**——对账是资金安全的最后防线。 ## 6. 关键数据流 挑 3 个最能体现这个系统特点的场景。 **场景一:浏览到加购(读多、重缓存的路径)** ``` 1. 用户逛首页/类目 ──▶ 网关 ──▶ 商品目录服务 2. 先查【内存级 KV 缓存】── 命中 ──▶ 直接返回(绝大多数请求走这里) └ 未命中 ─▶ 回源数据库 ─▶ 结果写回缓存 ─▶ 返回 3. 搜索关键词 ──▶ 专用搜索引擎(倒排索引)返回结果 4. 页面上的「猜你喜欢」──▶ 推荐服务(数据可略旧,最终一致即可) 5. 用户点「加入购物车」──▶ 购物车服务暂存 ``` > 注意:这一整条路径**几乎不碰核心数据库的写**,全靠缓存和专用引擎扛量。逛得越多,缓存价值越大。这是「读可凑合」的体现——数据旧几秒、推荐不那么准,都无所谓。 **场景二:下单(写路径,Saga 一步步推进、失败就回滚)** ``` 用户点「提交订单」──▶ 下单编排(Saga 协调者)启动: 步骤① 扣库存 ──▶ 库存服务:预扣成功? ├─ 失败(没货了)──▶ 直接结束,告诉用户「已售罄」 └─ 成功 ─▶ 继续 步骤② 创建订单 ──▶ 订单服务:生成订单,状态=「待支付」 └─ 失败 ─▶ 补偿:回补步骤①扣掉的库存 ──▶ 结束 步骤③ 调支付 ──▶ 支付服务:发起扣款(幂等,带唯一下单号) ├─ 成功 ─▶ 订单状态→「已支付」,预扣库存转为正式扣减 ──▶ 触发履约 └─ 失败/超时 ─▶ 补偿:订单→「已取消」,回补库存 ──▶ 结束 ★ 每一步都可独立重试;失败时按【相反顺序】补偿。全程没有一个横跨所有服务的大事务。 ``` > 架构要点:**用「可补偿的长流程(Saga)」替代「一个大分布式事务」。** 大事务会长时间锁住库存、订单、支付,在高并发下直接把系统拖垮;Saga 把它拆成「每步快进快出、错了再退回」,用**最终一致 + 对账**换来了吞吐。 **场景三:大促秒杀(削峰填谷 + 排队 + 限流)** ``` 百万人同时抢 1000 件 ──▶ 网关【限流】:大部分请求在门口就被挡掉/排号 │ 放进来的请求 ▼ 异步下单队列(削峰填谷)──▶ 把瞬时洪峰摊平成系统能消化的稳定速率 │ 逐个出队 ▼ 库存服务:对这件热点商品做【分段库存 / 排队扣减】,把单行争抢打散 │ 抢到的 ▼ 正常走 Saga 下单流程;没抢到的 ──▶ 立刻返回「已抢光」(快速失败,别让它干等) ``` > 架构要点:**洪峰不要硬扛,要「整形」。** 限流在门口拦、队列在中间摊平、分段库存在底层散热、降级在崩溃前丢车保帅——四件套合力,把「几十倍的尖峰」变成「系统能稳稳消化的水流」。 ## 7. 数据模型与存储选择 核心实体:`商品` ─ `库存(SKU 维度)`;`用户` ─ `购物车` ─ `订单` ─ `订单项`;`订单` ─ `支付单`;`促销/价格规则`。其中**订单**带状态机,**支付单**和**账务流水**是资金对账的依据。 | 数据 | 存储类型 | 为什么 | |---|---|---| | 订单 / 支付 / 账务流水 | 关系型(强事务) | 涉及钱,要强一致、要事务、要能逐笔对账 | | 库存(可扣减计数) | 支持原子扣减的强一致存储 | 防超卖靠原子操作;热点项再做分段 | | 商品目录(海量读) | 关系型为底 + 内存级 KV 缓存 | 读极多写极少,缓存挡住绝大部分读 | | 搜索索引 | 专用搜索系统(倒排索引) | 「按内容找」是通用数据库做不好的 | | 购物车 | 内存级 KV 缓存(可持久化) | 读写频繁、结构简单、对一致性要求不高 | | 推荐结果 | 预计算 + 缓存 | 可最终一致、可略旧,不该实时压数据库 | | 商品图片 | 对象存储 + CDN | 大、不变、按引用取,推到边缘加速 | | 大促削峰 | 异步消息队列 | 把瞬时洪峰摊平成可消化的稳定速率 | > 教学点(本节最重要):**一致性不是「全局开关」,而是「按数据分级的拨盘」。** 钱拨到「强一致 + 可对账」,库存拨到「防超卖」,而浏览、推荐、评价大可拨到「最终一致」。**用一种强度去要求所有数据,不是慢死就是出错——这正是电商架构的精髓。** ## 8. 关键架构决策与权衡 ⭐ **(本模板最值钱的一节。)** 电商的每个岔路口,几乎都在回答同一个问题:**「这块数据该用多强的一致性,以及怎么在洪峰下还不出错」。** **决策 1:库存怎么扣才不超卖?** - **悲观锁**(扣之前先锁住这行):绝对不超卖,但高并发下大家排队抢同一把锁,**热点商品直接锁成瓶颈**,吞吐崩塌。 - **乐观锁**(带版本号扣,冲突就重试):无锁、吞吐高,但热点商品冲突率极高,**大量请求反复重试**,同样拖慢。 - **预扣 + 异步确认**(先快速预占,支付成功再转正式扣减,超时未付就回补):响应快、能扛量,代价是要管理「预占超时回补」的复杂逻辑。 - **分段库存**(把 1000 件拆成 10 段各 100 件,请求被分散到不同段上扣):把「单行争抢」打散成「多行并行」,极大缓解热点。代价是分段间可能不均(有的段空了有的还剩),需要再平衡。 - **取向**:**普通商品用乐观锁/原子扣减足矣;热点/秒杀商品上「预扣 + 分段 + 排队」组合拳。** 核心思想是**把一个被疯抢的点,变成多个可并行的点**。代价是复杂度和「预占回补」的运维成本,但这是热点不超卖的唯一出路。 **决策 2:订单与支付的一致性——分布式事务,还是 Saga + 对账?** - **分布式事务(两阶段提交那一类)**:语义最干净,要么全成功要么全失败。但它会**长时间锁住多个服务的资源**,在高并发下吞吐极低、一个参与方卡住就全卡住,**电商扛不起**。 - **Saga + 最终一致 + 对账**:把下单拆成「扣库存→建单→支付」一串可补偿步骤,每步快进快出,失败按相反顺序回滚,再用对账系统兜底保证最终一致。 - **取向**:**几乎一定选 Saga + 对账。** 用「最终一致 + 一套靠谱的对账」换来了高吞吐。代价是中间会短暂出现「不一致窗口」(订单已建但还没支付),以及必须**额外建设对账系统**这条资金安全的最后防线——这笔投入省不得。 **决策 3:幂等设计——怎么防重复下单 / 重复支付?** - 不做幂等:网络一重试、用户手一抖连点两下,就**重复创建订单、重复扣款**,直接资损。 - 做幂等:每个下单/支付请求带一个**唯一标识(幂等键)**,服务端发现重复的同一个键,直接返回上次的结果而不再执行一遍。 - **取向**:**凡是涉及钱和库存的写操作,必须幂等,没有例外。** 因为重试在分布式系统里是常态(超时了不知道成没成,只能重发)。代价是要维护幂等键的存储和去重逻辑,但相比资损,这点成本微不足道。 **决策 4:读写分离——浏览的海量读和交易的关键写要不要分开?** - 不分:读写都压在同一套库上,**海量的浏览读会和关键的交易写抢资源**,互相拖累。 - 分:把读(目录、搜索、推荐)用缓存和只读副本扛起来,把写(库存、订单、支付)留给强一致的主存储。 - **取向**:**必分。** 读和写在电商里是「两种物种」——读求快求廉价、写求准求一致。**把读路径用缓存和专用引擎托管,让主存储专心伺候不能错的写。** 代价是读到的数据可能略有延迟(最终一致),但浏览场景完全可以接受。 **决策 5:大促洪峰怎么扛?** - 硬扛(无脑加机器):成本极高,且加机器有上限,数据库这种有状态部件没法无限扩,**到点还是会崩**。 - 整形(削峰填谷 + 限流 + 降级 + 缓存预热):用队列把尖峰摊平、用限流在门口拦、用降级在危急时丢掉非核心功能、提前把热点数据预热进缓存。 - **取向**:**洪峰要「整形」,不要「硬扛」。** 大促前**缓存预热**(把爆款数据提前灌进缓存)、入口**限流**(超过容量就排队或快速失败)、核心链路用**异步队列削峰**、危急时**降级**(暂时关掉推荐/评价等非核心功能,保住下单付款)。代价是体验有损(排队、功能临时缩水),但这是「保住核心交易」对「全崩」的明智取舍。 **决策 6:订单状态——用状态机管理,还是散落各处改字段?** - 散落改:哪儿方便就在哪儿改订单状态。简单,但**状态会乱跳**(比如从「已退款」又跳回「已发货」),难追溯、易出 bug。 - 状态机:明确定义「哪些状态、允许哪些流转」,任何非法跳转一律拒绝。 - **取向**:**订单必须用状态机。** 它是交易的事实凭证,每一次状态变化都要合法、可追溯。代价是前期要把状态和流转规则设计清楚,但这换来的是整个交易生命周期的可控与可审计。 ## 9. 规模化与瓶颈 电商的瓶颈很有规律:**先卡在「热点的写」,再卡在「洪峰的量」,最后卡在「读的成本」。** - **第一个瓶颈:热点商品的单行库存争抢。** 爆款的「最后那批货」被无数请求同时抢同一行,锁竞争把吞吐打到地板。 破解:① 分段库存,把一行拆成多段并行扣;② 排队化,让抢同一商品的请求排队顺序处理;③ 预扣 + 异步确认,缩短每个请求占用的时间;④ 热点识别 + 单独扩容。 - **第二个瓶颈:大促洪峰。** 瞬时流量几十倍,同步下单会把链路顶穿。 破解:① 异步下单(请求先入队,快速给用户「排队中/已受理」);② 队列削峰填谷,把尖峰摊平;③ 入口限流,超容量就快速失败别干等;④ 降级,危急时砍掉非核心功能保核心;⑤ 缓存预热,别让冷缓存在峰值回源压垮数据库。 - **第三个瓶颈:目录与搜索的海量读。** 浏览流量最大,直接压数据库必崩。 破解:① 多级缓存(边缘/CDN + 内存级 KV),把绝大多数读挡在数据库前;② 搜索交给专用搜索系统,别用通用数据库硬搜;③ 读写分离,只读副本扛读。 - **第四个瓶颈:一致性窗口带来的对账压力。** 用了 Saga/最终一致后,会持续产生少量「订单、支付、账务对不上」的情况。 破解:建设**对账系统**,逐笔核对、自动修复能修的、报警人工处理修不了的——这是规模化后资金安全的常态化基础设施。 ## 10. 安全与合规要点 - 🔴 **支付安全是头等大事。** 资金链路要强一致、可审计、逐笔可对账;敏感支付信息按合规要求处理,核心扣款判断绝不放在客户端。 - **幂等即安全**:重复请求不能造成重复扣款/重复发货——前面说的幂等键,既是正确性手段,也是防资损的安全手段。 - **价格篡改防护**:成交价**必须由服务端重新计算**,绝不能信任客户端传来的价格;否则会被人改包以一分钱下单。 - **防刷 / 防黄牛**:大促秒杀会招来脚本和黄牛,需要风控(限购、人机校验、行为识别)在入口拦截异常流量,既保公平也保护库存和系统。 - **服务端不信任客户端**:库存、价格、优惠资格、下单权限,全部以服务端判定为准。客户端传来的一切都可能被篡改。 - **超卖即事故**:库存的原子性和防超卖,既是正确性问题,也是「卖了发不出货」的合规与信誉风险。 ## 11. 常见误区 / 反模式 - ❌ **用一个大事务把『扣库存→建单→支付→发货』整串串起来** → ✅ 拆成可补偿的 Saga 长流程,每步快进快出,失败按相反顺序回滚。 - ❌ **下单时同步调支付,卡着等支付方回话** → ✅ 支付异步化、幂等化,别让一次外部调用把整条链路堵死。 - ❌ **不做幂等,网络重试/用户连点就重复下单、重复扣款** → ✅ 涉及钱货的写操作一律带幂等键去重。 - ❌ **库存直接 `UPDATE 库存 = 库存 - 1` 硬扣** → ✅ 用原子扣减 + 防超卖;热点商品上分段/排队,别让单行变成锁竞争的瓶颈。 - ❌ **用同一种强一致性要求所有数据(连推荐、评价都要求实时一致)** → ✅ 按数据分级:钱强一致、库存防超卖、浏览/推荐最终一致。 - ❌ **大促靠无脑加机器硬扛** → ✅ 削峰填谷 + 限流 + 降级 + 缓存预热,把洪峰「整形」而不是「硬接」。 - ❌ **信任客户端传来的价格/库存/优惠** → ✅ 一切金额和资格由服务端重新核算。 - ❌ **用了最终一致却不建对账** → ✅ 最终一致必须配对账兜底,否则「最终」可能永远到不了。 ## 12. 演进路线:MVP → 成长期 → 成熟期 架构是会长大的。**别拿成熟期的图去套 MVP。** | 阶段 | 用户/规模量级 | 架构长什么样 | 此时该操心什么 | |---|---|---|---| | **MVP** | 验证想法 | **单体商城**:商品、购物车、订单、支付都在一个应用里,一个数据库,下单可能就是一个事务搞定 | 先验证「这门生意跑不跑得通」,别一上来就拆微服务、上 Saga | | **成长期** | 万~百万订单 | **拆出核心域**(库存 / 订单 / 支付独立),引入缓存挡读、专用搜索系统、读写分离;下单改用 Saga + 幂等 | 找瓶颈、保住交易正确性,把读路径用缓存撑起来 | | **成熟期** | 千万级以上 / 有大促 | **大促专项**:分段库存、异步下单 + 削峰队列、限流降级、缓存预热;建设对账系统;关键链路多活容灾 | 库存防超卖、洪峰削峰、资金对账、容灾、跨团队协同 | ## 13. 可复用要点 - 💡 **一致性是「按数据分级的拨盘」,不是「全局开关」。** 先问每块数据「错了/旧了会怎样」,再决定给它多强的一致性。这条思想适用于任何「读写诉求差异巨大」的系统。 - 💡 **别用一个大事务串长流程,用「可补偿的长流程 + 对账」。** 跨多个服务、又扛高并发时,最终一致 + 兜底校验,几乎总是比分布式大事务更现实。 - 💡 **把『被疯抢的一个点』变成『可并行的多个点』。** 分段库存的思想,可推广到任何热点争抢场景——分片、分桶、排队,本质都是「给热点散热」。 - 💡 **洪峰要『整形』,不要『硬扛』。** 限流、排队、削峰、降级是一套通用的「流量整形」工具,任何会遇到尖峰的系统都用得上。 - 💡 **凡是会被重试的写操作,都要幂等。** 在分布式世界里重试是常态,幂等是保证「重试不闯祸」的通用纪律——尤其是任何碰钱的地方。 ## 🎯 随堂检验 --- ## 参考原型与延伸阅读 > 本模板基于以下**权威模式库**与**官方工程文档**整理。 **📖 模式 / 工程文档:** - [Pattern: Saga (microservices.io)](https://microservices.io/patterns/data/saga.html) — 用本地事务序列 + 补偿事务维持跨服务数据一致性。 - [Stripe: Designing robust and predictable APIs with idempotency](https://stripe.com/blog/idempotency) — 幂等键实现下单 / 支付防重复扣款。 - [Stripe: Idempotent requests (API 文档)](https://docs.stripe.com/api/idempotent_requests) — 幂等键的具体语义,对应订单 / 库存防重复操作。 --- > 📌 一句话记住电商平台:**它不是「商品很多的网站」,而是「一台钱货不能错、还要扛得住洪峰的交易机器」——所有架构取舍,最终都在回答『这块数据该多较真,以及大促那天它崩不崩』。**