互联网业务系统 - 电商系统设计

电商系统整体架构设计

业务流 (business process)


E-commerce process

系统流 (system process)


E-commerce whole process of system

产品架构 (Product Structure/组织架构)


E-commerce product structure

应用架构

技术架构

数据架构

商品管理 Product Center

商品信息包括哪些内容

商品系统的演进

阶段 主要特征/能力 技术架构/数据模型 适用场景/目标 实现方式简单说明
初始阶段 - 商品信息简单,字段少
- SKU/SPU未严格区分
- 价格库存直接在商品表
- 仅支持基本的增删改查
单表/简单表结构 小型电商、业务初期,SKU数量少 单体应用,单表存储,简单业务逻辑,直接数据库操作
成长阶段 - 引入SPU/SKU模型
- 属性、类目、品牌等实体独立
- 支持多规格商品
- 价格库存可拆分为独立表
关系型数据库,ER模型优化 SKU多样化,品类扩展,业务快速增长 关系型数据库,ER模型优化,多表存储,业务逻辑复杂
成熟阶段 - 商品中台化,支持多业务线/多渠道
- 属性体系灵活可扩展
- 多级类目、标签、图片、描述等丰富
- 商品快照、操作日志、版本控制
中台架构,微服务/多表/NoSQL 大型平台,业务复杂,需支撑多业务场景 分布式服务,插件化/配置化流程,状态机驱动,异步消息,灵活数据模型
未来演进 - 多语言多币种支持
- 商品内容多媒体化(视频、3D等)
- AI智能标签/推荐
- 商品数据实时分析与洞察
分布式/云原生/大数据平台 国际化、智能化、数据驱动的电商生态 云原生架构,AI/大数据分析,自动化运维,弹性伸缩,智能路由与风控

什么是SPU、SKU

方案一:同时创建多个SKU,并同步生成关联的SPU。整体方案是直接创建SKU,并维护多个不同的属性;该方案适用于大多数C2C综合电商平台(例如,阿里巴巴就是采用这种方式创建商品)。
方案二:先创建SPU,再根据SPU创建SKU。整体方案是由平台的主数据团队负责维护SPU,商家(包括自营和POP)根据SPU维护SKU。在创建SKU时,首先选择SPU(SPU中的基本属性由数据团队维护),然后基于SPU维护销售属性和物流属性,最后生成SKU;该方案适用于高度专业化的垂直B2B行业,如汽车、医药等。
这两种方案的原因是:垂直B2B平台上的业务(传统行业、年长的商家)操作能力有限,维护产品属性的错误率远高于C2C平台,同时平台对产品结构控制的要求较高。为了避免同一产品被不同商家维护成多个不同的属性(例如,汽车轮胎的胎面宽度、尺寸等属性),平台通常选择专门的数据团队来维护产品的基本属性,即维护SPU。
此外,B2B垂直电商的品类较少,SKU数量相对较小,品类标准化程度高,平台统一维护的可行性较高。
对于拥有成千上万品类的综合电商平台,依靠平台数据团队的统一维护是不现实的,或者像服装这样非标准化的品类对商品结构化管理的要求较低。因此,综合平台(阿里巴巴和亚马逊)的设计方向与垂直平台有所不同。
实际上,即使对于综合平台,不同的品类也会有不同的设计方法。一些品类具有垂直深度,因此也采用平台维护SPU和商家创建SKU的方式

数据库模型

  • 类目category
  • 品牌brand
  • 属性attribute
  • 标签tag
  • 商品主表/spu表/item表、item_stat 统计表、item属性值表
  • 商品变体表/variant表/sku表、sku attribute表
  • 其它实体表、其它实体和商品表的关联表


E-commerce product center

模型说明:

  • 商品(item/SPU)与商品变体(sku)分离,便于管理不同规格、价格、库存的商品。
  • 属性(attribute)、类目(category)、品牌(brand)等实体独立,便于扩展和维护
  • 商品分类体系如何设计?采用多级分类?分类的动态扩展只需插入新分类,指定其 parent_id,即可动态扩展任意层级
  • 灵活的属性体系。通过 category_attribute 和 spu_attr_value 支持不同类目下的不同属性,适应多样化商品需求。属性值与商品解耦,支持动态扩展
  • item_stat 单独存储统计信息,便于高并发下的读写优化。
  • 可以方便地增加标签(tag)、图片、描述、规格等字段,适应业务变化

商品信息录入JSON示例

实体商品
1、实体商品男士T恤 JSON 数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
{
"categoryId": 1003001,
"spu": {
"name": "经典圆领男士T恤",
"brandId": 2001,
"description": "柔软舒适,100%纯棉"
},
"basicAttributes": [
{
"attributeId": 101, // 品牌
"attributeName": "品牌",
"value": "NIKE"
},
{
"attributeId": 102, // 材质
"attributeName": "材质",
"value": "棉"
},
{
"attributeId": 103, // 产地
"attributeName": "产地",
"value": "中国"
},
{
"attributeId": 104, // 袖型
"attributeName": "袖型",
"value": "短袖"
}
],
"skus": [
{
"skuName": "黑色 L",
"price": 79.00,
"stock": 100,
"salesAttributes": [
{
"attributeId": 201,
"attributeName": "颜色",
"value": "黑色"
},
{
"attributeId": 202,
"attributeName": "尺码",
"value": "L"
}
]
},
{
"skuName": "白色 M",
"price": 79.00,
"stock": 150,
"salesAttributes": [
{
"attributeId": 201,
"attributeName": "颜色",
"value": "白色"
},
{
"attributeId": 202,
"attributeName": "尺码",
"value": "M"
}
]
}
]
}
虚拟商品
2、虚拟商品流量充值 JSON 数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
{
"categoryId": 1005002,
"spu": {
"name": "中国移动流量包充值",
"brandId": 3001,
"description": "全国通用流量包充值,按需选择,自动到账"
},
"basicAttributes": [
{
"attributeId": 301,
"attributeName": "运营商",
"value": "中国移动"
},
{
"attributeId": 302,
"attributeName": "适用网络",
"value": "4G/5G"
}
],
"skus": [
{
"skuName": "中国移动1GB全国流量包(7天)",
"price": 5.00,
"stock": 9999,
"salesAttributes": [
{
"attributeId": 401,
"attributeName": "流量容量",
"value": "1GB"
},
{
"attributeId": 402,
"attributeName": "有效期",
"value": "7天"
},
{
"attributeId": 403,
"attributeName": "流量类型",
"value": "全国通用"
}
]
},
{
"skuName": "中国移动5GB全国流量包(30天)",
"price": 20.00,
"stock": 9999,
"salesAttributes": [
{
"attributeId": 401,
"attributeName": "流量容量",
"value": "5GB"
},
{
"attributeId": 402,
"attributeName": "有效期",
"value": "30天"
},
{
"attributeId": 403,
"attributeName": "流量类型",
"value": "全国通用"
}
]
},
{
"skuName": "中国移动10GB全国流量包(90天)",
"price": 38.00,
"stock": 9999,
"salesAttributes": [
{
"attributeId": 401,
"attributeName": "流量容量",
"value": "10GB"
},
{
"attributeId": 402,
"attributeName": "有效期",
"value": "90天"
},
{
"attributeId": 403,
"attributeName": "流量类型",
"value": "全国通用"
}
]
}
]
}

商品的价格和库存

方案1. 价格和库存直接放在sku表中 (变化小)

在这种方案中,SKU(Stock Keeping Unit) 表包含商品的所有信息,包括价格和库存数量。每个 SKU 记录一个独立的商品实例,它有唯一的标识符,直接关联价格和库存。

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE sku_tab (
sku_id INT PRIMARY KEY, -- SKU ID
product_id INT, -- 商品ID (外键,指向商品表)
sku_name VARCHAR(255), -- SKU 名称
original_price DECIMAL(10, 2), -- 原始价格
price DECIMAL(10, 2), -- 销售价格
discount_price DECIMAL(10, 2), -- 折扣价格(如果有)
stock_quantity INT, -- 库存数量
warehouse_id INT, -- 仓库ID(如果有多个仓库)
created_at TIMESTAMP, -- 创建时间
updated_at TIMESTAMP -- 更新时间
);

优点:

  • 简单:所有信息都集中在一个表中,查询和管理都很方便。
  • 查询效率:查询某个商品的价格和库存不需要多表联接,减少了数据库查询的复杂度。
  • 维护方便:商品的所有信息(包括价格和库存)都在一个地方,减少了冗余数据和数据不一致的可能性。

缺点:

  • 灵活性差:如果价格和库存的管理策略较复杂(如促销、库存管理、动态定价等),这种方式可能不太适用。修改价格或库存时需要直接更新 SKU 表。
  • 扩展性差:对于一些复杂的定价和库存管理需求(如多层次的定价结构、分仓库管理等),直接放在 SKU 表中可能不够灵活。

适用场景:

  • 商品种类较少,SKU 数量相对固定且不复杂的场景。
  • 价格和库存变动较少,不涉及复杂的促销或动态定价的场景
方案2. 价格和库存单独管理(变化大)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

CREATE TABLE price_tab (
price_id INT PRIMARY KEY, -- 价格ID
sku_id INT, -- SKU ID (外键)
price DECIMAL(10, 2), -- 商品价格
discount_price DECIMAL(10, 2), -- 折扣价格
effective_date TIMESTAMP, -- 价格生效时间
expiry_date TIMESTAMP, -- 价格失效时间
price_type VARCHAR(50), -- 价格类型(如标准价、促销价等)
FOREIGN KEY (sku_id) REFERENCES ProductSKUs(sku_id)
);

CREATE TABLE inventory_tab (
inventory_id INT PRIMARY KEY, -- 库存ID
sku_id INT, -- SKU ID (外键)
quantity INT, -- 库存数量
warehouse_id INT, -- 仓库ID(如果有多个仓库)
updated_at TIMESTAMP, -- 库存更新时间
FOREIGN KEY (sku_id) REFERENCES ProductSKUs(sku_id)
);

优点:

  • 灵活性高:价格和库存信息可以独立管理,更容易支持多样化的定价策略、促销活动、库存管理等。
  • 可扩展性强:对于需要频繁更新价格、库存、促销等信息的商品,这种方案更容易扩展和适应变化。例如,可以灵活地增加新的价格策略或库存仓库。
  • 数据结构清晰:避免了价格和库存在 SKU 表中的冗余存储,使得数据结构更清晰。

缺点:

  • 查询复杂:获取某个商品的价格和库存信息时,需要联接多个表,查询效率可能会降低,尤其是在数据量大时。
  • 管理复杂:需要更多的表和关系,增加了维护成本和系统复杂度。

