ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java单体应用拆微服务:边界、治理、数据拆分与灰度迁移

Java单体应用拆微服务:边界、治理、数据拆分与灰度迁移

把一个跑了六年、二十多万行的单体应用拆成 Java 微服务架构,这件事我在两个不同规模的团队里各推动过一次,第一次拆得稀碎,第二次才算摸到门道。微服务不是把 jar 包拆成十个进程就叫分布式,它本质上是把“一次编译、一个进程、一个数据库”的强一致性世界,换成一个“多进程、多数据库、最终一致”的弱一致性世界,换来的是独立部署、独立扩容和故障隔离。这篇文章想聊的就是这条演进路上真正会卡住人的地方:什么时候该拆、按什么维度拆、工程结构怎么改、服务治理参数怎么定、数据怎么拆、灰度怎么切、出问题怎么查。适合已经在一线写 Java、正在被单体构建时长和发布风险折磨的开发者,也适合刚接手微服务项目、还没搞清楚为什么要这么设计的人。

1. 动手之前先算账:你的单体是不是真的到了该拆的时候

1.1 别靠感觉判断,先量化六个信号

我见过太多团队喊着“单体撑不住了”就要动手,结果拆完发现原来的问题一个没解决,反而多了服务发现、链路追踪、分布式事务三座大山。拆之前我习惯先拿数据说话,把痛点变成可对比的数字,这样后面做决策、向上汇报、评估收益都有依据。

我通常看六个数:单次全量构建加启动的时长、单次回归测试的时长、同一模块并行开发的冲突频率、单库的 QPS 峰值和连接数占用、单表最大数据量、一次线上故障的影响范围。这几个指标里,前三个反映的是研发效率损耗,后三个反映的是稳定性风险,两类问题对应的拆法完全不一样,混在一起谈就容易拍脑袋。

举个具体的判断线。构建加启动超过 8 分钟,开发者的心流就被打断了,一天多改几次代码全天都在等编译;回归测试超过 40 分钟,多数人会开始跳过着核心用例;一个 30 人团队同时改同一个 war 包,每周合并冲突超过 10 次,说明模块边界已经糊了。数据库那边,单表过 5000 万行、单库连接数长期打满 70% 以上、峰值 QPS 超过单实例压测上限的 60%,基本可以确认存储层是瓶颈。反过来,如果你的系统只有三五个人维护、日均请求几万、构建三分钟搞定,那多半是别的原因让你难受,比如代码质量差、缺少自动化测试,这些问题拆成微服务只会放大。

提示:把上面六个数字做成一张月度趋势表,连续观察两个月。趋势向上但绝对值不高的,优先做代码治理和模块化改造;绝对值已经越过阈值的,才考虑进程级拆分。

1.2 三条演进路线的时间账,别一上来就选最贵的那条

“从单体到分布式”并不是一条笔直的路,中间至少有三个可以停靠的站台,成本差着数量级。我一般会把这三种方案摊开跟团队算清楚,让大家自己选,而不是我拍板。

路线做什么人力投入(20万行规模)见效周期适合的信号
A. 单体模块化不动进程,只理清包结构、依赖方向、数据库 schema 归属2-3 人 × 1 个月2-4 周代码耦合严重但流量不大
B. 垂直切 3-5 个服务按业务域切出核心几个服务,其余仍在单体4-6 人 × 3 个月2-3 个月构建慢、部分模块需独立扩容
C. 全面微服务化按限界上下文细分,配套治理、可观测、CI/CD8-12 人 × 6-12 个月半年以上多团队并行、业务线差异大

路线 A 是最被低估的。很多人觉得“模块化不算演进”,但我实际做下来,把一个大泥球按业务域重新分包、用 Maven 的enforce插件禁止跨包直接调用、把共享的实体类下沉到独立模块,做完之后再谈拆分,成本会下降一大半。因为模块化阶段暴露出来的耦合关系,恰好就是未来服务边界的草图,这份草图免费且低风险。

路线 B 是我最推荐的落地姿势,也就是所谓的“绞杀者”思路:先挑一个变更频繁、故障影响面大、但业务相对独立的模块切出去,跑通了再切第二个。一个做了六年的交易系统,我当时的顺序是商品查询、用户中心、订单、支付,每个之间隔一个月,前一个稳定了才动下一个。这样任何一步出问题,影响面都可控,团队也有时间消化新技术栈。

