简介:这份PDF是京东商城超大型电商系统架构设计方案资料,面向电商技术架构师、后端开发与系统设计人员,重点阐述如何构建支持自营、商城与三方平台融合的高并发交易系统,可帮助读者理解电商平台在业务、应用、数据、技术及运维层面的架构设计思路。包体为1个PDF文件,大小2.51MB,内容结构完整,包含架构目标、业务架构设计原则、应用架构分层、服务依赖原则、数据架构设计原则、技术架构总览、系统运维原则等章节,适合用于系统学习与项目方案参考。已有186人学习浏览。资料提炼了京东电商系统的核心设计经验,例如业务平台化、主辅流程分离、核心与非核心业务隔离、异步解耦、容错与多机房多活等原则,并给出具体实例与分层模型,可帮助读者规避常见架构陷阱,提升系统可用性、扩展性与研发效率,对实际方案设计有直接借鉴意义。
1. 从99.99%可用性反推:超大型电商系统的架构目标拆解
一套电商架构方案好不好,先别急着看技术栈,看它敢不敢把可用性指标写进设计目标。京东这份方案里最硬的输入是「整体系统可用性 99.99%、单个系统 99.999%、全年故障不超过 50 分钟、单系统故障不超过 5 分钟」,这几个数字直接把架构决策逼到了墙角:业务不平台化,低成本和复用无从谈起;主流程和辅流程不分离,一次异步任务失败就可能回滚整个订单;服务不做到无状态和幂等,在线扩容就是一句空话。这份方案适合正在规划交易中台、或者准备把单体电商系统改造成分布式架构设计的技术负责人,对照自己的系统逐条检查依赖方向、数据分片和监控覆盖,比重新发明一套方法论要快得多。
2. 业务平台化与主流程隔离:京东式架构的拆分边界设计
2.1 平台化的边界:什么该下沉,什么该独立
原文对业务平台化的定义是「交易平台、仓储平台、物流平台、支付平台、广告平台相互独立,用户、商品、类目、促销、时效等基础业务下沉复用」。这里最容易做错的是把平台化理解成把所有能力塞进一个公共服务层,结果公共层越做越重,改一次促销规则要连带交易和仓储一起回归。
我判断边界时会看两个条件:第一,这个能力是否被三个以上业务平台依赖,如果是,下沉到基础层,并对外发布稳定的服务契约;第二,这个能力是否有独立的业务状态,比如交易有订单状态机、物流有运单状态机,有独立状态的就单独成平台。基础层只提供用户、商品、类目这类低状态的数据服务,业务平台在基础层之上各自演进。京东这套系统融合了自营、商城、三方平台三种模式,跟纯商城模式的电商比,业务链路更长,所以更强调「基础业务下沉、业务平台隔离」这种分层逻辑。
2.1.1 基础服务契约要稳定到什么程度
// 商品基础服务契约:只暴露读语义,不暴露内部实现 public interface ItemReadService { // 按场景取商品快照,场景不影响返回结构 ItemSnapshot getSnapshot(Long skuId, String scene); // 批量读取,减少跨服务RPC次数 Map<Long, ItemSnapshot> batchGet(List<Long> skuIds); }上面这段接口设计对应了「服务只依赖于服务抽象,不依赖服务实现细节」的原则。scene可以传trade、marketing这样的场景标识,服务内部据此做缓存分组或字段裁剪,但返回的ItemSnapshot结构对所有调用方一致。这样当商品服务把底层存储从单库切到分库时,交易和营销两个调用方都不需要感知。batchGet必须保留,商品详情、购物车、结算页在一条链路里会反复查询商品信息,单条 RPC 在高并发下的放大效应非常明显,批量接口是基础服务的基本功。
2.2 主流程与辅流程:失败的连带影响是判断依据
原文给出的案例是「下单时同步调用快照,异步通知台账、发票」。实际操作里,很多团队把「快照」也做成异步,理由是快照服务压力大;结果是订单创建成功后,详情页在高峰期查不到下单时的商品信息,客诉率立刻上升。判断主流程和辅流程,标准只有一个:这个步骤失败,是否会导致订单创建失败。
下单链路里,商品快照、价格计算、库存锁定、订单落库这四步是主流程,必须同步执行,且每一步都要可重试、可补偿。台账、发票、消息通知、风控异步任务属于辅流程,失败不能回滚主流程,只能记日志、进重试队列。下面这张表是我在做代码评审时会打印出来逐行对的:
| 环节 | 同步/异步 | 失败处理 | 缓存策略 |
|---|---|---|---|
| 商品快照 | 同步 | 重试3次后降级为读主库 | 本地缓存+Redis |
| 价格计算 | 同步 | 重试,失败则取消订单 | 促销规则本地缓存 |
| 库存锁定 | 同步 | 失败直接返回 | Redis预扣+DB对账 |
| 台账/发票 | 异步 | 写MQ,消费失败进死信队列 | MQ持久化 |
关键在「异步」要落成两套不同的东西:一个是同步调用但设超时和降级,比如快照的超时时间一般压在 200ms 以内,超过就切备用数据源;另一个是真正异步,比如台账通知直接投递到消息队列,根本不占下单线程。两者混用的时候,一定要在接口命名上区分开,比如SyncSnapshot和NotifyLedgerAsync,避免后续维护的人把异步接口误放到主流程里。
2.3 业务隔离:闪购、秒杀与常规下单的物理边界
原文提到「闪购业务对高并发要求很高,应该跟普通业务隔离」。这里的隔离不是逻辑隔离,而是物理隔离:独立的应用集群、独立的数据库实例、独立的缓存命名空间。因为一旦闪购流量把数据库连接池打满,常规下单的请求会被连带阻塞,等于闪购的故障把整个交易链路拖垮。
我一般会在网关层按 URL 前缀或参数把flash-sale路由到独立集群,常规下单走另一个集群。两个集群共用一套商品基础服务,但交易订单库是分开的。这里要特别注意分片键设计,闪购订单和常规订单如果共用订单号生成器,分库路由时很容易错乱。
// 订单分片路由:按买家ID取模,闪购与常规共用同一规则 public class OrderShardingRule { public static String route(String buyerId) { int shard = safeHash(buyerId) % 16; return "order_db_" + shard; } }这段路由规则的参数16是分片数,选多少取决于单库能承载的订单量和未来三年的增长预期。取模路由的好处是实现简单、扩容路径清晰,坏处是数据分布依赖 buyerId 的哈希质量。实际落地时,分片数建议设成 2 的幂次,比如 16、32、64,后续扩容可以用一致性哈希迁移,不用全量重算。闪购和常规共用一套路由规则,但数据库实例不同,所以同一个买家在闪购库和常规库各有一份订单分片,查询时按订单号类型路由到对应库,压测时两条链路互不干扰。
注意:分片数一旦定下来不要频繁改动,改动意味着全量数据迁移。前期宁可少分片、多观察,也要避免过度设计。
3. 服务依赖与数据分片:弱依赖、读写分离的落地参数
3.1 依赖规则怎么落到代码评审
原文的依赖原则看起来有六条,核心是一句话:稳定的一方永远不能反向依赖易变的一方。代码评审里我习惯把每对服务之间的依赖画成一张方向表,逐条过:
| 依赖对 | 允许方向 | 同步/异步 | 违反时的典型现象 |
|---|---|---|---|
| 核心服务 → 非核心服务 | 禁止 | - | 下单时同步调积分发放,积分抖动拖垮交易 |
| 非核心服务 → 核心服务 | 允许 | 异步优先 | 订单创建后异步通知积分、发票系统 |
| 平台服务 → 上层应用 | 禁止 | - | 商品基础服务反向依赖营销活动的优惠规则 |
| 上层应用 → 平台服务 | 允许 | 同步 | 交易应用调用商品快照接口 |
这张表的价值在于把「稳定」「易变」这种模糊词变成了可检查的依赖方向。实际执行时,我一般会把规则写进 RPC 框架的调用白名单,从框架层面禁止核心服务反向调用非核心服务接口,光靠人工评审在线上故障面前靠不住。另外,跨域调用要尽量异步化,必须同步时,接口上要显式声明超时时间和队列大小上限,防止依赖方被慢调用拖死。
3.2 数据架构:统一视图与数据异构落库
原文的数据架构原则里,统一数据视图不是让所有应用共享一张表,而是保证同一份业务数据在不同应用视角下一致:买家看到的订单状态和卖家看到的订单状态,必须来自同一个事实源。基于这一点,京东对订单做买家库和卖家库的数据异构,内容相同但索引维度不同。
数据异构的常见做法是:主订单库按订单ID组织,买家订单库按买家ID建索引,卖家订单库按卖家ID建索引,三份数据通过异步消息队列同步。这里要接受秒级延迟;对一致性要求极高的场景,比如支付回调改订单状态,必须走同步接口回写主订单库,不能依赖异步异构链路,否则对账时会产生差异。
商品库的索引异构也同理,同一份商品数据按类目、品牌、价格带分别组织成多套索引,服务层按查询入口选用不同索引。数据库选型上,方案里坚持用 MySQL 这类主流数据库,除了授权成本,更关键的是分库分表和主从复制的生态成熟,社区里踩过的坑都能找到答案,这对中小团队太重要了。
3.2.1 读写分离与分片的配置骨架
# 常见的数据访问层配置骨架(以ShardingSphere为例) rules: - !SHARDING tables: t_order: actualDataNodes: ds_$->{0..3}.t_order_$->{0..15} tableStrategy: standard: shardingColumn: buyer_id shardingAlgorithmName: buyer_id_hash_mod keyGenerateStrategy: column: order_id keyGeneratorName: snowflake - !READWRITE_SPLITTING dataSources: ds_0: writeDataSourceName: ds_0_master readDataSourceNames: - ds_0_slave_1 - ds_0_slave_2 loadBalancerName: round_robin上面是分布式架构设计里常见的数据访问层配置骨架,几个参数值得说明。actualDataNodes表示 4 个物理库、每库 16 张表,总共 64 个分片,这个量级对日订单量千万级的中型电商是够用的。shardingColumn选buyer_id而不是order_id,是因为买家维度的查询占总流量八成以上,按买家路由可以把同一个买家的订单落在同一分片,避免跨库聚合。snowflake主键生成是为了保证全局唯一且带时间序,方便按时间归档和冷热分离。读写分离这里,round_robin在连接数差异大时容易压到同一台从库,建议改成random或者带权重的负载均衡策略。
3.3 缓存引入的时机与容灾路径
原文这句话「数据库有能力支撑时,尽量不要引入缓存」,很多团队做不到。原因很简单:缓存在压测时收益明显,但一致性的成本是上线后才暴露的。我按三个条件判断:读流量是否超过数据库 QPS 阈值的 60%、数据是否允许秒级延迟、团队是否有人愿意维护缓存和数据库的一致性任务。三个条件全满足才引入,否则用只读从库硬扛。
缓存容灾是另一个容易忽略的点。即使加了 Redis,也要设计降级路径:缓存挂了自动切回数据库,而不是直接报错。这里要防的是缓存雪崩打垮数据库,所以降级时要限流回源,用一个简单的信号量控制并发回源数量,而不是让所有请求在缓存失效的瞬间同时打到数据库上。这一步对应了原文「合理利用缓存做容灾」的表述。
4. 技术组件与监控运维:把SLA拆成告警和切换动作
4.1 集成层组件职责:每件中间件只解决一个问题
原文技术架构总览里列了一批组件:缓存服务 JFS/Jimstore、图片服务 JSS、消息队列 JDMQ、数据库中间件 JDAL、调度服务 JDWorker、业务规则服务 JDRules、配置中心 JDCenter、推送服务 JMP,质量层还有监控 UMP、日志 Loghub、风控 JDriskM。这些组件单个看不稀奇,组合方式才是关键:JDAL 负责分库分表和读写分离路由,JDMQ 负责主流程与辅流程解耦,JDWorker 负责异步任务的调度和重试,PAF 负责服务流程编排,SAF 负责服务通信治理。
| 组件 | 职责 | 常见替代方案 | 对应架构原则 |
|---|---|---|---|
| JDMQ | 跨域异步解耦 | 主流消息队列中间件 | 松耦合、辅流程异步化 |
| JDAL | 分库分表与读写分离路由 | ShardingSphere | 数据与应用分离 |
| PAF | 服务流程编排 | 工作流引擎或BPM | 主流程可编排可回滚 |
| JDWorker | 异步任务调度与重试 | 分布式任务调度平台 | 辅流程可靠执行 |
| UMP | 服务监控与告警 | 监控系统组合 | 可监控、可治理 |
选型逻辑的重点不是组件本身,而是「每个组件只解决一个问题」。JDAL 只管数据库路由,不做业务逻辑;JDMQ 只管消息投递和可靠性,不碰消息格式定义。分布式架构设计里最常见的失控,是中间件越用越多但职责边界越来越模糊,一个消息队列同时承担异步解耦、最终一致性、事件驱动三件事,出了问题很难定位。我在评审中间件引入时只问一个问题:这个组件解决了哪个架构原则对应的问题,如果答不上来,就不该引入。
4.2 质量层监控:从TPS到RT的告警规则设计
原文运维原则第一条是「服务的 TPS 和 RT 是否符合 SLA,是否出现超预期流量」。落地到监控系统里,我把所有服务的告警分成四个维度:TPS 突增或突降、RT 的 P99 与 P999、错误率、线程池活跃度。最容易漏的是 TPS 突降,它往往意味着服务假死或者上游熔断,比 RT 升高更危险。
# 监控告警规则示意(PromQL风格,按实际环境调整) groups: - name: ecommerce-sla rules: - alert: ServiceRTHigh expr: histogram_quantile(0.99, rate(rpc_rt_seconds_bucket{job="order-service"}[5m])) > 1.5 for: 3m labels: severity: page annotations: summary: "订单服务P99超过1.5秒" - alert: TpsDrop expr: rate(rpc_requests_total{job="order-service"}[5m]) < 0.3 * avg_over_time(rate(rpc_requests_total{job="order-service"}[1d])) for: 5m labels: severity: page这段规则里两个表达式是关键。histogram_quantile(0.99, ...)算的是 P99 延迟,阈值 1.5 秒对下单接口来说已经偏慢,正常应该在 300ms 以内,这里故意放开是因为不同集群基线不同,告警阈值要按七天窗口的动态基线来定,不能写死。TpsDrop用当前 5 分钟速率对比前一天同一时段的均值,低于 30% 就报警,能抓到 RT 还没升高但流量已经断崖的场景。for: 3m是连续持续时间,避免抖动触发误报;我一般从 5 分钟起步,运行稳定后再缩短,告警灵敏度要跟团队的响应能力匹配。
4.3 在线扩容与故障转移:演练比预案重要
原文运维原则里的「在线扩容」和「多机房故障转移」是最难落地的两条。很多团队预案写得很好,但没演练过,真出故障时才发现扩容脚本连不上新机器、流量切换后缓存仍指向旧机房。
我的做法是每季度做一次半小时的故障注入演练:随机挑一个核心应用,杀掉一个机房的所有实例,观察流量调度是否在 60 秒内切到备用机房,同时监控订单创建成功率,要求不低于 99.9%,否则演练失败。在线扩容的演练更简单,直接对一台没有流量的新机器执行扩容脚本,验证配置中心推送、服务注册发现、缓存预热三个环节的耗时。这三个环节任一超过 5 分钟,就意味着大促流量真的进来时扩容速度跟不上,需要提前扩容或者优化镜像启动流程。组合服务的多个实例必须保持无状态,状态都丢到缓存和数据库里,扩容才不会有会话一致性问题。
5. 中小团队落地:先把主链路压测和降级开关做扎实
5.1 用一次压测识别主流程的真正依赖
中小团队直接复刻京东这套技术架构不现实,但「主流程与辅流程分离」这件事是可以立刻落地的。具体做法是:把下单接口在压测环境跑一次全链路压测,逐步加大并发,观察每个下游服务的耗时和错误率。
# 压测时观察下单链路各环节指标 wrk -t 8 -c 200 -d 60s http://gateway/order/create # 同时监控: # 1. item-service P99 # 2. price-service P99 # 3. order-db 活跃连接数 # 4. MQ 生产速率与消费积压压测有两个常见结果:一是某个下游服务先到达瓶颈,这个服务就是主流程里的最弱环节,优先给它配置降级策略;二是 MQ 消费积压持续增长,说明异步环节的生产速度大于消费速度,需要检查消费线程池配置而不是盲目加机器。压测报告里最值得关注的指标是「第一个到达瓶颈的服务是哪个」,它决定了降级开关的优先级。对外承诺的 SLA 要按最弱环节来定,而不是按平均耗时来定。
5.2 降级开关至少要分三层
降级开关不能只是代码里的一个 if 分支,至少要三层:配置中心开关、本地缓存开关、兜底逻辑。配置中心开关用于全局切换,本地缓存开关防止配置中心故障时决策抖动,兜底逻辑是数据源切换或者直接返回默认值。
// 降级开关三段式:全局开关 -> 本地开关 -> 兜底逻辑 public class SnapshotDegrade { private static volatile boolean localDegrade = false; public ItemSnapshot getSnapshot(Long skuId) { // 第一段:配置中心下发的全局开关 if (configCenter.isDegrade("item.snapshot")) { return cacheOnly(skuId); // 第二段:本地缓存兜底 } // 第三段:正常调用,超时自动降级 return rpcCallWithTimeout(skuId, 150); } }这段代码的要点在rpcCallWithTimeout上:150ms 超时后不直接抛异常,而是返回一个由本地最近一次成功快照拼出的兜底对象。兜底返回的快照可能不是最新的,但商品快照本身是下单那一刻的历史数据,只要价格和名称不变,延迟几秒可接受。第 5 行的本地开关由监控系统在连续 10 次调用超时后自动置位,避免配置中心尚未下发时的抖动。把降级开关的触发次数和恢复时间也接进监控指标,压测时观察 P99 拐点、开关触发次数、MQ 积压量三条曲线,基本就能判断主流程隔离做得是否到位。降级开关必须保留手动恢复按钮,自动恢复常常发生在流量高峰还没过去的时候,恢复后马上又被冲垮,来回抖动比一直降级更伤系统。
本文还有配套的精品资源,点击获取