适用场景:

  • 商品种类繁多,SKU 数量较大,且需要支持动态定价、促销、库存管理等复杂需求的场景。
  • 需要频繁变动价格或库存的商品,且这些信息与 SKU 无法紧密绑定的场景

商品快照 item_snapshots

  1. 商品编辑时生成快照:
  • 每次商品信息(如价格、描述、属性等)发生编辑时,生成一个新的商品快照。
  • 将快照信息存储在 item_snapshots 表中,并生成一个唯一的 snapshot_id。
  1. 订单创建时使用快照:
    在用户下单时,查找当前商品的最新 snapshot_id。
    在 order_items 表中记录该 snapshot_id,以确保订单项反映下单时的商品状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    CREATE TABLE `snapshot_tab` (
    `snapshot_id` int(11) NOT NULL AUTO_INCREMENT,
    `snapshot_type` int(11) NOT NULL,
    `create_time` int(11) NOT NULL DEFAULT '0',
    `data` text NOT NULL,
    `entity_id` int(11) DEFAULT NULL,
    PRIMARY KEY (`snapshot_id`),
    KEY `idx_entity_id` (`entity_id`)
    )

用户操作日志

1
2
3
4
5
6
7
8
9
10
CREATE TABLE user_operation_logs (
log_id INT PRIMARY KEY AUTO_INCREMENT, -- Unique identifier for each log entry
user_id INT NOT NULL, -- ID of the user who made the edit
entity_id INT NOT NULL, -- ID of the entity being edited
entity_type VARCHAR(50) NOT NULL, -- Type of entity (e.g., SPU, SKU, Price, Stock)
operation_type VARCHAR(50) NOT NULL, -- Type of operation (e.g., CREATE, UPDATE, DELETE)
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Time of the operation
details TEXT, -- Additional details about the operation
FOREIGN KEY (user_id) REFERENCES users(id) -- Assuming a users table exists
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

商品的统计信息

缓存的使用

核心流程

B端:商品创建和发布的流程

  • 批量上传、批量编辑
  • 单个上传、编辑
  • 审核、发布
  • OpenAPI,支持外部同步API push 商品
  • auto-sync,自动同步外部商品

C端:商品搜索、商品详情

  • 商品搜索
    • elastic search 索引构建。获取商品列表(首页索引)
    • 如何处理商品的SEO优化?
      1、item index
      
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      POST /products/_doc/1
      {
      "product_id": "123456",
      "name": "Wireless Bluetooth Headphones",
      "description": "High-quality wireless headphones with noise-cancellation.",
      "price": 99.99,
      "stock": 50,
      "category": "Electronics",
      "brand": "SoundMax",
      "sku": "SM-123",
      "spu": "SPU-456",
      "image_urls": [
      "http://example.com/images/headphones1.jpg",
      "http://example.com/images/headphones2.jpg"
      ],
      "ratings": 4.5,
      "seller_info": {
      "seller_id": "78910",
      "seller_name": "BestSeller"
      },
      "attributes": {
      "color": "Black",
      "size": "Standard",
      "material": "Plastic"
      },
      "release_date": "2023-01-15",
      "location": {
      "lat": 40.7128,
      "lon": -74.0060
      },
      "tags": ["headphones", "bluetooth", "wireless"],
      "promotional_info": "20% off for a limited time"
      }
  • 商品推荐
    • 商品的A/B测试如何设计?
    • 如何设计商品的推荐算法?
    • 商品的个性化定制如何实现?
  • 获取商品详情

订单管理 Order Center

订单系统,平台的”生命中轴线”

订单中需要包含哪些信息

常见的订单类型

  1. 实物订单
    典型场景:电商平台购物(如买衣服、家电)
    核心特征:
    需要物流配送,涉及收货地址、运费、物流跟踪
    需要库存校验与扣减
    售后流程(退货、换货、退款)复杂
    订单状态多(待发货、已发货、已收货等)

  2. 虚拟订单
    典型场景:会员卡、电子券、游戏点卡、电影票等
    核心特征:
    无物流配送,不需要收货地址和运费
    通常无需库存(或库存为虚拟库存)
    订单完成后直接发放虚拟物品或凭证
    售后流程简单或无售后

  3. 预售订单
    典型场景:新品预售、定金膨胀、众筹等
    核心特征:
    订单分为定金和尾款两阶段
    需校验定金支付、尾款支付的时效
    可能涉及定金不退、尾款未付订单自动关闭等规则
    发货时间通常在尾款支付后

  4. O2O订单,外卖订单
    典型场景:酒店预订
    核心特征:
    需选择入住/离店日期、房型、入住人信息
    需对接第三方酒店系统实时查房、锁房
    取消、变更政策复杂,可能涉及违约金
    无物流,但有电子凭证或入住确认

订单系统的演进

阶段 主要特征/能力 技术架构/数据模型 适用场景/目标 实现方式简单说明
初始阶段 - 实现订单基本流转(下单、支付、发货、收货、取消)
- 单一订单类型(实物订单)
- 订单与商品、用户简单关联
单体应用/单表或少量表结构 业务初期,订单量小,流程简单,SKU/商家数量有限 单体应用,单表存储,简单业务逻辑,直接数据库操作
成长阶段(订单中心) - 支持订单拆单、合单(如多仓发货、合并支付)
- 支持多品类订单(如实物+虚拟)
- 订单中心化,订单与支付、配送、售后等子系统解耦
- 订单与商品快照、操作日志关联
微服务/多表/订单中心架构 平台型电商,业务扩展,需支持多商家、多类型订单,订单量大幅增长 订单中心服务,微服务拆分,多表关联,服务间接口调用,快照与日志表设计
成熟期(平台化) - 支持多样化订单类型(预售、虚拟、O2O、定制、JIT等)
- 订单流程可配置/插件化/工作流引擎/状态机框架/规则引擎等
- 订单状态机、履约、支付、退款等子流程解耦
- 支持复杂的促销、分账、履约模式
分布式/服务化/灵活数据模型 大型/综合电商,业务复杂,需快速适应新业务模式和高并发场景 分布式服务,插件化/配置化流程,状态机驱动,异步消息,灵活数据模型
未来智能化 - 订单智能路由与分配(如智能分仓、智能客服)
- 实时风控与反欺诈
- 订单数据实时分析与洞察
- 高可用、弹性伸缩、自动化运维
云原生/大数据/AI驱动架构 超大规模平台,国际化、智能化、数据驱动,需极致稳定与创新能力 云原生架构,AI/大数据分析,自动化运维,弹性伸缩,智能路由与风控

常见的订单模型设计

订单表(order_tab):记录用户的购买订单信息。主键为 order_id。

  • pay_order_id:支付订单ID,作为外键关联支付订单。
  • user_id:用户ID,标识购买订单的用户。
  • total_amount:订单的总金额。
  • order_status:订单状态,如已完成、已取消等。
  • payment_status:支付状态,与支付订单相关。
  • fulfillment_status:履约状态,表示订单的配送或服务状态。
  • refund_status:退款状态,用于标识订单是否有退款

订单商品表(order_item_tab:记录订单中具体商品的信息。主键为 order_item_id。

  • order_id:订单ID,作为外键关联订单。
  • item_id:商品ID,表示订单中的商品。
  • item_snapshot_id:商品快照ID,记录当时购买时的商品信息快照。
  • item_status:商品状态,如已发货、退货等。
  • quantity:购买数量。
  • price:商品单价。
  • discount:商品折扣金额

订单支付表(pay_order_tab):主要用于记录用户的支付信息。主键为 pay_order_id,标识唯一的支付订单。

  • user_id:用户ID,标识支付的用户。
  • payment_method:支付方式,如信用卡、支付宝等。
  • payment_status:支付状态,如已支付、未支付等。
  • pay_amount、cash_amount、coin_amount、voucher_amount:支付金额、现金支付金额、代币支付金额、优惠券使用金额。
  • 时间戳字段包括创建时间、初始化时间和更新时间

退款表(refund_tab):记录订单或订单项的退款信息。主键为 refund_id。

  • order_id:订单ID,作为外键关联订单。
  • order_item_id:订单项ID,标识具体商品的退款。
  • refund_amount:退款金额。
  • reason:退款原因。
  • quantity:退款的商品数量。
  • refund_status:退款状态。
  • refund_time:退款操作时间。

实体间关系:

  • 支付订单与订单:
  • 一个支付订单可能关联多个购买订单,形成 一对多 关系。
    例如,用户可以通过一次支付购买多个不同的订单。
  • 订单与订单商品:
    一个订单可以包含多个订单项,形成 一对多 关系。
    订单项代表订单中所购买的每个商品的详细信息。
  • 订单与退款:
    • 一个订单可能包含多个退款,形成 一对多 关系。
    • 退款可以是针对订单整体,也可以针对订单中的某个商品

订单状态机设计

Order 主状态机

支付状态机

履约状态机

退货退款状体机

异常单人工介入

  • 用户发起退款单拒绝
  • 退货失败,订单状态无法流转
  • 退款失败
  • 退营销失败

订单ID 生成策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
  # 时间戳 + 机器id + uid % 1000 + 自增序号

import time
import threading
from typing import Union

class OrderNoGenerator:
def __init__(self, machine_id: int):
"""
初始化订单号生成器
:param machine_id: 机器ID (0-999)
"""
if not 0 <= machine_id <= 999:
raise ValueError("机器ID必须在0-999之间")

self.machine_id = machine_id
self.sequence = 0
self.last_timestamp = -1
self.lock = threading.Lock() # 线程锁,保证线程安全

def _wait_next_second(self, last_timestamp: int) -> int:
"""
等待下一秒
:param last_timestamp: 上次时间戳
:return: 新的时间戳
"""
timestamp = int(time.time())
while timestamp <= last_timestamp:
timestamp = int(time.time())
return timestamp

def generate_order_no(self, user_id: int) -> Union[int, str]:
"""
生成订单号
:param user_id: 用户ID
:return: 订单号(整数或字符串形式)
"""
with self.lock: # 使用线程锁保证线程安全
# 获取当前时间戳(秒级)
timestamp = int(time.time())

# 处理时间回拨
if timestamp < self.last_timestamp:
raise RuntimeError("系统时间回拨,拒绝生成订单号")

# 如果是同一秒,序列号自增
if timestamp == self.last_timestamp:
self.sequence = (self.sequence + 1) % 1000
# 如果序列号用完了,等待下一秒
if self.sequence == 0:
timestamp = self._wait_next_second(self.last_timestamp)
else:
# 不同秒,序列号重置
self.sequence = 0

self.last_timestamp = timestamp

# 获取用户ID的后3位
user_id_suffix = user_id % 1000

# 组装订单号
order_no = (timestamp * 1000000000 + # 时间戳左移9位
self.machine_id * 1000000 + # 机器ID左移6位
user_id_suffix * 1000 + # 用户ID左移3位
self.sequence) # 序列号

return order_no

def generate_order_no_str(self, user_id: int) -> str:
"""
生成字符串形式的订单号
:param user_id: 用户ID
:return: 字符串形式的订单号
"""
order_no = self.generate_order_no(user_id)
return f"{order_no:019d}" # 补零到19位

# 使用示例
def main():
# 创建订单号生成器实例
generator = OrderNoGenerator(machine_id=1)

# 生成订单号
user_id = 12345
order_no = generator.generate_order_no(user_id)
order_no_str = generator.generate_order_no_str(user_id)

print(f"整数形式订单号: {order_no}")
print(f"字符串形式订单号: {order_no_str}")

# 测试并发
def test_concurrent():
for _ in range(5):
order_no = generator.generate_order_no(user_id)
print(f"并发生成的订单号: {order_no}")

# 创建多个线程测试并发
threads = []
for _ in range(3):
t = threading.Thread(target=test_concurrent)
threads.append(t)
t.start()

# 等待所有线程完成
for t in threads:
t.join()

if __name__ == "__main__":
main()

订单商品快照

方案1. 直接使用商品系统的item snapshot。(由商品系统维护快照)

  • 商品系统负责维护商品快照
  • 订单系统通过引用商品快照ID来关联商品信息
  • 商品信息变更时,商品系统生成新的快照版本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    -- 商品系统维护的快照表
    CREATE TABLE item_snapshot_tab (
    snapshot_id BIGINT PRIMARY KEY,
    item_id BIGINT NOT NULL,
    version INT NOT NULL,
    data JSON NOT NULL, -- 存储商品完整信息
    created_at TIMESTAMP NOT NULL,
    INDEX idx_item_version (item_id, version)
    );

    -- 订单系统引用快照
    CREATE TABLE order_item_tab (
    order_id BIGINT,
    item_id BIGINT,
    snapshot_id BIGINT, -- 引用商品快照
    quantity INT,
    price DECIMAL(10,2),
    FOREIGN KEY (snapshot_id) REFERENCES item_snapshot(snapshot_id)
    );
    优点
  • 数据一致性高:商品系统统一管理快照,避免数据不一致
  • 存储效率高:多个订单可以共享同一个快照版本
  • 维护成本低:订单系统不需要关心快照的生成和管理
  • 查询性能好:可以直接通过快照ID获取完整商品信息

缺点

  • 系统耦合度高:订单系统强依赖商品系统的快照服务
  • 扩展性受限:商品系统需要支持所有订单系统可能需要的商品信息
  • 版本管理复杂:需要处理快照的版本控制和清理
  • 跨系统调用:订单系统需要调用商品系统获取快照信息

方案2. 创单时提供商品详情信息。(由订单维护商品快照)

1
2
3
4
5
6
7
8
CREATE TABLE order_item (
order_id BIGINT,
item_id BIGINT,
quantity INT,
price DECIMAL(10,2),
snapshot_data JSON NOT NULL, -- 存储下单时的商品信息
FOREIGN KEY (order_id, item_id) REFERENCES order_item_snapshot(order_id, item_id)
);

方案3. 创单时提供商品详情信息。(由订单维护商品快照)+ 快照复用

设计思路:

  • 订单系统维护自己的快照表,但增加快照复用机制
  • 使用商品信息的摘要(摘要算法如MD5)作为快照的唯一标识
  • 相同摘要的商品信息共享同一个快照记录
  • 创单时先检查摘要是否存在,存在则复用,不存在则创建新快照
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 订单系统维护的快照表
CREATE TABLE order_item_snapshot (
snapshot_id BIGINT PRIMARY KEY,
item_id BIGINT NOT NULL,
item_hash VARCHAR(32) NOT NULL COMMENT '商品信息摘要',
snapshot_data JSON NOT NULL COMMENT '存储下单时的商品信息',
created_at TIMESTAMP NOT NULL,
INDEX idx_item_hash (item_hash),
INDEX idx_item_id (item_id)
);
-- 订单商品表
CREATE TABLE order_item (
order_id BIGINT,
item_id BIGINT,
snapshot_id BIGINT,
quantity INT,
price DECIMAL(10,2),
FOREIGN KEY (snapshot_id) REFERENCES order_item_snapshot(snapshot_id)
);

适用场景:

  • 商品模型比较固定,项目初期,团队比较小,能接受系统之间的耦合,可以考虑用1
  • 不同商品差异比较大,商品信息结构复杂,考虑用2
  • 订单量太大,考虑复用快照

核心流程

正常流程和逆向流程

创单

核心步骤
  1. 参数校验。用户校验,是否异常用户。
  2. 商品与价格校验。校验商品是否存在、是否上架、价格是否有效
  3. 库存校验与预占。检查库存是否充足,部分场景下进行库存预占(锁库存)。
  4. 营销信息校验。校验优惠券、积分等是否可用,计算优惠金额。
  5. 订单金额计算。计算订单总金额、应付金额、各项明细。
  6. 生成订单号。生成全局唯一订单号,保证幂等性。
  7. 订单数据落库。写入订单主表、订单明细表、扩展表等。
  8. 扣减库存、扣减实际库存(有的系统在支付后扣减)。
  9. 发送消息/异步处理。发送订单创建成功消息,通知库存、物流、营销等系统。
  10. 返回下单结果。返回订单号、支付信息等给前端。
实现思路
  • 接口定义:通过OrderCreationStep接口定义了每个步骤必须实现的方法
  • 上下文共享:使用OrderCreationContext在步骤间共享数据
  • 步骤独立:每个步骤都是独立的,便于维护和测试
  • 回滚机制:每个步骤都实现了回滚方法
  • 流程管理:通过OrderCreationManager统一管理步骤的执行和回滚
  • 错误处理:统一的错误处理和回滚机制
  • 可扩展性:易于添加新的步骤或修改现有步骤
  • 如何解决不同category 创单差异较大的问题?
    • 插件化/策略模式。将订单处理流程拆分为多个步骤(如校验、支付、通知等)。不同订单类型实现各自的处理逻辑,通过策略模式动态选择。
    1. 订单类型标识。在订单主表中增加订单类型字段,根据类型选择不同的处理流程。
    2. 扩展字段。使用JSON或扩展表存储特定订单类型的特殊字段(如酒店的入住日期、机票的航班信息)。
    3. 流程引擎。使用流程引擎(如BPMN)定义和管理复杂的订单处理流程,支持动态调整。
点击查看创单核心逻辑代码实现


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
package order

import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"time"
)

// OrderType 订单类型
type OrderType string

const (
OrderTypePhysical OrderType = "physical" // 实物订单
OrderTypeVirtual OrderType = "virtual" // 虚拟订单
OrderTypePresale OrderType = "presale" // 预售订单
OrderTypeHotel OrderType = "hotel" // 酒店订单
OrderTypeTopUp OrderType = "topup" // 充值订单
)

// OrderStatus 订单状态
type OrderStatus string

const (
OrderStatusInit OrderStatus = "init" // 初始化
OrderStatusPending OrderStatus = "pending" // 待支付
OrderStatusPaid OrderStatus = "paid" // 已支付
OrderStatusShipping OrderStatus = "shipping" // 发货中
OrderStatusSuccess OrderStatus = "success" // 成功
OrderStatusFailed OrderStatus = "failed" // 失败
OrderStatusCanceled OrderStatus = "canceled" // 已取消
)

// Order 订单基础信息
type Order struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Type OrderType `json:"type"`
Status OrderStatus `json:"status"`
Amount float64 `json:"amount"`
Detail json.RawMessage `json:"detail"` // 不同类型订单的特殊字段
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// OrderCreationContext 创单上下文
type OrderCreationContext struct {
Ctx context.Context
Order *Order
Params map[string]interface{} // 创单参数
Cache map[string]interface{} // 步骤间共享数据
Errors []error // 错误记录
StepResults map[string]StepResult // 每个步骤的执行结果
RollbackFailedSteps []string // 记录回滚失败的步骤
}

// StepResult 步骤执行结果
type StepResult struct {
Success bool
Error error
Data interface{}
CompensateData interface{} // 用于补偿的数据
}

// OrderCreationStep 创单步骤接口
type OrderCreationStep interface {
Execute(ctx *OrderCreationContext) error
Rollback(ctx *OrderCreationContext) error
Compensate(ctx *OrderCreationContext) error // 异步补偿
Name() string
}

// 错误定义
var (
ErrInvalidParams = errors.New("invalid parameters")
ErrProductNotFound = errors.New("product not found")
ErrProductOffline = errors.New("product is offline")
ErrStockInsufficient = errors.New("stock insufficient")
ErrUserBlocked = errors.New("user is blocked")
ErrSystemBusy = errors.New("system is busy")
)

// OrderError 订单错误
type OrderError struct {
Step string
Message string
Err error
}

func (e *OrderError) Error() string {
return fmt.Sprintf("step: %s, message: %s, error: %v", e.Step, e.Message, e.Err)
}

// 参数校验步骤
type ParamValidationStep struct{}

func (s *ParamValidationStep) Execute(ctx *OrderCreationContext) error {
// 通用参数校验
if ctx.Order.UserID == "" || ctx.Order.Type == "" {
return &OrderError{Step: s.Name(), Message: "missing required fields", Err: ErrInvalidParams}
}

// 订单类型特殊参数校验
switch ctx.Order.Type {
case OrderTypePhysical:
if addr, ok := ctx.Params["address"].(string); !ok || addr == "" {
return &OrderError{Step: s.Name(), Message: "missing address for physical order", Err: ErrInvalidParams}
}
case OrderTypeHotel:
if _, ok := ctx.Params["check_in_date"].(time.Time); !ok {
return &OrderError{Step: s.Name(), Message: "missing check-in date for hotel order", Err: ErrInvalidParams}
}
}
return nil
}

func (s *ParamValidationStep) Rollback(ctx *OrderCreationContext) error {
// 参数校验步骤无需回滚
return nil
}

func (s *ParamValidationStep) Compensate(ctx *OrderCreationContext) error {
// 参数校验步骤无需补偿
return nil
}

func (s *ParamValidationStep) Name() string {
return "param_validation"
}

// Product 商品信息
type Product struct {
ID string
Name string
Price float64
IsOnSale bool
}

// ProductService 商品服务接口
type ProductService interface {
GetProduct(ctx context.Context, productID string) (*Product, error)
}

// StockService 库存服务接口
type StockService interface {
LockStock(ctx context.Context, productID string, quantity int) (string, error)
UnlockStock(ctx context.Context, lockID string) error
DeductStock(ctx context.Context, productID string, quantity int) error
RevertDeductStock(ctx context.Context, productID string, quantity int) error
}

// PromotionService 营销服务接口
type PromotionService interface {
ValidateCoupon(ctx context.Context, couponCode string, userID string, orderAmount float64) (*Coupon, error)
UseCoupon(ctx context.Context, couponCode string, userID string, orderID string) error
RevertCouponUsage(ctx context.Context, couponCode string, userID string, orderID string) error
DeductPoints(ctx context.Context, userID string, points int) error
RevertPointsDeduction(ctx context.Context, userID string, points int) error
}

// Coupon 优惠券信息
type Coupon struct {
Code string
Type string
Amount float64
Threshold float64
ExpireTime time.Time
}

// 商品校验步骤
type ProductValidationStep struct {
productService ProductService
}

func (s *ProductValidationStep) Execute(ctx *OrderCreationContext) error {
productID := ctx.Params["product_id"].(string)
product, err := s.productService.GetProduct(ctx.Ctx, productID)
if err != nil {
return &OrderError{Step: s.Name(), Message: "failed to get product", Err: err}
}

if !product.IsOnSale {
return &OrderError{Step: s.Name(), Message: "product is offline", Err: ErrProductOffline}
}

ctx.Cache["product"] = product
return nil
}

func (s *ProductValidationStep) Rollback(ctx *OrderCreationContext) error {
return nil
}

func (s *ProductValidationStep) Compensate(ctx *OrderCreationContext) error {
return nil
}

func (s *ProductValidationStep) Name() string {
return "product_validation"
}

// 库存校验步骤
type StockValidationStep struct {
stockService StockService
}

func (s *StockValidationStep) Execute(ctx *OrderCreationContext) error {
if ctx.Order.Type == OrderTypeVirtual || ctx.Order.Type == OrderTypeTopUp {
return nil
}

productID := ctx.Params["product_id"].(string)
quantity := ctx.Params["quantity"].(int)

lockID, err := s.stockService.LockStock(ctx.Ctx, productID, quantity)
if err != nil {
return &OrderError{Step: s.Name(), Message: "failed to lock stock", Err: err}
}

ctx.Cache["stock_lock_id"] = lockID
return nil
}

func (s *StockValidationStep) Rollback(ctx *OrderCreationContext) error {
if lockID, ok := ctx.Cache["stock_lock_id"].(string); ok {
return s.stockService.UnlockStock(ctx.Ctx, lockID)
}
return nil
}

func (s *StockValidationStep) Compensate(ctx *OrderCreationContext) error {
return nil
}

func (s *StockValidationStep) Name() string {
return "stock_validation"
}

// 库存扣减步骤
type StockDeductionStep struct {
stockService StockService
}

func (s *StockDeductionStep) Execute(ctx *OrderCreationContext) error {
// 虚拟商品和充值订单跳过库存扣减
if ctx.Order.Type == OrderTypeVirtual || ctx.Order.Type == OrderTypeTopUp {
return nil
}

productID := ctx.Params["product_id"].(string)
quantity := ctx.Params["quantity"].(int)

// 执行库存扣减
if err := s.stockService.DeductStock(ctx.Ctx, productID, quantity); err != nil {
return &OrderError{
Step: s.Name(),
Message: "failed to deduct stock",
Err: err,
}
}

// 记录扣减信息,用于回滚
ctx.Cache["stock_deducted"] = map[string]interface{}{
"product_id": productID,
"quantity": quantity,
}

return nil
}

func (s *StockDeductionStep) Rollback(ctx *OrderCreationContext) error {
deducted, ok := ctx.Cache["stock_deducted"].(map[string]interface{})
if !ok {
return nil
}

productID := deducted["product_id"].(string)
quantity := deducted["quantity"].(int)

return s.stockService.RevertDeductStock(ctx.Ctx, productID, quantity)
}

func (s *StockDeductionStep) Compensate(ctx *OrderCreationContext) error {
deducted, ok := ctx.Cache["stock_deducted"].(map[string]interface{})
if !ok {
return nil
}

productID := deducted["product_id"].(string)
quantity := deducted["quantity"].(int)

// 创建补偿消息
compensationMsg := StockCompensationMessage{
OrderID: ctx.Order.ID,
ProductID: productID,
Quantity: quantity,
Timestamp: time.Now(),
}

// TODO: 实现发送到补偿队列的逻辑
// return sendToCompensationQueue("stock_compensation", compensationMsg)
return nil
}

func (s *StockDeductionStep) Name() string {
return "stock_deduction"
}

// 营销活动扣减步骤
type PromotionDeductionStep struct {
promotionService PromotionService
}

func (s *PromotionDeductionStep) Execute(ctx *OrderCreationContext) error {
// 处理优惠券
if couponCode, ok := ctx.Params["coupon_code"].(string); ok {
// 验证优惠券
coupon, err := s.promotionService.ValidateCoupon(
ctx.Ctx,
couponCode,
ctx.Order.UserID,
ctx.Order.Amount,
)
if err != nil {
return &OrderError{
Step: s.Name(),
Message: "invalid coupon",
Err: err,
}
}

// 使用优惠券
if err := s.promotionService.UseCoupon(ctx.Ctx, couponCode, ctx.Order.UserID, ctx.Order.ID); err != nil {
return &OrderError{
Step: s.Name(),
Message: "failed to use coupon",
Err: err,
}
}

// 记录优惠券使用信息,用于回滚
ctx.Cache["used_coupon"] = couponCode

// 更新订单金额
ctx.Order.Amount -= coupon.Amount
}

// 处理积分抵扣
if points, ok := ctx.Params["use_points"].(int); ok && points > 0 {
// 扣减积分
if err := s.promotionService.DeductPoints(ctx.Ctx, ctx.Order.UserID, points); err != nil {
return &OrderError{
Step: s.Name(),
Message: "failed to deduct points",
Err: err,
}
}

// 记录积分扣减信息,用于回滚
ctx.Cache["deducted_points"] = points

// 更新订单金额(假设1积分=0.01元)
ctx.Order.Amount -= float64(points) * 0.01
}

return nil
}

func (s *PromotionDeductionStep) Rollback(ctx *OrderCreationContext) error {
// 回滚优惠券使用
if couponCode, ok := ctx.Cache["used_coupon"].(string); ok {
if err := s.promotionService.RevertCouponUsage(ctx.Ctx, couponCode, ctx.Order.UserID, ctx.Order.ID); err != nil {
return err
}
}

// 回滚积分扣减
if points, ok := ctx.Cache["deducted_points"].(int); ok {
if err := s.promotionService.RevertPointsDeduction(ctx.Ctx, ctx.Order.UserID, points); err != nil {
return err
}
}

return nil
}

func (s *PromotionDeductionStep) Compensate(ctx *OrderCreationContext) error {
// 优惠券补偿
if couponCode, ok := ctx.Cache["used_coupon"].(string); ok {
// TODO: 实现优惠券补偿逻辑
// 1. 发送到补偿队列
// 2. 记录补偿日志
// 3. 通知运营人员
}

// 积分补偿
if points, ok := ctx.Cache["deducted_points"].(int); ok {
// TODO: 实现积分补偿逻辑
// 1. 发送到补偿队列
// 2. 记录补偿日志
// 3. 通知运营人员
}

return nil
}

func (s *PromotionDeductionStep) Name() string {
return "promotion_deduction"
}

// OrderFactory 订单工厂
type OrderFactory struct {
commonSteps []OrderCreationStep
typeSteps map[OrderType][]OrderCreationStep
}

func NewOrderFactory() *OrderFactory {
f := &OrderFactory{
commonSteps: []OrderCreationStep{
&ParamValidationStep{},
&ProductValidationStep{},
&PromotionDeductionStep{},
},
typeSteps: make(map[OrderType][]OrderCreationStep),
}

// 实物订单特有步骤
f.typeSteps[OrderTypePhysical] = []OrderCreationStep{
&StockValidationStep{},
&StockDeductionStep{},
}

// 虚拟订单特有步骤
f.typeSteps[OrderTypeVirtual] = []OrderCreationStep{}

// 预售订单特有步骤
f.typeSteps[OrderTypePresale] = []OrderCreationStep{
&StockValidationStep{},
}

// 酒店订单特有步骤
f.typeSteps[OrderTypeHotel] = []OrderCreationStep{}

return f
}

func (f *OrderFactory) GetSteps(orderType OrderType) []OrderCreationStep {
steps := make([]OrderCreationStep, 0)
steps = append(steps, f.commonSteps...)
if typeSteps, ok := f.typeSteps[orderType]; ok {
steps = append(steps, typeSteps...)
}
return steps
}

// Logger 日志接口
type Logger interface {
Info(msg string, args ...interface{})
Error(msg string, args ...interface{})
}

// OrderCreationManager 订单创建管理器
type OrderCreationManager struct {
factory *OrderFactory
logger Logger
}

func (m *OrderCreationManager) CreateOrder(ctx context.Context, params map[string]interface{}) (*Order, error) {
orderCtx := &OrderCreationContext{
Ctx: ctx,
Params: params,
Cache: make(map[string]interface{}),
StepResults: make(map[string]StepResult),
RollbackFailedSteps: make([]string, 0),
}

// 初始化订单
order := &Order{
ID: generateOrderID(),
UserID: params["user_id"].(string),
Type: OrderType(params["type"].(string)),
Status: OrderStatusInit,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
orderCtx.Order = order

// 获取订单类型对应的处理步骤
steps := m.factory.GetSteps(order.Type)

// 执行步骤
executedSteps := make([]OrderCreationStep, 0)
for _, step := range steps {
stepName := step.Name()
m.logger.Info("executing step", "step", stepName)

err := step.Execute(orderCtx)
if err != nil {
m.logger.Error("step execution failed", "step", stepName, "error", err)

orderCtx.Errors = append(orderCtx.Errors, err)

// 执行回滚,并记录回滚失败的步骤
m.rollbackSteps(orderCtx, executedSteps)

// 只对回滚失败的步骤进行补偿
if len(orderCtx.RollbackFailedSteps) > 0 {
go m.compensateFailedRollbacks(orderCtx)
}

return nil, err
}

executedSteps = append(executedSteps, step)
m.logger.Info("step executed successfully", "step", stepName)
}

return order, nil
}

// 修改回滚逻辑,记录回滚失败的步骤
func (m *OrderCreationManager) rollbackSteps(ctx *OrderCreationContext, steps []OrderCreationStep) {
for i := len(steps) - 1; i >= 0; i-- {
step := steps[i]
stepName := step.Name()

if err := step.Rollback(ctx); err != nil {
m.logger.Error("step rollback failed", "step", stepName, "error", err)
// 记录回滚失败的步骤
ctx.RollbackFailedSteps = append(ctx.RollbackFailedSteps, stepName)
}
}
}

// 新的补偿方法,只处理回滚失败的步骤
func (m *OrderCreationManager) compensateFailedRollbacks(ctx *OrderCreationContext) {
m.logger.Info("starting compensation for failed rollbacks",
"failed_steps", ctx.RollbackFailedSteps)

// 获取所有步骤的映射
allSteps := make(map[string]OrderCreationStep)
for _, step := range m.factory.GetSteps(ctx.Order.Type) {
allSteps[step.Name()] = step
}

// 只对回滚失败的步骤进行补偿
for _, failedStepName := range ctx.RollbackFailedSteps {
if step, ok := allSteps[failedStepName]; ok {
if err := step.Compensate(ctx); err != nil {
m.logger.Error("step compensation failed",
"step", failedStepName,
"error", err)

// 补偿失败处理
m.handleCompensationFailure(ctx, failedStepName, err)
}
}
}
}

// 处理补偿失败的情况
func (m *OrderCreationManager) handleCompensationFailure(ctx *OrderCreationContext, stepName string, err error) {
// 创建补偿任务
compensationTask := CompensationTask{
OrderID: ctx.Order.ID,
StepName: stepName,
Params: ctx.Params,
Cache: ctx.Cache,
RetryCount: 0,
MaxRetries: 3,
CreatedAt: time.Now(),
}

// 记录错误日志
m.logger.Error("compensation task created for failed step",
"order_id", compensationTask.OrderID,
"step", compensationTask.StepName,
"error", err)

// TODO: 实现具体的补偿任务处理逻辑
// 1. 将任务保存到数据库
// 2. 发送到消息队列
// 3. 触发告警
}

// DefaultLogger 默认日志实现
type DefaultLogger struct{}

func NewDefaultLogger() Logger {
return &DefaultLogger{}
}

func (l *DefaultLogger) Info(msg string, args ...interface{}) {
log.Printf("INFO: "+msg, args...)
}

func (l *DefaultLogger) Error(msg string, args ...interface{}) {
log.Printf("ERROR: "+msg, args...)
}

// 辅助函数
func generateOrderID() string {
return fmt.Sprintf("ORDER_%d", time.Now().UnixNano())
}

// CompensationTask 补偿任务结构
type CompensationTask struct {
OrderID string
StepName string
Params map[string]interface{}
Cache map[string]interface{}
RetryCount int
MaxRetries int
CreatedAt time.Time
}

// StockCompensationMessage 库存补偿消息
type StockCompensationMessage struct {
OrderID string
ProductID string
Quantity int
Timestamp time.Time
}

支付

支付流程

1. 支付校验。用户校验,订单状态校验等 2. 营销活动扣减deduction、回滚rollback、补偿compensation. 3. 支付初始化 4. 支付回调 5. 补偿队列 6. OrderBus 订单事件
支付状态的设计
1
2
3
4
5
6
7
8
9
10
11
12
13
14
P0: PAYMENT_NOT_STARTED - 未开始
P1: PAYMENT_PENDING - 支付中,用户点击了pay按钮,等待支付)

P2: MARKETING_Init - 营销初始化
P3: MARKETING_FAILED - 营销扣减失败
P4: MARKETING_SUCCESS - 营销扣减成功

P5: PAYMENT_INITIALIZED - 支付初始化
P6: PAYMENT_INITIALIZED_FAILED - 支付初始化失败
P7: PAYMENT_PROCESSING - 支付处理中。(支付系统正在处理支付请求)
P8: PAYMENT_SUCCESS - 支付成功
P9: PAYMENT_FAILED - 支付失败
P10: PAYMENT_CANCELLED - 支付取消
P11: PAYMENT_TIMEOUT - 支付超时
异常和补偿设计

常见的异常:
营销部分:

  1. 营销扣减补偿操作重复。(营销接口幂等设计)
  2. 营销已经扣减了,但是后续步骤失败,需要回滚扣减的操作。(业务代码中需要有rollback操作)
  3. 营销已经扣减了,回滚扣减失败。延时队列任务补偿。(回滚失败发送延时队列,任务补偿)
  4. 营销已经扣减了,写延时队列失败,任务没有补偿成功。(补偿任务通过扫描异常单进行补偿)
  5. 营销已经扣减了,延时队列消息重复,重复回滚。(依赖营销系统的幂等操作)
  6. 营销已经扣减了,请求已经发给了营销服务,营销服务已经扣减了,但是回包失败。(请求营销接口之前更新订单状态为P2,针对P2的订单进行补偿)

支付部分:

  1. 重复支付。(支付接口幂等设计)
  2. 支付初始化请求支付成功,但是回包失败(重续针对P5的订单进行补偿,查询支付系统是否收单,已经支付结果查询)
  3. 支付回调包重复,更新回调结果幂等。
  4. 支付回调包丢失,对于P7支付单需要补偿。

履约

履约核心流程

履约状态机的设计
1
2
3
4
5
6
F0: FULFILLMENT_NOT_STARTED - 未开始
F1: FULFILLMENT_PENDING - 履约开始
F2: FULFILLMENT_PROCESSING - 履约处理中
F3: FULFILLMENT_FAILED - 履约失败
F4: FULFILLMENT_SUCCESS - 履约成功
F5: FULFILLMENT_CANCELLED - 履约取消
异常和补偿的设计
  1. 订阅支付完成的事件O2
  2. 在请求fulfillment/init履约初始化之前,更新订单状态为F1
  3. fulfillment/init 接口的回包丢了。(针对F1订单进行补偿)
  4. fulfillment/init 重复请求(幂等设计)
  5. F2订单补偿。(fulfillment/callback 丢包,处理失败等)

return & refund

主要流程
  1. 订单服务作为协调者。与履约服务、营销服务、支付服务解耦
  2. 用 OrderBus 进行事件传递
  3. 状体机设计
  4. 异常处理
异常和补偿机制
  1. 退货环节异常
  • 退货初始化失败:直接发送退款失败事件
  • 退货回调失败:更新状态为 R7,发送失败事件
  1. 营销退款异常
  • 营销处理失败:更新状态为 R14,发送失败事件
  • 营销处理成功:更新状态为 R13,继续后续流程
  1. 支付退款异常
  • 支付退款失败:更新状态为 R11,发送失败事件
  • 支付退款成功:更新状态为 R10,发送成功事件
订单详情查询

系统挑战和解决方案

如何维护订单状态的最终一致性?

不一致的原因

  • 重复请求
  • 丢包。例如,请求发货,对方收单,回包失败。
  • 资源回滚:营销、库存
  • 并发问题

状态机

  • 设计层面,严格的状态转换规则 + 状态转换的触发事件
  • 状态转换的原子性。(事务性)

并发更新数据库前,要用乐观锁或者悲观锁,

  • 乐观锁:同时在更新时判断版本号是否是之前取出来的版本号,更新成功就结束
  • 悲观锁:先使用select for update进行锁行记录,然后更新
1
2
3
4
5
6
7
8
9
UPDATE orders 
SET status = 'NEW_STATUS',
version = version + 1
WHERE id = ? AND version = ?

BEGIN;
SELECT * FROM orders WHERE id = ? FOR UPDATE;
UPDATE orders SET status = 'NEW_STATUS' WHERE id = ?;
COMMIT;

幂等设计。比如重复支付、重复扣减营销、重复履约等

  • 支付重复支付,支付回调幂等设计。
  • 重复营销扣减,回滚,
  • 重复履约
  • 重复回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 使用支付单号作为幂等键
@Transactional
public void handlePaymentCallback(String paymentId, String status) {
// 检查是否已处理
if (isProcessed(paymentId)) {
return;
}
// 处理支付回调
processPaymentCallback(paymentId, status);
// 记录处理状态
markAsProcessed(paymentId);
}


// 使用订单号+营销资源ID作为幂等键
@Transactional
public void deductMarketingResource(String orderId, String resourceId) {
if (isDeducted(orderId, resourceId)) {
return;
}
// 扣减营销资源
deductResource(orderId, resourceId);
// 记录扣减状态
markAsDeducted(orderId, resourceId);
}

补偿机制兜底

  • 异常回滚。营销扣减回滚
  • 消息队列补偿:补偿队列,重试。(可能丢消息)
  • 定时任务补偿:扫表补偿
  • 依赖方支付查询和幂等设计

分布式事务

  • 营销扣减
  • 库存扣减
  • 支付等业务
  • 实现状态转换和业务操作在同一个事务中完成
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Transactional
    public void processOrderWithDistributedTransaction(Order order) {
    try {
    // 1. 更新订单状态
    updateOrderStatus(order);
    // 2. 扣减库存
    deductInventory(order);
    // 3. 创建物流单
    createLogistics(order);
    } catch (Exception e) {
    // 触发补偿机制
    triggerCompensation(order);
    }
    }

异常单人工介入

对账机制

商品信息缓存和数据一致性

主从架构中如何获取最新的数据,避免因为主从延时导致获得脏数据

策略 优点 缺点
1. 直接读取主库 - 一致性: 始终获取最新的数据。 - 性能: 增加主库的负载,可能导致性能瓶颈。
- 简单性: 实现简单直接,因为它直接查询可信的源。 - 可扩展性: 主库可能成为瓶颈,限制系统在高读流量下有效扩展的能力。
2. 使用VersionCache与从库 - 性能: 分散读取负载到从库,减少主库的压力。 - 复杂性: 实现更加复杂,需要进行缓存管理并处理潜在的不一致性问题。
- 可扩展性: 通过将大部分读取操作卸载到从库,实现更好的扩展性。 - 缓存管理: 需要进行适当的缓存失效处理和同步,以确保数据的一致性。
- 一致性: 通过比较版本并在必要时回退到主库,提供确保最新数据的机制。 - 潜在延迟: 从库的数据可能仍然存在不同步的可能性,导致数据更新前有轻微延迟。

常见问题1: 重复下单、支付、履约问题(重复和幂等问题)

场景:

  1. 下单、去重、DB唯一键兜底。去重逻辑是约定的
  2. 支付、checkoutid,唯一键
  3. 履约、先获取reference id,再履约

解决方案:

  1. 前端方案
    前端通过js脚本控制,无法解决用户刷新提交的请求。另外也无法解决恶意提交。
    不建议采用该方案,如果想用,也只是作为一个补充方案。

  2. 中间环节去重。根据请求参数中间去重
    当用户点击购买按钮时,渲染下单页面,展示商品、收货地址、运费、价格等信息,同时页面会埋上 Token 信息,用户提交订单时,后端业务逻辑会校验token,有且匹配才认为是合理请求。

  3. 利用数据库自身特性 “主键唯一约束”,在插入订单记录时,带上主键值,如果订单重复,记录插入会失败。
    操作过程如下:
    引入一个服务,用于生成一个”全局唯一的订单号”;
    进入创建订单页面时,前端请求该服务,预生成订单ID;
    提交订单时,请求参数除了业务参数外,还要带上这个预生成订单ID

快照和操作日志

为了保证数据的 完整性、可追溯性,写操作需要关注的问题
场景:
商品信息是可以修改的,当用户下单后,为了更好解决后面可能存在的买卖纠纷,创建订单时会同步保存一份商品详情信息,称之为订单快照

解决方案:
同一件商品,会有很多用户会购买,如果热销商品,短时间就会有上万的订单。如果每个订单都创建一份快照,存储成本太高。另外商品信息虽然支持修改,但毕竟是一个低频动作。我们可以理解成,大部分订单的商品快照信息都是一样的,除非下单时用户修改过。
如何实时识别修改动作是解决快照成本的关键所在。我们采用摘要比对的方法‍。创建订单时,先检查商品信息摘要是否已经存在,如果不存在,会创建快照记录。订单明细会关联商品的快照主键。

账户余额更新,保证事务
用户支付,我们要从买家账户减掉一定金额,再往卖家增加一定金额,为了保证数据的 完整性、可追溯性, 变更余额时,我们通常会同时插入一条 记录流水。

账户流水核心字段: 流水ID、金额、交易双方账户、交易时间戳、订单号。
账户流水只能新增,不能修改和删除。流水号必须是自增的。
后续,系统对账时,我们只需要对交易流水明细数据做累计即可,如果出现和余额不一致情况,一般以交易流水为准来修复余额数据。
更新余额、记录流水 虽属于两个操作,但是要保证要么都成功,要么都失败。要做到事务。
当然,如果涉及多个微服务调用,会用到 分布式事务。
分布式事务,细想下也很容易理解,就是 将一个大事务拆分为多个本地事务, 本地事务依然借助于数据库自身事务来解决,难点在于解决这个分布式一致性问题,借助重试机制,保证最终一致是我们常用的方案。

常见问题3: 并发更新的ABA问题 (订单表的version)

场景:
商家发货,填写运单号,开始填了 123,后来发现填错了,然后又修改为 456。此时,如果就为某种特殊场景埋下错误伏笔,具体我们来看下,过程如下:
开始「请求A」发货,调订单服务接口,更新运单号 123,但是响应有点慢,超时了;
此时,商家发现运单号填错了,发起了「请求B」,更新运单号为 456 ,订单服务也响应成功了;
这时,「请求A」触发了重试,再次调用订单服务,更新运单号 123,订单服务也响应成功了;订单服务最后保存的 运单号 是 123。

是不是犯错了!!!!,那么有什么好的解决方案吗?
数据库表引入一个额外字段 version ,每次更新时,判断表中的版本号与请求参数携带的版本号是否一致。这个版本字段可以是时间戳
复制
update order
set logistics_num = #{logistics_num} , version = #{version} + 1
where order_id= 1111 and version = #{version}

秒杀系统中的库存管理和订单蓄洪

常见的库存扣减方式有:
下单减库存: 即当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精确的一种,但是有些人下完单可能并不会付款。
付款减库存: 即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家。但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了。
预扣库存: 这种方式相对复杂一些,买家下单后,库存为其保留一定的时间(如 30 分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。在买家付款前,系统会校验该订单的库存是否还有保留:如果没有保留,则再次尝试预扣;
方案一:数据库乐观锁扣减库存
通常在扣减库存的场景下使用行级锁,通过数据库引擎本身对记录加锁的控制,保证数据库的更新的安全性,并且通过where语句的条件,保证库存不会被减到 0 以下,也就是能够有效的控制超卖的场景。
先查库存
然后乐观锁更新:update … set amount = amount - 1 where id = $id and amount = x
设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时 SQL 语句会报错
方案二:redis 扣减库存,异步同步到DB
redis 原子操作扣减库存
异步通过MQ消息同步到DB

购物车模块的实现和优化

技术设计并不是特别复杂,存储的信息也相对有限(用户id、商品id、sku_id、数量、添加时间)。这里特别拿出来单讲主要是用户体验层面要注意几个问题:
添加购物车时,后端校验用户未登录,常规思路,引导用户跳转登录页,待登录成功后,再添加购物车。多了一步操作,给用户一种强迫的感觉,体验会比较差。有没有更好的方式?
如果细心体验京东、淘宝等大平台,你会发现即使未登录态也可以添加购物车,这到底是怎么实现的?
细细琢磨其实原理并不复杂,服务端这边在用户登录态校验时,做了分支路由,当用户未登录时,会创建一个临时Token,作为用户的唯一标识,购物车数据挂载在该Token下,为了避免购物车数据相互影响以及设计的复杂度,这里会有一个临时购物车表。
当然,临时购物车表的数据量并不会太大,why?用户不会一直闲着添加购物车玩,当用户登录后,查看自己的购物车,服务端会从请求的cookie里查找购物车Token标识,并查询临时购物车表是否有数据,然后合并到正式购物车表里。
临时购物车是不是一定要在服务端存储?未必。
有架构师倾向前置存储,将数据存储在浏览器或者 APP LocalStorage, 这部分数据毕竟不是共享的,但是不太好的增加了设计的复杂度。

客户端需要借助本地数据索引,远程请求查完整信息;
如果是登录态,还要增加数据合并逻辑;
考虑到这两部分数据只是用户标识的差异性,所以作者还是建议统一存到服务端,日后即使业务逻辑变更,只需要改一处就可以了,毕竟自运营系统,良好的可维护性也需要我们非常关注的。

购物车是电商系统的标配功能,暂存用户想要购买的商品。

  • 分为添加商品、列表查看、结算下单三个动作。
  • 用户未登录时,将数据存储在浏览器或者 APP LocalStorage。登录后写入后端
  • 后端使用DB,为了性能考虑可以结合redis和DB联合存储
  • 存redis定期同步到DB
  • 前后端联合存储

系统中的分布式ID是怎么生成的

item ID 自增。(100w级别)
order id. 时间戳 + 机器ID + uid % 100 + sequence
DP唯一ID生成调研说明
request 生成方法:时间戳 + 机器mac地址 + sequence

系统稳定性建设

Google 理论:怎样的系统算是稳定高可用的

Google SRE中(SRE三部曲[1])有一个层级模型来描述系统可靠性基础和高层次需求(Dickerson’s Hierarchy of Service Reliability),如下图:


该模型由Google SRE工程师Mikey Dickerson在2013年提出,将系统稳定性需求按照基础程度进行了不同层次的体系化区分,形成稳定性标准金字塔模型:

  1. 金字塔的底座是监控(Monitoring),这是一个系统对于稳定性最基础的要求,缺少监控的系统,如同蒙上眼睛狂奔的野马,无从谈及可控性,更遑论稳定性。

  2. 更上层是应急响应(Incident Response),从一个问题被监控发现到最终解决,这期间的耗时直接取决于应急响应机制的成熟度。合理的应急策略能保证当故障发生时,所有问题能得到有序且妥善的处理,而不是慌乱成一锅粥。

  3. 研发流程规范

  • 测试和发布管控(Testing&Release procedures),大大小小的应用都离不开不断的变更与发布,有效的测试与发布策略能保障系统所有新增变量都处于可控稳定区间内,从而达到整体服务终态稳定
  • 事后总结以及根因分析(Postmortem&Root Caue Analysis),即我们平时谈到的”复盘”,虽然很多人都不太喜欢这项活动,但是不得不承认这是避免我们下次犯同样错误的最有效手段,只有当摸清故障的根因以及对应的缺陷,我们才能对症下药,合理进行规避
  1. 容量规划(Capacity Planning)则是针对于这方面变化进行的保障策略。现有系统体量是否足够支撑新的流量需求,整体链路上是否存在不对等的薄弱节点,都是容量规划需要考虑的问题。

  2. 高可用性产品设计(Product)与软件研发(Development),即通过优秀的产品设计与软件设计使系统具备更高的可靠性,构建高可用产品架构体系,从而提升用户体验

  3. 除此之外,还需要系统资损防控等

下面也会从六个个方面分别介绍


可观测性设计

监控指标 - 完备性


监控类型 监控指标
业务监控 - 订单量、GMV、转化率等业务KPI
- 用户活跃度、留存率等用户指标
- 支付成功率、退款率等交易指标
- 下单失败率
- 支付超时率
- 库存不足率
应用监控 - 接口响应时间(RT)
- 接口调用量(QPS)
- 接口成功率
- 应用JVM指标
- 线程池使用情况
- 应用日志监控
系统监控 - 服务器CPU/内存/磁盘使用率
- 网络带宽使用率
- 系统负载
- 容器资源使用情况
- 网关流量/延迟/错误率
外部依赖 - RPC调用成功率/延迟
- 数据库连接池状态/慢查询
- 缓存命中率/延迟
- 消息队列积压量/消费延迟
- 第三方服务调用成功率/超时率
- 分布式锁获取成功率
- 分布式事务状态
- CDN服务质量
- 对象存储可用性
其它 - 安全相关指标(登录失败/异常IP等)
- 业务告警统计/处理率
- 系统变更记录/回滚率
- 运维操作审计
- 数据备份状态
- 证书过期监控
- 配置变更记录
- 资源成本监控
- 服务SLA达标率

告警 - 实时性和有效性

是不是每项监控都需要告警?答案当然是否定的。建议优先设置Biz层告警,因为Biz层我们对外服务最直观业务表现,最贴切用户感受。Application&System层指标主要用于监控,部分关键&高风险指标可设置告警,用于问题排查定位以及故障提前发现。对于一项告警,我们一般需要关注级别、阈值、通知人等几个点。

  1. 级别
    即当前告警被触发时,问题的严重程度,一般来说有几个衡量点:
  • 是否关联NOC.影响业务指标,事故级别
  • 是否影响核心链路,应用指标异常
  • 是否产生资损
  1. 阈值
  • 即一项告警的触发条件&时间,需根据具体场景合理制定。一般遵循以下原则:
  • 不可过于迟钝。一个合理的监控体系中,任何异常发生后都应触发相关告警。
  • 不可过于敏感。过于敏感的阈值会造成频繁告警,从而导致响应人员疲劳应对,无法筛选真实异常。若一个告警频繁出现,一般是两个原因:系统设计不合理 or 阈值设置不合理。
  • 若单一指标无法反馈覆盖整体业务场景,可结合多项指标关联构建。
  • 需符合业务波动曲线,不同时段可设置不同条件&通知策略。
  • 定期review 告警指标合理性
  1. 通知人&方式
  • 若为业务指标异常(Biz层告警),通知人应为问题处理人员(开发、运维同学)与业务关注人员(TL、业务同学)的集合,通知方式较为实时,比如电话通知。
  • 若为应用 & 系统层告警,主要用于定位异常原因,通知人设置问题排查处理人员即可,通知方式可考虑钉钉、短信等低干扰方式。
  • 除了关联层次,对于不同级别的告警,通知人范围也可适当扩大,尤其是关联GOC故障的告警指标,应适当放宽范围,通知方式也应更为实时直接

日志体系

  • 日志收集
  • 日志分析
  • 日志存储
  • 日志检索

追踪体系

  • 分布式追踪
  • 性能分析
  • 异常追踪
  • 依赖分析

异常应急策略

常用的应急策略

  • 降级策略
  • 限流策略
  • 熔断策略
  • 超时配置化(context 超时配置和传递)
  • 补偿策略
  • 其它业务策略:
    • 商品暂停销售
    • 营销券暂停发送等

降级策略

策略类型 具体场景 实现方式 应用案例 配置建议
降级策略 服务降级 降级开关 推荐服务返回本地缓存 开关粒度: 接口级
降级时长: 5min
功能降级 业务降级 搜索服务简化召回逻辑 降级优先级配置
降级比例配置

限流策略

策略类型 具体场景 实现方式 应用案例 配置建议
限流策略 单机接口限流(粗略) 令牌桶/漏桶算法 下单接口限制QPS为1000 QPS: 1000
突发流量: 1200
分布式限流(全局限流) Redis + Lua脚本 秒杀接口全局限限流 \n 用户维度限流 全局QPS: 5000
单机QPS: 1000,时间窗口: 1min
最大请求数: 5

熔断策略

策略类型 具体场景 实现方式 应用案例 配置建议
熔断策略 服务调用熔断 Hystrix-go 外部支付服务调用保护 错误阈值: 50%
最小请求数: 20
超时时间: 1s
中间件数据库、缓存熔断 Circuit Breaker 数据库访问保护,缓存访问保护 错误阈值: 30%
恢复时间: 5s

补偿策略

策略类型 具体场景 实现方式 应用案例 配置建议
补偿机制 状态机补偿 定时任务 订单状态异常修复 执行间隔: 5min
重试次数: 3
数据补偿 人工触发 库存数据不一致修复 补偿任务可回滚
补偿日志记录
业务补偿 消息队列 支付结果异步通知 重试策略配置
补偿通知方式
降级补偿 降级恢复 服务恢复后数据同步 补偿优先级
补偿超时时间

组合策略

组合策略应用场景

业务场景 组合策略 实现方式 配置建议
秒杀系统 限流 + 熔断 + 降级 1. 入口限流
2. 服务熔断
3. 降级返回
- 限流: 5000 QPS
- 熔断: 50% 错误率
- 降级: 返回售罄
订单系统 限流 + 补偿 + 熔断 1. 用户限流
2. 状态补偿
3. DB熔断
- 用户限流: 5次/分钟
- 补偿间隔: 1分钟
- DB超时: 1秒
支付系统 熔断 + 降级 + 补偿 1. 支付熔断
2. 降级支付
3. 异步补偿
- 熔断阈值: 30%
- 降级通道: 备用
- 补偿周期: 实时
库存系统 限流 + 熔断 + 补偿 1. 接口限流
2. 缓存熔断
3. 数据补偿
- QPS限制: 1000
- 熔断恢复: 5s
- 补偿策略: 定时

其它业务策略:

  1. 商品暂停销售
  2. 营销券暂停发送等

业务和容量规划

业务策略

不同于高可用系统建设体系,大促稳定性保障体系与面向特定业务活动的针对性保障建设,因此,业务策略与数据是我们进行保障前不可或缺的数据。
一般大促业务数据可分为两类,全局业务形态评估以及应急策略&玩法。

流量模型评估

  1. 入口流量
    对于一次大促,系统峰值入口流量一般由常规业务流量与非常规增量(比如容灾预案&业务营销策略变化带来的流量模型配比变化)叠加拟合而成。
  • 常规业务流量一般有两类计算方式:
    • 历史流量算法:该类算法假设当年大促增幅完全符合历史流量模型,根据当前&历年日常流量,计算整体业务体量同比增量模型;然后根据历年大促-日常对比,计算预估流量环比增量模型;最后二者拟合得到最终评估数据。
    • 由于计算时无需依赖任何业务信息输入,该类算法可用于保障工作初期业务尚未给出业务总量评估时使用,得到初估业务流量。
    • 业务量-流量转化算法(GMV\DAU\订单量):该类算法一般以业务预估总量(GMV\DAU\订单量)为输入,根据历史大促&日常业务量-流量转化模型(比如经典漏洞模型)换算得到对应子域业务体量评估。- 该种方式强依赖业务总量预估,可在保障工作中后期使用,在初估业务流量基础上纳入业务评估因素考虑。
  • 非常规增量一般指前台业务营销策略变更或系统应急预案执行后流量模型变化造成的增量流量。例如,NA61机房故障时,流量100%切换到NA62后,带来的增量变化.考虑到成本最小化,非常规增量P计算时一般无需与常规业务流量W一起,全量纳入叠加入口流量K,一般会将非常规策略发生概率λ作为权重
  1. 节点流量
    节点流量由入口流量根据流量分支模型,按比例转化而来。分支流量模型以系统链路为计算基础,遵循以下原则:
  • 同一入口,不同链路占比流量独立计算。
  • 针对同一链路上同一节点,若存在多次调用,需计算按倍数同比放大(比如DB\Tair等)。
  • DB写流量重点关注,可能出现热点造成DB HANG死。

容量转化

节点容量是指一个节点在运行过程中,能够同时处理的最大请求数。它反映了系统的瞬时负载能力。

1)Little Law衍生法则
不同类型资源节点(应用容器、Tair、DB、HBASE等)流量-容量转化比各不相同,但都服从Little Law衍生法则,即:
节点容量=节点吞吐率×平均响应时间
2)N + X 冗余原则