路线 C 只适合一种情况:组织结构本身已经按业务线分开了,多个团队要各自发版、各自扩容。这时候微服务解决的不是技术问题,而是协作问题。如果还是一个团队维护全部代码,全面微服务化带来的运维负担会远大于收益。

2. 拆分方案的整体设计:边界、粒度与依赖方向

2.1 拆分维度:业务能力优先,技术分层靠后

我见过最典型的一种错误拆法,是按技术分层拆:网关服务、Web 服务、Service 服务、DAO 服务。听起来很整齐,实际上是一场灾难。因为一个业务需求的变更,比如“下单时增加优惠券校验”,要同时改 Web 层、Service 层、DAO 层三个服务,三个团队联调,一次发版要协调三次,发布窗口从一天变成一周,跨服务调用还多出两跳网络开销。

正确的维度是按业务能力切,让一个服务完整地拥有一个业务闭环。订单服务内部该有 Controller、Service、Repository,它自己就是一个小单体,只是这个小单体对外只暴露接口。判断一个候选边界是否合理,我常用三个问题自测:这个服务的数据是不是基本只被它自己写?业务规则变更时,改动是否集中在这一个服务里?它宕机时,其他服务能不能降级继续跑?三个答案都是“是”,边界大概率是对的。

2.2 服务粒度:拆到几个人维护一个服务才合适

粒度是第二个大坑。新手常犯的错是拆得太细,把“用户查询”和“用户注册”拆成两个服务,结果两个服务共享同一张用户表,任何一次注册逻辑调整都要双发。我踩过这个坑,最后不得不把两个服务又合回去,白干两个月。

我现在的经验值是这样:一个服务对应一个限界上下文,代码量大概 1 万到 5 万行,由一个 5 到 8 人的小团队负责,团队能独立完成从需求到上线的全流程。小于 5000 行的服务要认真想想是不是切错了,大于 10 万行的要看看是不是还能再拆。

除了代码量,还有三个更硬的判断标准。变更频率:总是一起改的东西不该分家。数据归属:共享一张主表的两块逻辑,要么合并,要么明确主从,绝不能双方都能写。伸缩需求:秒杀时段需要独立扩容的,才值得单独成为一个服务;如果所有模块的扩容比例都一样,拆开反而浪费机器。

注意:服务拆分的下限不是代码行数,而是“能不能独立完成一次业务闭环”。做不到闭环的,拆出来就是个需要频繁跨服务调用的半成品。

2.3 依赖方向治理:把网状调用拉成树

拆完之后最容易失控的是调用关系。半年之后回头看,往往会发现 A 调 B、B 调 C、C 又调 A,中间还夹着几个循环依赖,链路一断全站雪崩。这种结构在业内被叫做“分布式单体”,是最糟糕的形态:付出了微服务的全部成本,只拿到了单体的全部缺点。

我的做法是拆之前先画一张依赖图,把所有调用关系画出来,然后强制三条规则。第一,禁止环:出现环就说明两个服务该合并,或者中间该抽一层。第二,公共能力下沉:用户身份、权限校验、消息发送、文件存储这类横切能力,抽成基础服务,所有人只依赖它,它不依赖任何人。第三,禁止跨层直连:如果 A 只通过 B 拿到 C 的数据,那 A 就不该有 C 的客户端依赖;数据要由 B 组装后返回,而不是让 A 自己去拼。

这张图我会贴在团队的文档里,每次新增服务依赖都要更新。一旦发现某个服务被超过五个服务同时调用,就得警惕它是不是正在变成新的单点,需要给它加上限流、降级和独立扩容的能力。

3. 工程结构改造:从单体仓库到多模块聚合

3.1 目录结构:接口定义与实现分离

单体应用的工程结构通常是三层包加一个大pom.xml,改成服务化之后,第一步要动的不是代码逻辑,而是工程结构。我的标准做法是搭一个聚合工程,父pom只管版本和插件,下面每个服务是独立子模块,每个子模块再分成两个模块:xxx-api和xxx-service。

api模块里只放接口定义、请求响应对象、枚举和常量,不含任何实现和第三方重依赖;service模块依赖自己的api,负责真正的实现。这样拆的好处非常直接:调用方只依赖api,不会把提供方的数据库驱动、缓存客户端、内部工具类全都传递过来,编译速度快,依赖污染少,接口变更也容易识别出来。

