SET化架构:从单元化原理到大规模落地实践
SET化架构:从单元化原理到大规模落地实践
当系统规模突破单机房、单集群的承载极限,当一次机房故障就可能导致全站不可用时,SET 化架构就成为了必然选择。它不是一种特定的技术方案,而是一种将系统划分为独立自治单元,实现水平扩展和故障隔离的架构思想。
互联网业务的高速增长给架构带来了两个根本性挑战:容量的天花板和可用性的脆弱性。传统的垂直扩展(Scale-up)终有极限,而简单的水平扩展(Scale-out)在数据一致性、服务依赖、运维复杂度等方面又面临诸多困难。
SET 化架构(也称为单元化架构、Cell-based Architecture)正是为了系统性地解决这些问题而诞生的。本文将从原理到实践,全面解析 SET 化架构的设计与落地。
什么是 SET 化架构?
概念定义
SET(Scalable Elastic Topology,可扩展弹性拓扑)化架构是一种将系统按照某个维度(通常是用户 ID)划分为多个独立、自包含的部署单元的架构模式。每个 SET 都是一个"小型完整系统",拥有独立的应用服务、缓存、数据库等全套基础设施,能够独立处理分配给它的流量。
SET 化的核心思想:
传统架构: 所有用户 → 一套系统
(纵向扩展,存在单点瓶颈)
SET 化架构: 用户按规则分组 → 每组对应一个 SET
SET-1: 用户 0~999W → 独立的一套完整系统
SET-2: 用户 1000W~1999W → 独立的一套完整系统
SET-3: 用户 2000W~2999W → 独立的一套完整系统
(水平扩展,理论上无上限)
SET 的核心特征
| 特征 | 说明 |
|---|---|
| 自包含 | 每个 SET 拥有完整的服务栈(应用、缓存、DB),能独立处理请求 |
| 对等部署 | 所有 SET 的架构相同,只是处理的数据分片不同 |
| 故障隔离 | 单个 SET 的故障不会影响其他 SET |
| 水平扩展 | 通过增加 SET 数量实现容量扩展 |
| 流量可调度 | 通过路由规则灵活调度流量在 SET 间的分配 |
SET 化与传统分布式的区别
| 维度 | 传统分布式架构 | SET 化架构 |
|---|---|---|
| 扩展方式 | 各层独立扩展(加应用节点、加 DB 从库) | 整体作为一个单元扩展 |
| 故障影响 | 某一层故障影响全局 | 故障隔离在单个 SET 内 |
| 数据分片 | 数据库层分片,应用层无感知 | 从入口到数据库全链路分片 |
| 部署单元 | 按服务部署 | 按 SET(单元)部署 |
| 容量规划 | 各组件独立评估 | 按 SET 整体评估 |
SET 化架构演进历程
SET 化不是一步到位的设计,而是随着业务规模增长逐步演化的结果。
阶段一:单体架构
用户 → 应用服务器 → 数据库
所有功能在一个应用中,单库单表。适用于初创期,简单高效。
瓶颈:单机容量有限,数据库成为瓶颈。
阶段二:读写分离 + 缓存
用户 → 应用集群 → 缓存 → 主库(写)/ 从库(读)
通过读写分离缓解数据库压力,引入缓存降低 DB 负载。
瓶颈:写入瓶颈无法解决,主库仍是单点。
阶段三:分库分表
用户 → 应用集群 → 数据库中间件 → DB 分片 1 / DB 分片 2 / DB 分片 N
数据库水平拆分,解决写入瓶颈。但分片逻辑散落在各处,跨分片查询复杂。
瓶颈:应用层无分片感知,缓存与 DB 分片不对齐,运维复杂。
阶段四:服务化(微服务)
用户 → API 网关 → 微服务 A / 微服务 B / ... → 各自的 DB
按业务域拆分为独立服务,各服务独立部署和扩展。
瓶颈:服务间调用复杂,全链路缺乏统一的分片和隔离机制。
阶段五:SET 化(单元化)
用户 → 统一路由层 → SET-1(完整服务栈)/ SET-2 / SET-N
↕ 数据同步
全链路按统一维度分片,每个 SET 自包含完整服务栈,实现真正的水平扩展和故障隔离。
这就是 SET 化架构的终态。 下面详细介绍每个核心组件的设计。
核心设计一:流量路由
流量路由是 SET 化架构的"大脑",它决定了每个请求应该被路由到哪个 SET。
路由键的选择
路由键(Sharding Key)是 SET 化的核心决策之一,选择不当会导致严重的跨 SET 调用问题。
| 路由键 | 优点 | 缺点 | 适用业务 |
|---|---|---|---|
| 用户 ID | 用户维度天然隔离,覆盖面广 | 用户间交互需跨 SET | 电商、社交、O2O |
| 商户 ID | 商户维度隔离 | 用户下单需跨 SET | B 端平台 |
| 地理区域 | 天然的流量隔离 | 跨区域业务需特殊处理 | 本地生活、物流 |
| 订单 ID | 订单维度隔离 | 需要提前生成带路由信息的 ID | 交易系统 |
实践经验:绝大多数 C 端业务选择用户 ID 作为路由键,因为用户是最核心的业务实体,以用户为维度分片可以最大程度地减少跨 SET 调用。
路由架构设计
SET 化的路由通常分为三层:
第一层:接入路由(DNS / LB 层)
在最外层通过 DNS 或负载均衡器将流量分配到对应的 SET。
用户请求 → DNS 解析 → 全局负载均衡(GSLB)
↓
根据用户 ID 哈希路由
↓ ↓ ↓
SET-1 LB SET-2 LB SET-3 LB
第二层:网关路由(API Gateway 层)
API 网关根据请求中的路由键(如 Header、Cookie、Token 中的用户 ID)将请求路由到正确的 SET。
请求 → API Gateway → 提取路由键 → 查询路由表 → 转发到目标 SET
第三层:服务路由(RPC 层)
服务间调用时,RPC 框架自动根据上下文中的路由键将请求路由到同 SET 的服务实例。
Service A (SET-1) → RPC Framework → 自动路由到 → Service B (SET-1)
(通过上下文传递 SET 标识)
路由表设计
路由表是映射用户到 SET 的核心数据结构:
路由表结构:
┌──────────────┬──────────┬──────────┐
│ 分片范围 │ SET ID │ 状态 │
├──────────────┼──────────┼──────────┤
│ 0 ~ 999 │ SET-1 │ Active │
│ 1000 ~ 1999 │ SET-2 │ Active │
│ 2000 ~ 2999 │ SET-3 │ Active │
│ 3000 ~ 3999 │ SET-1 │ Active │ ← 同一个 SET 可承载多个分片
└──────────────┴──────────┴──────────┘
路由策略的关键设计要点:
- 虚拟分片:不直接将用户映射到物理 SET,而是先映射到虚拟分片(如 1024 个),再将虚拟分片映射到物理 SET。这样扩容时只需调整虚拟分片的映射关系
- 路由缓存:路由表在网关和服务端本地缓存,避免每次请求都查询路由服务
- 路由一致性:路由表变更时需要保证全链路一致性,避免请求被路由到错误的 SET
核心设计二:数据分片与同步
数据层是 SET 化最复杂的部分,需要解决数据分片、跨 SET 数据访问、数据同步等问题。
数据分类
SET 化架构中的数据按照与路由键的关系分为三类:
| 数据类型 | 定义 | 存储方式 | 举例 |
|---|---|---|---|
| SET 内数据 | 与路由键强绑定的数据 | 仅存储在对应 SET | 用户订单、用户资产、购物车 |
| 全局数据 | 所有 SET 共享的数据 | 全局存储 + 各 SET 只读副本 | 商品信息、配置数据、类目 |
| 跨 SET 数据 | 涉及多个路由键的数据 | 全局存储或冗余存储 | 商户维度的聚合数据、排行榜 |
SET 内数据
SET 内数据遵循"谁的数据谁存储"原则,每个 SET 只处理和存储自己分片内的数据:
SET-1 数据库:只存储 UserID 0~999 的数据
SET-2 数据库:只存储 UserID 1000~1999 的数据
用户 A (ID=500) 下单 → 请求路由到 SET-1 → 订单写入 SET-1 DB
用户 B (ID=1500) 下单 → 请求路由到 SET-2 → 订单写入 SET-2 DB
全局数据
全局数据(如商品信息)需要所有 SET 都能访问,通常采用以下方案:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 全局服务 | 独立部署的全局服务 + 数据库 | 数据一致性好 | 全局服务成为依赖瓶颈 |
| 数据广播 | 写入全局库后异步同步到各 SET | 本地读取性能好 | 数据有延迟,存储冗余 |
| 缓存分发 | 全局数据写入后推送到各 SET 缓存 | 读取极快 | 缓存一致性需要保障 |
实践建议:高频读取的全局数据(如商品详情)采用"数据广播 + 本地缓存"方案;低频但要求强一致的全局数据(如配置变更)采用"全局服务"方案。
数据同步机制
SET 间的数据同步是保证业务连续性的关键,特别是在故障切换场景下:
主 SET 备 SET
┌──────────┐ ┌──────────┐
│ 应用层 │ │ 应用层 │
│ 缓存层 │ │ 缓存层 │
│ 数据库 │ ── Binlog 同步 ──→ │ 数据库 │
└──────────┘ └──────────┘
同步方式:MySQL Binlog → Canal/DTS → 目标 SET 数据库
同步延迟:通常 < 1s,需要监控告警
数据同步的关键指标:
| 指标 | 目标值 | 监控方式 |
|---|---|---|
| 同步延迟 | < 1 秒 | Binlog 位点差监控 |
| 数据一致性 | 99.99% | 定期全量对账 |
| 同步可用性 | 99.99% | 同步链路健康检查 |
核心设计三:全局服务
有些服务天然不能被 SET 化,它们需要作为全局服务为所有 SET 提供能力。
全局 ID 生成
在 SET 化架构中,ID 生成必须保证全局唯一且带有路由信息:
ID 结构设计:
┌────────────┬──────────┬───────────┬──────────┐
│ 时间戳 │ SET ID │ 机器 ID │ 序列号 │
│ 41 bits │ 5 bits │ 5 bits │ 12 bits │
└────────────┴──────────┴───────────┴──────────┘
总长度:63 bits(Long 类型)
| 生成方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 全局 ID 服务 | 全局唯一性保证最强 | 依赖外部服务,存在可用性风险 | 核心业务(订单、支付) |
| 本地 Snowflake | 无外部依赖,性能最高 | 需要解决时钟回拨问题 | 非核心业务 |
| 号段模式 | 批量获取减少调用 | 号段用尽时有短暂延迟 | 通用场景 |
兜底策略:本地 ID 生成作为兜底方案,当全局 ID 服务不可用时自动降级为本地生成,确保业务不中断。
全局配置中心
配置中心负责管理所有 SET 的路由规则、业务配置和开关:
配置中心架构:
┌─────────────────┐
│ 配置中心集群 │
│ (ZK/Nacos/etcd) │
└────────┬────────┘
↙ ↓ ↘
SET-1 Agent SET-2 Agent SET-3 Agent
↓ ↓ ↓
本地缓存 本地缓存 本地缓存
推送机制:配置变更 → 配置中心 → 推送给各 SET Agent → 更新本地缓存
全局调度中心
负责 SET 的健康监控、故障检测和流量调度:
| 功能 | 说明 |
|---|---|
| 健康检查 | 定期探测各 SET 的健康状态 |
| 故障检测 | 发现 SET 异常时触发告警 |
| 流量切换 | 故障 SET 的流量自动切换到备用 SET |
| 容量管理 | 监控各 SET 的容量使用率 |
| 扩缩容编排 | 新增或下线 SET 时的流量编排 |
核心设计四:故障隔离与切换
故障隔离是 SET 化架构最核心的价值之一。
故障域划分
SET 化架构将故障影响范围从"全站"缩小到"单个 SET":
传统架构故障:
DB 主库宕机 → 全站不可用 → 影响 100% 用户
SET 化架构故障:
SET-2 DB 宕机 → 仅 SET-2 不可用 → 影响约 33% 用户(假设 3 个 SET)
↓ 自动切换
SET-2 流量切换到备用 → 影响时间 < 分钟级
故障切换策略
| 策略 | 切换速度 | 数据风险 | 适用场景 |
|---|---|---|---|
| 主备切换 | 秒级~分钟级 | 可能丢失未同步数据 | SET 内部 DB 主备切换 |
| SET 间切换 | 分钟级 | 依赖数据同步延迟 | 整个 SET 故障 |
| 跨机房切换 | 分钟级~小时级 | 需要全量数据同步 | 机房级故障 |
故障切换流程
正常状态:
用户流量 → 路由层 → SET-2(主)
故障检测:
健康检查失败 → 确认 SET-2 不可用 → 触发切换流程
切换执行:
1. 停止 SET-2 的流量接入(路由层摘除)
2. 等待 SET-2 → SET-2-备 的数据同步完成(或接受部分数据丢失)
3. 更新路由表:SET-2 的分片 → SET-2-备
4. 开放 SET-2-备 的流量接入
5. 验证切换后的业务正确性
恢复状态:
用户流量 → 路由层 → SET-2-备(新主)
容灾等级
| 等级 | 容灾范围 | 实现方式 | RTO |
|---|---|---|---|
| L1 | 单机故障 | 应用集群 + DB 主备 | 秒级 |
| L2 | 机架故障 | 跨机架部署 | 秒级 |
| L3 | 机房故障 | 同城双机房 SET 互备 | 分钟级 |
| L4 | 城市故障 | 异地 SET 互备 | 分钟级~小时级 |
核心设计五:SET 扩缩容
SET 化架构的一个重要优势是可以通过增减 SET 数量来调整系统容量。
扩容流程
扩容场景:当前 3 个 SET 容量不足,需要扩容到 4 个 SET
Step 1: 部署新 SET(SET-4)
- 部署完整的应用服务、缓存、数据库
- 从现有 SET 同步全局数据
Step 2: 数据迁移
- 将 SET-1 的部分虚拟分片的数据迁移到 SET-4
- 采用双写方案保证迁移过程不中断服务
Step 3: 路由切换
- 更新路由表:迁移的虚拟分片指向 SET-4
- 灰度切换流量,逐步验证
Step 4: 清理
- 验证完成后,清理 SET-1 中已迁移的数据
- 回收空闲资源
虚拟分片的价值
虚拟分片是实现平滑扩缩容的关键:
初始状态(3 个 SET,1024 个虚拟分片):
SET-1: 虚拟分片 0~341
SET-2: 虚拟分片 342~682
SET-3: 虚拟分片 683~1023
扩容到 4 个 SET(只需调整虚拟分片映射):
SET-1: 虚拟分片 0~255
SET-2: 虚拟分片 256~511
SET-3: 虚拟分片 512~767
SET-4: 虚拟分片 768~1023
优势:用户 → 虚拟分片的映射不变,只调整虚拟分片 → 物理 SET 的映射
实践案例:电商交易系统 SET 化
以一个典型的电商交易系统为例,展示 SET 化的具体落地方案。
业务分析
| 服务 | 路由键关系 | SET 化策略 |
|---|---|---|
| 用户服务 | 用户 ID(强绑定) | SET 内部署 |
| 订单服务 | 用户 ID(强绑定) | SET 内部署 |
| 支付服务 | 用户 ID(强绑定) | SET 内部署 |
| 商品服务 | 无关(全局数据) | 全局部署 + 数据广播 |
| 库存服务 | 商品维度(跨 SET) | 全局部署 |
| 搜索服务 | 无关(全局数据) | 全局部署 |
| 营销服务 | 活动维度(跨 SET) | 全局部署 |
整体架构
┌──────────────────────────────────┐
│ 统一接入层(GSLB) │
└───────────────┬──────────────────┘
↓
┌──────────────────────────────────┐
│ API Gateway(路由层) │
│ 提取 UserID → 查询路由表 → 转发 │
└──┬──────────────┬────────────┬───┘
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ SET-1 │ │ SET-2 │ │ SET-3 │
│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │
│ │用户服务 │ │ │ │用户服务 │ │ │ │用户服务 │ │
│ │订单服务 │ │ │ │订单服务 │ │ │ │订单服务 │ │
│ │支付服务 │ │ │ │支付服务 │ │ │ │支付服务 │ │
│ │Redis │ │ │ │Redis │ │ │ │Redis │ │
│ │MySQL │ │ │ │MySQL │ │ │ │MySQL │ │
│ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │
└─────────────┘ └─────────────┘ └─────────────┘
↕ ↕ ↕
┌──────────────────────────────────────────┐
│ 全局服务层 │
│ 商品服务 │ 库存服务 │ 搜索服务 │ 营销服务 │
│ 全局 ID 服务 │ 配置中心 │
└──────────────────────────────────────────┘
下单流程的 SET 化处理
用户 A(ID=500)下单购买商品 X:
1. 请求到达 API Gateway
2. Gateway 提取 UserID=500,查路由表 → SET-1
3. 请求转发到 SET-1 的订单服务
4. 订单服务调用全局商品服务查询商品信息
5. 订单服务调用全局库存服务扣减库存
6. 订单服务在 SET-1 本地 DB 创建订单
7. 订单服务调用 SET-1 本地的支付服务发起支付
8. 支付完成后,SET-1 的订单服务更新本地订单状态
关键点:
- 用户维度的数据操作(创建订单、支付)在 SET 内完成,无跨 SET 调用
- 商品、库存等全局数据通过全局服务访问
- RPC 框架自动将 SET 标识通过上下文传递,保证 SET 内调用的正确性
SET 化实施路线
SET 化是一个渐进式的过程,不应该一步到位。
阶段规划
| 阶段 | 目标 | 关键动作 | 周期 |
|---|---|---|---|
| P0:基础设施准备 | 具备 SET 化的基础能力 | 统一 RPC 框架、引入路由组件、改造 ID 生成 | 1~2 月 |
| P1:核心链路 SET 化 | 交易核心链路实现 SET 化 | 订单、支付、用户服务 SET 化部署 | 2~3 月 |
| P2:全链路 SET 化 | 所有服务完成 SET 化改造 | 非核心服务 SET 化、全局服务治理 | 3~6 月 |
| P3:异地 SET | 实现异地多活能力 | 跨机房 SET 部署、数据同步、故障切换 | 3~6 月 |
改造清单
应用层改造:
- 所有服务支持从请求上下文中提取和传递路由键
- RPC 框架支持基于路由键的服务路由
- 消息队列的生产和消费支持 SET 路由
- 定时任务支持按 SET 分片执行
数据层改造:
- 数据库按 SET 进行物理隔离
- 缓存按 SET 进行 namespace 隔离
- 全局数据的同步机制建设
- 数据对账和修复工具
基础设施改造:
- 统一路由服务建设
- 全局 ID 生成服务建设
- 监控体系支持 SET 维度
- 发布系统支持按 SET 灰度
SET 化与异地多活的关系
SET 化架构是异地多活的基础。两者的关系可以这样理解:
SET 化 = 单元化部署 + 流量路由 + 数据分片
异地多活 = SET 化 + 跨地域部署 + 数据同步 + 故障切换
| 维度 | 同城 SET 化 | 异地多活 SET 化 |
|---|---|---|
| 部署范围 | 同城多机房 | 跨城市多机房 |
| 网络延迟 | < 1ms | 10~50ms |
| 数据同步 | 同步/半同步复制 | 异步复制(最终一致性) |
| 故障切换 | 自动秒级切换 | 手动/半自动分钟级切换 |
| 核心挑战 | 路由准确性 | 数据一致性 + 切换决策 |
SET 化架构天然具备"每个 SET 独立自治"的特性,这为异地多活提供了完美的基础。只需将不同的 SET 部署到不同的地域,配合数据同步和流量调度,就能实现异地多活。
常见问题与解决方案
跨 SET 调用问题
问题:部分业务场景不可避免需要跨 SET 访问数据。
解决方案:
| 场景 | 解决方案 |
|---|---|
| 用户查看商户信息 | 商户数据作为全局数据广播 |
| 商户查看所有订单 | 聚合服务从各 SET 并行查询后合并 |
| 全站排行榜 | 各 SET 本地计算后汇总到全局服务 |
| 跨用户转账 | 通过消息队列异步通知目标 SET |
数据迁移问题
问题:扩容时需要在 SET 间迁移数据。
解决方案:双写方案
Phase 1: 新 SET 开始从旧 SET 同步增量数据(Binlog 订阅)
Phase 2: 同步追上后,开启双写模式(新请求同时写入新旧 SET)
Phase 3: 路由切换,新请求全部路由到新 SET
Phase 4: 验证无误后,停止双写,清理旧数据
全局服务瓶颈
问题:全局服务成为所有 SET 的共同依赖,可能成为瓶颈。
解决方案:
- 数据本地化:全局数据尽可能广播到各 SET 本地,减少全局服务调用
- 缓存优先:全局数据走多级缓存,降低对全局 DB 的访问
- 异步化:非实时性要求的全局操作通过消息队列异步处理
- 弹性扩展:全局服务本身也需要集群化部署和弹性扩展
总结
SET 化架构是应对互联网业务规模化增长的系统性解决方案。它的核心思想并不复杂——把一个大系统拆分成多个独立自治的小系统——但真正的挑战在于落地过程中的每一个细节。
回顾 SET 化的关键设计决策:
- 路由键选择决定了架构的天花板。选错路由键会导致大量跨 SET 调用,抵消 SET 化的优势
- 数据分类是 SET 化的基础。明确哪些是 SET 内数据、哪些是全局数据,才能设计合理的数据架构
- 虚拟分片是弹性扩展的关键。不要将用户直接映射到物理 SET,虚拟分片层带来的灵活性至关重要
- 全局服务的治理不能忽视。全局服务是所有 SET 的共同依赖,必须做到高可用和高性能
- 渐进式实施是务实的选择。从核心链路开始,逐步扩展,而不是试图一步到位
SET 化不是目的,而是手段。 它服务于两个根本目标:让系统能够水平扩展以承载业务增长,让故障影响可控以保障用户体验。在实施 SET 化之前,先问自己:当前的业务规模真的需要 SET 化吗?