在满足目标流量所需要的最小容量基础上,冗余保留X单位冗余能力
X与目标成本与资源节点故障概率成正相关,不可用概率越高,X越高
对于一般应用容器集群,可考虑X = 0.2N

全链路压测

成本优化

研发流程规范

  • 研发
  • 测试
  • 发布

高可用的架构设计

整体架构

架构层面 设计类型 具体措施 说明
整体架构和部署 冗余设计 多机房部署 在不同地理位置部署多个机房,避免单点故障
服务多副本 每个服务部署多个实例,提供故障转移能力
数据多副本 数据多副本存储,保证数据可靠性
无状态服务设计 服务无状态化,便于水平扩展
容灾设计 同城双活 同一城市两个机房同时对外服务
异地多活 多地机房同时对外服务,就近访问
机房级容灾 单机房故障时可切换到其他机房
数据中心容灾 数据中心级别的容灾能力
灾备演练 演练计划 定期制定灾备演练计划
演练流程 标准化的演练流程和checklist
效果评估 对演练结果进行评估和复盘
持续改进 根据演练反馈持续优化容灾方案
安全架构 网络安全 防火墙/WAF 防御网络攻击,过滤恶意流量
VPN/专线 安全的网络连接方式
数据安全 加密存储 敏感数据加密存储
访问控制 严格的数据访问权限控制
应用安全 身份认证 多因素认证,防止账号盗用
操作审计 记录关键操作审计日志