<!-- 父 pom 的 dependencyManagement 片段 --> <dependencyManagement> <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>platform-bom</artifactId> <version>1.4.2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

api模块的发布要单独走节奏,因为它是给别人用的契约。每次改动我都要求至少向下兼容一个小版本,加字段可以,改字段名和删字段必须走新的版本号,否则调用方一升级就炸。

3.2 版本统一管理:别让子模块自己写版本号

多模块工程最容易出的事故是版本漂移。A 模块用某个基础框架 2.3 版,B 模块用 2.5 版,本地能跑,打包上线时依赖仲裁选了其中一个,另一个模块的某个方法签名对不上,启动直接NoSuchMethodError。这类问题排查起来极其费劲,因为报错位置和真实原因隔了好几层。

解决办法只有一个:所有版本号统一写进父pom的dependencyManagement,子模块一律不准写<version>。再狠一点,在父pom里加maven-enforcer-plugin的banDuplicateClasses和requireUpperBoundDeps规则,构建阶段就把重复类和版本冲突拦下来。我还会额外加一条规则,禁止引入SNAPSHOT版本上线,这条规则救过我至少两次。

插件版本同样要锁死。编译插件、打包插件、代码规范插件版本不一致,会导致不同机器构建出来的产物行为不同,这类“我这里没问题”的问题多数都出在这儿。

3.3 公共代码的边界:common模块是分布式单体的温床

几乎每个团队都会有一个common模块,然后慢慢变成垃圾桶:工具类、常量、异常、拦截器、甚至业务实体全往里塞。半年后你会发现,只要改一行common,全部服务都要重新构建发布,所谓的独立部署变成了一个笑话。

我给自己定的红线是:common里只放没有业务语义的东西,比如日期工具、加密工具、统一的返回结构、异常基类、通用常量。凡是带“订单”“用户”“商品”字样的类,一律不许进common。业务实体的共享要靠接口契约,不靠代码共享。

如果两个服务真的需要同一份业务逻辑,正确的做法是把它下沉成一个独立的领域服务,通过接口调用,而不是复制一份代码到common里。代码共享看着省事,实际上是给未来的耦合埋雷,因为一旦共享代码需要为某一方改行为,另一方就被迫接受。

4. 服务治理能力落地:注册发现、配置、调用、容错

4.1 注册中心与配置中心:几个容易被忽略的配置项

注册中心的核心作用只有一个:让调用方知道提供方现在在哪几个地址上。原理不难,难的是几个细节没配对,线上就会出各种“偶发调不通”。

第一是健康检查方式。用 TCP 探测只能证明端口活着,证明不了应用能处理请求;用 HTTP 探测要指向一个轻量的健康端点,里面别塞数据库全表扫描这种重逻辑,否则健康检查本身就是压垮服务的最后一根稻草。第二是优雅下线。服务停止时先向注册中心注销,再等一个“摘除延迟”,让调用方本地的地址缓存过期,最后才真正关闭进程;这个延迟我一般设 10 到 15 秒,比刷新间隔略长一点。第三是心跳与剔除阈值,心跳间隔 5 秒、连续 3 次失败标记不健康,是个比较稳的组合,调得太激进会因网络抖动误摘节点。

配置中心那边,我认为必须做到三件事:本地兜底,配置中心不可用时能读本地缓存启动,否则配置中心一挂全站起不来;变更审计,谁在什么时候改了哪个值要能查到;灰度推送,新配置先推一两台观察,别一键全推。我经历过一次把限流阈值少写一个零的事故,就是没有灰度推送导致的。

4.2 同步调用还是异步消息:按语义分,不按喜好分

选通信方式我只看一个东西:这次调用是“查询”还是“命令”。查询类,调用方需要立刻拿到结果才能继续,用同步调用;命令类,调用方只是通知对方“这件事发生了”,不需要对方立刻返回结果,用异步消息。按这个标准分,绝大多数争抢都能平息。

同步调用的代价是可用性相乘。A 依赖 B、C、D 三个服务,三个服务各自的可用性是 99.9%,串起来 A 的可用性就变成 99.7%,一个月多出两小时不可用。所以同步链路越短越好,能用一次聚合查询解决的别调三次。异步消息的代价是可靠性,消息可能丢、可能重复、可能乱序,需要额外做发送确认、消费幂等和顺序保障。

