服务拆分方法论
Category
server / services 子树。节点定位:拆分回答四个硬问题——状态绑定、故障隔离、独立扩缩、团队边界;先按状态画边界,不按菜单画。services 子树总纲。
Definition
服务拆分不是把一个大进程切成若干个小进程,也不是把项目管理里的“模块”直接翻译成“服务”。真正有价值的拆分,回答的是四个更硬的问题:哪些状态必须放在一起推进,哪些故障应该被限制在局部,哪些链路值得独立扩缩容,以及哪些团队需要被边界支持而不是互相卡死。
如果这些问题没有先想清楚,系统拆得越碎,后面要补的东西就越多:服务发现、路由绑定、幂等、补偿、迁移、重连、治理、审计,全都会一起追上来。
Problem
把拆分当切进程或翻译项目管理的「模块」,会得到十四个服务和三倍延迟:真正有价值的拆分由状态推进、故障域、扩缩容与团队协作四个问题驱动;菜单式拆分增加的多、买到的少,成本先涨、收益后置。
项目为什么会走到拆分这一步
很少有项目在第一天就需要复杂的分布式架构。大多数项目是被线上现实一点点推到这一步的:
- 某些玩法开局瞬时流量极高,某些系统却长期低频但必须稳定
- 房间、副本、场景这些运行时状态开始呈现明显不同的负载形态
- 活动、支付、社交、排行等外围能力需要和核心玩法解耦
- 玩家开始跨玩法、跨节点、跨区服流动
- 组织规模扩大,不同团队必须独立发布和治理
- 单点故障影响面太大,已经不能接受“一个模块出问题全站陪跑”
真正推动拆分的不是抽象的“并发高”,而是状态边界、故障域和运维需求开始明显分化。
先围绕状态画边界,不要先围绕菜单画边界
很多团队一上来按功能菜单拆:
- 登录一个服务
- 邮件一个服务
- 活动一个服务
- 商店一个服务
这种切法有时能用,但它并不天然正确。拆分更可靠的起点通常是状态:
- 哪些状态必须在同一个进程里高频协同
- 哪些状态可以容忍异步流转
- 哪些状态是短生命周期的实例态
- 哪些状态是长期资产态
- 哪些状态一旦出错代价极高,必须单独治理
比如房间内战斗状态通常要求高频、低延迟、同域推进,不适合被切成多段远程调用;支付订单、奖励发放、邮件投递则更像长期资产链路,更适合被单独治理;活动规则和活动进度虽然都叫“活动”,也未必应该落在同一条运行时链路里。
边界真正该围绕的,是状态更新方式和失败语义,而不是前端菜单长什么样。
拆分之前先回答四个问题
1. 最希望把故障隔离在哪里
如果房间节点打满了,你希望只是当前房间受影响,而不是登录、支付、社交一起抖;如果活动链路故障了,你希望不要反向拖垮战斗主链路。这类“希望哪里坏、哪里不要跟着坏”的判断,本质上就是故障域划分,也是最真实的拆分信号。
2. 哪些链路值得独立扩缩容
不同服务的流量曲线往往完全不同:
- 匹配和房间随开局高峰起伏
- 活动和商城受运营时点驱动
- 聊天、推送和社交有自己的独立节奏
- 支付和订单链路要求峰值兜底但不常驻高负载
如果这些完全不同的负载长期被绑在一个部署单元里,扩容动作会越来越粗暴,资源浪费和风险都会上升。
3. 哪些状态绝不能跨服务高频抖动
一条链路偶尔跨服务请求一次没什么问题,但如果技能释放、对象同步、位置更新、碰撞结算这类高频状态需要不断跨服务,系统很快会被网络延迟、超时和重试拖垮。拆分的核心不是“尽量多分”,而是避免把本该同域推进的状态硬切开。
4. 哪些边界需要支持独立演进
活动、支付、登录、匹配、房间、世界运维,现实里往往由不同团队维护。如果这些边界根本不支持独立发布、独立扩缩容和独立问题定位,组织规模上来以后,工程成本会持续放大。
常见拆分思路各自适合什么问题
按玩法实例拆
典型例子是匹配服、房间服、副本服、战斗服。这种拆法适合会话边界清晰、局内生命周期明确的项目。它的优点是运行时模型直观,实例可以独立创建、销毁和扩缩容;缺点是容易把注意力都放在局内,忽视局外长期状态和跨玩法迁移。
按世界或场景拆
这种方式常见于 MMO、大地图、生存沙盒或持续在线世界。它更贴近空间边界和长期世界状态,但代价是迁移、跨场景协作、热点治理和控制权交接都会明显变重。
按领域能力拆
账号、支付、社交、邮件、排行、活动、风控都属于这类。它适合治理长期资产和通用能力,优点是边界相对稳定,便于独立治理;缺点是如果和实例态边界没衔接好,很容易让主链路上出现大量跨服务耦合。
实际项目里最常见的不是三选一,而是混合模式:实例态按玩法或场景组织,长期资产和外围能力按领域组织。
服务拆分真正增加了什么成本
拆分不是免费换弹性。你获得的是故障隔离、独立扩缩容和团队解耦,付出的则是下面这些持续成本:
- 服务发现和节点治理
- 路由、绑定、迁移和重连
- 跨服务超时、重试、幂等和补偿
- 链路追踪、日志关联和问题定位
- 配置下发、灰度、版本兼容和部署编排
- 控制平面、健康检查、摘流和恢复机制
如果团队没有准备好承受这些基础设施成本,过早拆分通常只是把问题从代码内部搬到了系统边界上。
一个更实用的判断标准
判断服务该不该拆,通常看下面几件事就够了:
- 单体内部是否已经存在长期稳定的状态边界
- 某个子系统是否值得独立扩缩容
- 某类故障是否必须被局部隔离
- 某个团队是否必须独立发布和治理
- 拆开后高频链路和一致性成本是否还能承受
只要这些问题答不清楚,所谓拆分很可能只是把复杂度重新命名。
更稳妥的拆分顺序
比较可靠的顺序通常是:
- 先按状态与责任画边界。
- 再按故障域和扩缩容需求校正边界。
- 最后再决定这些边界对应什么部署形态、进程数和节点拓扑。
这样做的好处是,系统先围绕现实问题组织,而不是先围绕部署时髦度组织。
常见误区
最常见的错误,是把服务拆分理解成“组织升级”或“技术升级”,而不是状态边界设计。第二类错误,是一边拆服务,一边让高频强一致状态在服务之间来回抖动。第三类错误,是只把业务节点拆开,却不补服务发现、路由、监控、恢复和控制平面,结果只是把单体复杂度换成了分布式混乱。
实战案例:一次「服务拆了十四个,技能慢了三倍」的过度拆分排查
案例为教学示例(非摘自真实项目),数字经过取整简化。它把本页「先围绕状态画边界,不要先围绕菜单画边界」「拆分之前先回答四个问题」「服务拆分真正增加了什么成本」「常见误区」用到深水区:拆分回答的是状态边界问题,不是技术时髦度问题——这是服务拆分的边界排查,也是「所谓拆分很可能只是把复杂度重新命名」的完整展开。
背景与现象:一款带实时战场的竞技项目,25 人团队按微服务模板拆出 14 个服务——登录、匹配、房间、输入、战斗计算、状态广播、结算、邮件、商店、排行、聊天、活动、支付、GM,独立仓库、独立部署。上线后 PVP 对局的技能链路端到端 P99 130 毫秒,而单体期的同链路压测只要 45 毫秒,接近 3 倍;「技能放出去半秒才响」的反馈稳定在日 260 单。更迷惑的是服务治理大盘一片绿:拆得越细,单服务视角越好看,链路视角越没人看。
第一步:现象与口径——先把「慢了三倍」变成可对照的时延账
口径统一:技能链路时延 = 客户端发出技能请求 → 服务端结算回包,不含客户端表现。对照账用同一压测模型、同一机房跑两版:
- 单体版:P99 45 毫秒
- 拆分版:P99 130 毫秒,增量 85 毫秒
增量不是均匀分布的:抓包按技能请求逐条对照,时延集中在「请求在服务间排队」的区间,网络传输本身几乎不占——先排除物理层,再算跳数账。
第二步:分层归因——从网络到跳数的增量账本
物理网络 机房内 RTT 0.3ms、跨可用区 0 次——排除网络质量
序列化 单次 payload 约 2KB、编解码 0.6ms/次——不是包大小与编解码
跳数账 一次技能释放串行 3 跳:输入服务 → 战斗计算服务 → 状态广播服务
每跳排队+调度+序列化 ≈ 28ms,3 跳叠加 ≈ 84ms——增量几乎全在跳数上
状态账 战斗状态被三个服务来回读写:输入队列、判定状态、广播快照分属三处,
同一帧的数据跨进程走两圈增量账收束成一句:85 毫秒里约 84 毫秒来自三跳串行——每跳的排队、调度与序列化,正是本页「跨服务超时、重试、幂等和补偿」那项成本在链路上的逐跳计息。
第三步:根因——高频强一致状态被切到了服务边界外
对照本页「拆分之前先回答四个问题」,这次拆分四问全没答:故障域没设想(「希望哪里坏、哪里不要跟着坏」没讨论过);扩缩容是拍脑袋(输入、战斗、广播的负载曲线完全同源,没有独立伸缩价值);最致命的是第三问——技能释放、对象同步、位置更新、碰撞结算这类高频状态需要不断跨服务,正是本页原话点名的反面做法;独立演进也不成立(三个服务同一团队、同一版本节奏)。拆分动机是「向微服务架构升级」——本页常见误区第一类「把服务拆分理解成组织升级或技术升级」逐字应验;拆法按功能菜单切,而不是按状态边界——技能战斗状态本该「在同一个进程里高频协同」,却被切成了三段远程调用。
第四步:处置与回填——战斗链路收回同域,领域拆分保留
修法:输入、战斗计算、状态广播合并回单进程房间实例(回到本页「按玩法实例拆」的正轨),房间内状态同域推进;邮件、商店、排行、活动、支付等长期资产链路保留领域拆分。复核:技能链路 P99 130 → 48 毫秒,投诉 260 → 30 单/日;服务数 14 → 9(合并 5 个实例态服务),服务发现条目与链路跳数同步回落——运维面没有变重,反而变轻。
回填清单:
| 落点 | 动作 | 回填处 |
|---|---|---|
| 拆分评审 | 新服务立项必答「四问」:故障域、扩缩容、高频状态、独立演进 | 拆分之前先回答四个问题 |
| 状态边界先行 | 先画状态边界再定部署形态,禁止按菜单或组织架构切服务 | 先围绕状态画边界 |
| 高频链路同域 | 帧级链路登记「同域推进」,跨服务调用需架构评审豁免 | 哪些状态绝不能跨服务高频抖动 |
| 拆分成本账 | 立项时六项成本(发现/路由/补偿/追踪/配置/控制平面)落到承担方 | 服务拆分真正增加了什么成本 |
案例的三个教训
- 拆分回答的是状态边界问题,不是组织口号——「微服务化」不是目标,故障域、扩缩容与独立演进才是。
- 高频强一致状态是拆分的硬边界——每多一跳,就是把排队与序列化的税再交一遍。
- 判断标准先于拆分动作——四问答不清楚时,所谓拆分很可能只是把复杂度重新命名。
Used By
服务拆分评审(四问先行);拆分顺序规划;过度拆分复盘。
- skynet(commit
64391f7):按角色拆而不按进程拆的可运行样本——service/下 22 个内置服务(gate、datacenterd、launcher、cmaster、cslave、multicastd、debug_console 等),由 launcher 逐个skynet.launch拉起(service/launcher.lua:95),整节点只跑一个进程(skynet-src/skynet_main.c:173)。角色边界清楚与进程数为 1 并不冲突,这是判断「该拆几个进程」时最省事的对照面
Related
关系链:拆分动因 → 状态画边界 → 四问 → 拆分思路 → 成本 → 判断标准 → 顺序。上游:无(services 入口)。下游:services/02(角色划分)、runtime/04(故障隔离与容量)。
Reference
本页为工程经验归纳;页内实战案例为教学示例(数字取整简化)。