应用层架构

设计类型 具体措施 说明
隔离设计 服务隔离 核心服务独立部署,避免互相影响
数据隔离 按业务领域划分数据存储
故障隔离 故障隔离机制,防止故障扩散
资源隔离 CPU/内存等资源隔离管理
水平扩展设计 服务无状态化 服务设计无状态,便于扩容
数据分片策略 数据分片存储,支持横向扩展
弹性伸缩 根据负载自动扩缩容
负载均衡 多实例间的负载分配策略

系统链路梳理和维护 System & Biz Profiling

系统链路梳理是所有保障工作的基础,它就像对整个应用系统进行一次全面体检。从流量入口开始,按照链路轨迹,逐级分层节点,最终得到系统全局画像与核心保障点。主要包含以下几个方面:

  1. 入口梳理盘点
    系统通常有多个流量入口,包括HTTP、RPC、消息等多种来源。建议优先从以下三类重点入口开始梳理:
  • 核心重保流量入口
    • 有明确的服务SLI承诺,对数据准确性、响应时间、可靠度有严格要求
    • 业务核心链路(浏览、下单、支付、履约等)
    • 面向企业级用户的服务
  • 资损风险入口
    • 涉及公司或客户资金收入的收费服务
    • 可能造成资金损失的关键节点
  • 大流量入口
    • 系统TPS/QPS排名前5-10的入口
    • 虽无高SLI要求但流量大,对系统整体负载影响显著
  1. 节点分层分析
    按照流量轨迹,对链路上的各类节点(HSF、DB、Tair、HBase等外部依赖)进行分层分析:
  • 强弱依赖判定

    • 业务强依赖:节点不可用会导致业务中断或严重受损
    • 系统强依赖:节点不可用会导致系统执行中断
    • 性能强依赖:节点不可用会严重影响系统性能
    • 弱依赖:有降级方案或影响较小
  • 可用性风险判定

    • 日常超时频发的节点
    • 系统资源紧张的节点
    • 历史故障频发的节点
  • 高风险节点识别

    • 近期改造过的节点
    • 首次参与大促的新节点
    • 曾出现过高级别故障的节点
    • 可能造成资损的关键节点
  1. 调用关系分析
  • 绘制核心接口调用拓扑图或时序图
  • 计算各节点间调用比例
  • 分析调用链路上的资损风险点
  • 识别内外部系统依赖关系
  1. 系统调用拓扑图(可借助分布式链路追踪系统),包含QPS、强弱依赖、高风险节点标注

  2. 接口时序图(以创建订单为例)