我实际项目里的比例大概是七三开,查询走同步,状态变更类的通知走异步。有一类特别容易搞错:跨服务的“扣减库存”如果做成异步,用户下单成功了但库存没扣,超卖就是这么来的。凡是涉及资源占用的强约束,老老实实做同步加事务补偿,别图省事。

4.3 超时、重试、熔断:参数不是拍脑袋,是算出来的

这几个参数是微服务稳定性的命门,但很多项目里全是默认值或者随手写的整数。我习惯按下面这套逻辑算。

超时时间先看下游接口的 P99 响应时间,取它的 2 到 3 倍。比如 P99 是 200 毫秒,超时设 600 毫秒,既能容忍正常的毛刺,又不会让线程卡太久。更关键的是逐层收敛:如果 A 的超时是 600 毫秒,A 调 B 的超时就得小于这个数,比如 400 毫秒,B 再往下 200 毫秒,形成一个漏斗。反过来设的话,上游等不及先超时了,下游还在傻算,白白浪费资源。

重试次数只给幂等的读操作,最多两次,并且必须加退避,比如 100 毫秒、300 毫秒。写操作默认不重试,除非业务层自己实现了幂等键。不加退避的重试是最危险的,下游一抖,上游三次重试直接把流量放大三倍,把小抖动变成雪崩。

线程池和连接池的容量也能算。需要的并发线程数约等于 QPS 乘以平均响应时间(秒)。比如一个接口峰值 200 QPS,平均耗时 20 毫秒,那理论上 4 个并发线程就够,加上峰值余量设 16 到 32 比较合适。连接池同理,峰值 QPS 200、平均 20 毫秒,理论 4 个连接,实际设 10 到 20,再多只会把压力推给数据库。

熔断我一般用滑动窗口统计:窗口 10 秒,最小请求数 20,失败率阈值 50%,熔断后 5 秒进入半开,放 5 个请求试探,全部成功才恢复。最小请求数这个值很关键,设成 1 的话,第一个请求失败就直接熔断,误判率极高。

参数计算依据常见误设后果
调用超时下游 P99 × 2~3统一设 5 秒慢调用堆积,线程池打满
重试次数幂等性 + 退避策略无脑重试 3 次放大流量,雪崩
熔断阈值失败率 50%,最小请求 20最小请求设为 1频繁误熔断
线程池上限QPS × 平均 RT × 余量固定 200内存浪费或排队
连接池上限QPS × 平均 RT × 余量与线程池相同数据库连接被打满

4.4 链路追踪:让一个请求在所有服务里叫同一个名字

没有链路追踪的微服务基本没法排障。用户投诉“下单慢”,你得知道是订单服务慢、库存服务慢,还是中间某次 RPC 卡住了。做法是在入口生成一个全局的 TraceId,通过 HTTP 头或消息头一路透传,每个服务在日志里都打印它,最后按 TraceId 把所有日志捞出来就是一条完整链路。

这里有几个实操细节容易漏。线程池会丢上下文,把任务丢进线程池之后 TraceId 就断了,需要对任务做包装,提交时把上下文复制过去。消息队列会断,生产消息时把 TraceId 塞进消息头,消费端取出来重新放进上下文。异步回调会断,同类问题。这三个地方是 TraceId 丢失的九成原因。

采样率也要设。全量采在流量大的时候存储成本扛不住,我一般按 10% 采样,同时把所有错误请求和慢请求强制采样,这样既控成本又不丢关键信息。

5. 数据拆分与一致性:最容易翻车的一段路

5.1 数据库拆分的三个步骤,一步都不能跳

服务拆了但库没拆,等于没拆。所有服务连同一个库,一个慢 SQL 拖垮全站,表的归属也说不清楚。但库也不能一次拆到位,我一般分三步走。

第一步,逻辑归拢:物理上还是一个库,但把表按业务域分组,用不同的 schema 或者至少用表名前缀清晰区分,明确每张表的“写归属”只有一个服务。这一步不改代码,只改约定,风险最低。

第二步,物理拆库:同一个实例上建多个库,把表迁过去,服务连各自的库。这一步开始出现跨库查询问题,需要盘点所有跨域 SQL:跨库 JOIN 改成先查主表再批量查关联表;跨库分页改成从驱动方取 ID 列表再回表;跨库排序改成在应用层归并。这些改动量往往比想象中大,要在排期里留够时间。

第三步,拆实例:不同库放到不同实例,才能真正做到故障隔离和独立扩容。到这一步才会真正面对分布式事务问题。

跨库之后有几样东西必须放弃:数据库外键约束、存储过程、跨库视图。这些在单体里很好用,在分布式里就是枷锁。替代方案是应用层校验、冗余字段、宽表、异步补偿,代价是必须接受“短时间内的数据不一致”。

5.2 分布式事务的四种方案,别被概念吓住

一提到分布式事务,很多人第一反应是上重量级框架,其实绝大多数场景根本用不上。我把常见方案按落地难度排一下,从低到高。

方案一致性性能适用场景落地难度
本地消息表最终一致高跨服务状态通知、非强约束低
事务消息最终一致高有消息中间件支持的异步场景中
TCC最终一致中资金、库存等需要预留资源的场景高
两阶段提交强一致低极少用,性能代价太大中

我做过统计,项目里九成的跨服务一致性问题,用本地消息表就能解决。做法很简单:业务操作和“待发送消息”写在同一个本地事务里,事务提交后由定时任务或异步线程把消息投出去,消费方做幂等处理,投递失败就重试,超过次数进死信表人工介入。这套方案的好处是不依赖任何特殊中间件,任何团队都能实现,缺点是有一点点延迟,通常几百毫秒到几秒。

TCC 只在真正需要资源预留的场景才用,比如扣库存,得先 Try 冻结、Confirm 确认扣减、Cancel 释放。它的复杂度在于要写三个方法,还要处理空回滚、悬挂、幂等三个经典问题:Cancel 比 Try 先到怎么办,Try 因为网络超时没到但后面又到了怎么办,重复调用怎么保证只生效一次。这三个问题不处理干净,TCC 反而比不用还危险。

注意:不管用哪种方案,幂等都是前提。没有幂等,任何重试和补偿都是在制造脏数据。

5.3 幂等的四种实现,按业务特点挑

幂等这件事,我的建议是别追求通用方案,按业务特点挑最合适的那种。唯一索引最可靠,把业务唯一键建成唯一索引,重复插入直接报错,代码里捕获异常当作成功处理;缺点是只适用于插入场景。状态机适用于有明确状态流转的业务,比如订单只能从“待支付”到“已支付”,重复回调因为状态不匹配自然被拒。去重表是记一张消息 ID 表,处理前先查,处理完记录,适合消息消费场景。令牌是前端先申请一个一次性令牌,提交时带上,服务端校验后作废,适合防重复提交。

实际项目里我通常组合使用:接口层用令牌防重复提交,服务层用状态机防非法流转,消息层用去重表防重复消费。三层各管一段,任何一层单独失效都不至于出大问题。

6. 灰度迁移:让老单体和新服务并存半年的落地打法

6.1 绞杀者模式的执行顺序:先读后写,先边后核

拆分不是一刀切,而是让新服务慢慢把老单体的功能“绞杀”掉。执行顺序上我有几条固定的原则。

先切读,后切写。读请求无状态、可回滚、可双跑对比,风险最低。把查询切到新服务,新旧两套同时跑一段时间,比对结果是否一致,一致了再切写。写操作涉及数据变更,一旦出错回滚成本极高,必须放在读稳定之后。

先切边缘,后切核心。商品详情、评论列表这种非核心链路先动,跑顺了再动订单、支付。核心链路的任何抖动都是真金白银,容错空间几乎为零。

网关做路由开关。所有流量都从网关进,网关按用户 ID 取模、按请求头、按租户维度把流量分到新老两套。开关要能做到秒级切换,出问题一键回退。我一般按 1%、10%、50%、100% 四档放量,每档观察至少一天,重点看错误率、耗时 P99 和业务指标。

6.2 双写与对账:差异不是靠祈祷消失的

数据迁移阶段最难的是新库和老库的数据同步。我的做法是以老库为准单向同步,新服务写请求仍然写老库,通过数据同步组件把变更同步到新库,新库只服务读。等读到完全一致后,再把写切到新库,反向同步回老库一段时间作为回滚保障。

光同步不够,必须有对账。我一般做两层:实时层,写操作后抽样比对关键字段,发现不一致立即告警;全量层,每天凌晨跑一次全量比对,把差异记录到差异表,人工确认后修复。差异率是我判断能不能继续推进的核心指标,低于万分之一才允许进入下一档放量。

这里有个坑要提醒:时间字段和自增主键。两边数据库的时区、精度不一致,会导致大量“假差异”;自增主键在双写场景下会冲突,需要改成全局唯一 ID 生成方案。这两类问题会吃掉对账工具的绝大部分精力,提前处理好能省很多时间。