资损防控架构

定期review资损风险
事中及时发现


【得物技术】浅谈资损防控

事后复盘和知识沉淀

性能优化


  1. 应用层优化

    • 代码优化
    • JVM调优
    • 线程池优化
    • 连接池优化
  2. 数据层优化

    • SQL优化
    • 索引优化
    • 分库分表
    • 读写分离
  3. 缓存优化

    • 多级缓存
    • 热点缓存
    • 缓存预热
    • 缓存穿透/击穿防护

其它常见问题

基础概念与架构设计

  • 电商后台系统的核心架构设计原则有哪些?
  • 电商后台系统与前端系统的交互方式有哪些?各自的特点是什么?
  • 如何设计电商后台系统的用户权限管理模块?
  • 电商后台系统中,微服务架构和单体架构的适用场景分别是什么?
  • 简述电商后台系统的分层架构设计,各层的主要职责是什么?
  • 如何实现电商后台系统的接口幂等性?
  • 电商后台系统中,分布式 Session 管理有哪些常见方案?
  • 设计电商后台系统时,如何考虑系统的可扩展性和可维护性?
  • 电商后台系统的 API 设计规范应包含哪些内容?
  • 如何设计电商后台系统的异常处理机制?

商品管理

  • 什么是SPU和SKU?它们之间的关系是什么?

  • 电商系统中的商品分类体系是如何设计的? category 父子类目

  • 什么是商品属性?如何区分规格属性(Sales Attributes))和非规格属性?属性,会影响商品SKU的属性直接关系到库存和价格,用户购买时需要选择的属性,例如:颜色、尺码、内存容量等。非规格属性(Basic Attributes):用于描述商品特征,产地、材质、生产日期

  • 商品的生命周期包含哪些状态?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    创建阶段
    DRAFT(0): 草稿状态
    PENDING_AUDIT(1): 待审核
    AUDIT_REJECTED(2): 审核拒绝
    AUDIT_APPROVED(3): 审核通过
    销售阶段
    ON_SHELF(10): 在售/上架
    OFF_SHELF(11): 下架
    SOLD_OUT(12): 售罄
    特殊状态
    FROZEN(20): 冻结(违规/投诉)
    DELETED(99): 删除

    什么是商品快照?为什么需要商品快照?

  • 商品的 SKU 和 SPU 概念在后台系统中如何体现?两者的关系是怎样的?

  • 电商后台系统中商品的基础信息包括哪些?如何设计商品表的数据库模型?

  • 商品的库存管理和价格管理在后台系统中是如何关联的?

  • 如何处理商品的多规格(如颜色、尺寸、型号等)信息?数据库表结构如何设计?

  • 商品详情页的信息(如描述、图片、参数)在后台系统中如何存储和管理?

  • 商品上下架的逻辑在后台系统中是如何实现的?需要考虑哪些因素(如库存、审核状态等)?

  • 商品的搜索和筛选功能在后台系统中是如何实现的?涉及哪些技术(如全文搜索、数据库索引等)?

  • 新品发布和商品淘汰在后台系统中的处理流程是怎样的?

  • 如何保证商品信息的唯一性和完整性,避免重复录入和数据错误?

  1. 唯一性保证:
    使用唯一索引
    引入商品编码系统
    查重机制
  2. 完整性保证:
    必填字段验证
    数据格式校验
    关联完整性检查
    业务规则校验