6.3 回滚预案:写代码之前先想好怎么退

我要求每个拆出去的服务在上线前必须回答一个问题:如果现在要回退,需要几步、多长时间、会不会丢数据。答不上来的不允许上线。

回滚一般分三层。第一层是网关开关,秒级把流量切回老单体,适用于新服务完全不可用的情况。第二层是功能开关,新服务里的新逻辑关掉,走老逻辑分支,适用于部分功能出问题。第三层是数据回滚,这个最难,所以设计时就要保证新服务写入的数据老单体也能识别,比如不新增老单体不认识的枚举值、不改字段语义。老代码至少要保留两个完整的发布周期再删,急不得。

7. 常见问题速查与踩坑实录

7.1 高频问题速查表

下面这张表是我这几年攒下来的,基本都是排查过、定位过、修过的问题,遇到类似现象可以先按这个顺序看。

现象优先排查方向典型原因
服务启动成功但调不通注册中心、网络、健康检查命名空间或分组不一致,注册到了另一个环境
偶发调用超时,重试后成功连接池、GC、慢 SQL连接池被打满,请求排队等待
配置改了不生效本地缓存、监听器、优先级远程配置被本地配置文件覆盖
日志里 TraceId 时有时无线程池、消息、异步回调上下文未透传,任务提交时丢了
内存持续上涨最终 OOM容器限制与堆参数堆上限按物理机设置,超过容器限制被强杀
上游熔断,下游其实很健康熔断统计窗口最小请求数太小,少量失败触发熔断
消息重复消费导致数据翻倍消费端幂等消息重投后没有去重校验
跨库查询结果对不上数据同步延迟主从或双写延迟,读到旧数据
本地正常线上报类找不到依赖冲突传递依赖版本不一致,打包时被仲裁
服务下线瞬间大量报错优雅停机未先注销就关闭,调用方缓存未刷新

排查顺序上我有个习惯:先看影响面(是单个服务还是全链路)、再看时间点(是不是刚发过版、刚改过配置)、然后看指标(耗时、错误率、线程数、连接数),最后才翻日志。倒过来做的话,几万行日志能看一天。

7.2 几条踩出来的心得

第一条,别在业务高峰期做架构变更。我吃过这个亏,下午两点开始切流量,结果赶上流量高峰,新服务连接池不够用,一边排查一边回滚,手忙脚乱。现在我所有切换操作都放在低峰期,并且提前两天把监控大盘、告警规则、回滚脚本全部准备好,切换当天只做执行,不做设计。

第二条,容器里的堆内存一定要按容器限制来算。一个限制 2G 的容器,堆参数还按物理机设成 4G,进程一膨胀就被系统直接干掉,日志里连 OOM 都看不到。改用按可用内存百分比设置堆上限之后,这类问题基本消失。同理,线程数、连接数这些也要跟着容器规格走,别一套配置打天下。

第三条,服务拆分的前三个月,别急着上全套治理。我见过团队一上来就把限流、熔断、灰度、链路追踪全配上,结果每个都要调参,每个都有坑,最后没人搞得清楚是哪个组件在起作用。我的建议是先让服务能跑通、能独立发布、能看日志,稳定之后再逐个加治理能力,一次加一个,加完观察一周。

第四条,文档要跟着服务走。服务一多,最缺的往往不是技术,而是“这个服务是干什么的、谁在维护、依赖谁、SLA 是多少”。我要求每个服务根目录放一份README,写清楚职责边界、核心接口、数据表归属、负责人、常见故障处理。这份文档维护成本很低,但在半夜被告警叫醒的时候,价值千金。

第五条,为“不确定”留出预算。拆分排期我通常按评估值的 1.5 倍来报,因为跨库查询改造、数据对账、接口兼容这些隐性工作量几乎必然超预期。压着排期走的结果往往是砍测试、砍灰度时间,最后在线上还回来。

最后说一个我自己的体会:微服务架构的本质不是技术选型,而是一次组织边界的重新划分。技术上的注册中心、链路追踪、分布式事务,都是为了让这种划分在工程上可行;真正决定成败的,是你有没有想清楚哪块业务该独立、哪个团队该负责哪一段。技术方案可以照抄,边界判断抄不了,这块只能靠对业务的熟悉程度一点点磨出来。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进