订单管理

  • 电商后台系统中订单的主要状态有哪些?状态流转的触发条件和处理逻辑是怎样的?
  • 订单的创建流程在后台系统中是如何处理的?涉及哪些模块(如库存、价格、用户信息等)的交互?
  • 如何实现订单的分单处理(如不同仓库发货、不同店铺订单拆分)?
  • 订单的支付状态如何与支付系统进行同步?后台系统需要处理哪些异常情况?
  • 订单的取消、修改(如收货地址、商品数量)在后台系统中有哪些限制和处理逻辑?
  • 如何计算订单的总价(包括商品价格、运费、优惠活动等)?优惠分摊的逻辑是怎样的?
  • 订单的物流信息在后台系统中如何获取和更新?与物流服务商的接口如何对接?
  • 历史订单的存储和查询在后台系统中如何优化?涉及大量数据时如何提高查询效率?
  • 如何设计订单的反欺诈机制,识别和防范恶意订单?
  • 订单的售后服务(如退货、换货、退款)在后台系统中的处理流程是怎样的?与库存、财务等模块如何交互?

用户与账户管理

  • 电商后台系统中用户信息通常包含哪些字段?如何设计用户表的数据库结构?
  • 如何实现用户的注册、登录(包括第三方登录)功能在后台系统中的处理逻辑?
  • 怎样处理用户密码的加密存储和找回功能?
  • 电商后台如何管理用户的收货地址?地址数据的增删改查逻辑是怎样的?
  • 用户账户余额和积分的管理在后台系统中有哪些注意事项?如何保证数据的一致性?
  • 如何实现用户权限的分级管理(如普通用户、VIP 用户、管理员等)?
  • 当用户账户出现异常登录时,后台系统应如何处理和记录?
  • 电商后台如何统计用户的活跃度、留存率等指标?数据来源和计算逻辑是怎样的?
  • 用户信息修改(如手机号、邮箱)时,后台系统需要进行哪些验证和处理?
  • 如何设计用户操作日志的记录和查询功能,以满足审计和问题排查需求?

库存管理

  • 电商后台系统中库存管理的主要目标是什么?常见的库存管理策略有哪些?
  • 如何实现库存的实时更新?在高并发场景下如何保证库存数据的一致性?
  • 库存预警机制如何设计?预警的条件(如安全库存、滞销库存等)和通知方式是怎样的?
  • 多仓库库存管理在后台系统中如何实现?库存的分配和调拨逻辑是怎样的?
  • 库存盘点功能在后台系统中的实现步骤是怎样的?如何处理盘点差异?
  • 预售商品的库存管理与普通商品有何不同?后台系统需要特殊处理哪些方面?
  • 如何防止超卖现象的发生?在库存不足时,订单的处理逻辑是怎样的?
  • 库存数据与订单、采购、物流等模块的交互接口是如何设计的?
  • 对于虚拟商品(如电子卡券),库存管理的方式与实物商品有何区别?
  • 如何统计库存的周转率、缺货率等指标?数据来源和计算方法是怎样的?

支付与结算

  • 电商后台系统支持哪些支付方式?每种支付方式的对接流程和注意事项是什么?
  • 支付系统与电商后台系统的交互接口应包含哪些关键信息?如何保证支付数据的安全性?
  • 支付过程中的异步通知机制是如何实现的?后台系统如何处理重复通知和通知失败的情况?
  • 如何实现支付订单与业务订单的关联和对账功能?
  • 结算周期和结算规则在后台系统中如何配置和管理?(如供应商结算、平台佣金结算等)
  • 支付过程中的手续费计算和分摊逻辑是怎样的?如何在后台系统中实现?
  • 对于跨境支付,后台系统需要处理哪些特殊问题(如汇率转换、支付合规性等)?
  • 如何设计支付系统的异常处理和回滚机制?
  • 支付成功后,后台系统如何触发后续的业务流程(如订单发货、积分发放等)?
  • 财务对账在后台系统中的实现方式有哪些?如何保证财务数据与业务数据的一致性?

物流与供应链

  • 电商后台系统如何与物流服务商(如快递、仓储)进行接口对接?需要获取哪些物流信息?
  • 物流单号的生成和管理在后台系统中是如何实现的?如何避免重复和错误?
  • 发货流程在后台系统中的处理逻辑是怎样的?涉及哪些部门或系统的协作(如仓库、库存、订单等)?
  • 如何实现物流信息的实时追踪和更新?在后台系统中如何展示给用户和客服?
  • 退换货的物流处理在后台系统中有哪些特殊流程?如何与原订单和库存进行关联?
  • 供应链管理在电商后台系统中包括哪些主要功能?如何实现供应商管理、采购管理和库存管理的协同?
  • 如何根据商品的特性和用户地址选择合适的物流方案(如快递类型、运费模板等)?
  • 物流异常(如包裹丢失、破损)在后台系统中的处理流程是怎样的?如何与用户和物流服务商沟通协调?
  • 如何统计物流成本和物流效率(如发货时效、配送成功率等)?数据来源和分析方法是怎样的?
  • 对于海外仓和跨境物流,后台系统需要处理哪些额外的业务逻辑(如清关、关税计算等)?

营销与促销

  • 电商后台系统中常见的促销策略有哪些(如满减、打折、优惠券、秒杀、拼团等)?如何设计支持多种促销策略的模块?
  • 优惠券的生成、发放、使用和核销在后台系统中的处理流程是怎样的?
  • 促销活动的时间管理和范围管理(如针对特定用户群体、特定商品、特定时间段)如何实现?
  • 如何避免促销活动中的超卖和优惠叠加错误?后台系统的校验逻辑是怎样的?
  • 秒杀活动在后台系统中如何应对高并发场景?需要进行哪些技术优化?
  • 营销活动的效果评估指标(如转化率、客单价提升、销售额增长等)在后台系统中如何统计和分析?
  • 如何设计推荐系统与后台营销模块的集成,实现个性化的促销推荐?
  • 会员体系(如 VIP 等级、积分兑换)在后台系统中如何与营销活动结合?
  • 促销活动的库存预留和释放逻辑是怎样的?如何与库存管理模块进行交互?
  • 营销费用的预算管理和成本核算在后台系统中如何实现?

数据统计与分析

  • 电商后台系统需要统计哪些核心业务指标(如 GMV、UV、PV、转化率、复购率等)?数据采集的方式和频率是怎样的?
  • 如何设计数据报表功能,支持不同角色(如运营、管理层、客服)的个性化报表需求?
  • 数据统计中的维度和指标如何定义和管理?如何实现多维度的交叉分析?
  • 实时数据统计和离线数据统计在后台系统中的实现方式有何不同?各自的适用场景是什么?
  • 如何保证数据统计的准确性和完整性?数据清洗和校验的流程是怎样的?
  • 数据分析结果如何反馈到业务模块(如库存调整、促销策略优化等)?
  • 数据可视化在后台系统中的实现方式有哪些(如图表、仪表盘、数据大屏等)?
  • 对于海量数据的统计分析,后台系统需要进行哪些性能优化(如分布式计算、缓存、索引等)?
  • 如何设计数据权限管理,确保不同用户只能查看和操作其权限范围内的数据?
  • 数据统计分析模块与其他业务模块(如订单、商品、用户等)的数据接口是如何设计的?

其他综合问题

  • 电商后台系统在应对大促(如双 11、618)时,需要进行哪些准备工作和技术优化?
  • 如何保障电商后台系统的高可用性和容灾能力?常见的解决方案有哪些?
  • 后台系统的代码维护和版本管理有哪些最佳实践?如何保证多人协作开发的效率和代码质量?
  • 当电商业务拓展到新的领域或增加新的业务模块时,后台系统如何进行适应性改造?
  • 如何处理不同国家和地区的电商业务在后台系统中的差异化需求(如语言、货币、法规等)?
  • 电商后台系统中的日志管理有哪些重要性?如何设计日志的记录、存储和查询功能?
  • 对于第三方服务(如短信验证码、邮件通知、数据分析工具等)的接入,后台系统需要注意哪些问题?
  • 如何评估电商后台系统的性能瓶颈?常见的性能测试工具和方法有哪些?
  • 简述你在以往项目中参与过的电商后台系统开发经验,遇到过哪些挑战,是如何解决的?
  • 对于电商后台系统的未来发展趋势(如智能化、自动化、区块链应用等),你有哪些了解和思考?

参考: