这里是Z哥的个人公众号
每周五11:45 按时送达
当然了,也会时不时加个餐~
我的第「235」篇原创敬上
大家好,我是Z哥。
你有没有见过这种场景?
一个后台 Job,因为运行时间长、资源消耗大,被提议单独拆一个服务。
一个商品详情页,因为要聚合商品、库存、营销、评价,也被提议单独建一个聚合服务。
App、H5、PC 三端字段不一样,于是有人说,那就每端一个 BFF。
再加上 CI 慢、部署慢、扩缩容策略不同、MQ Consumer 和 API 运行方式不同,最后所有理由都指向同一个结论:
「要不再拆一个服务吧。」
我最近重新梳理服务治理时,越来越强烈地感觉到:很多所谓的微服务治理,真正要解决的不是「服务拆多细」的问题,而是别把几个完全不同的概念混成同一个词。
这个词就是「服务」。
我先讲结论:
服务不是代码分组单位,也不是部署单位。服务是一个团队长期负责某组业务事实,并对外承诺稳定契约、能独立发布和排障的边界。
如果这个定义没对齐,后面的所有治理动作都会走偏。
系统看起来更微服务,实际上只是仓库更多、进程更多、调用链更长、数据归属更乱、故障更难追。
很多服务拆错,不是因为大家不懂架构,而是因为日常工程里确实有很多「看起来应该独立」的东西。
逻辑服务:有明确负责人,负责业务事实、稳定契约、独立发布和排障
运行入口:API / MQ Consumer / Job / CLI
部署单元:平台上真正运行、扩缩容、观测和调度的对象
-
给前台页面查询价格的 API;
-
消费价格变更消息的 MQ Consumer;
-
定期重建价格缓存或索引的 Job。
这些入口的运行方式不一样,但它们使用的是同一套价格规则、同一份事实源、同一个长期负责团队。
如果 Job 需要独立超时、独立并发、独立资源配额、独立告警,那是「部署方案」要解决的问题。
我判断一个服务能不能独立,第一反应不是看它有没有独立仓库,也不是看它是不是单独部署。
比如订单服务拥有订单事实,库存服务拥有库存事实,支付服务拥有支付事实。
这些事实不是简单的数据表,而是业务世界里对某件事的权威解释。
一个价格重建 Job 跑得很慢,占资源,还可能拖垮线上 API。
这时候最容易发生的误判是:既然运行方式不同,那就拆一个 price-job-service。
-
这个 Job 有没有新的价格事实源?
-
有没有新的长期负责团队?
-
有没有新的价格契约?
-
能不能独立构建、发布、回滚和排障?
正确动作不是新建逻辑服务,而是把它变成独立部署单元:单独资源配额、单独超时、单独并发、单独告警、单独 runbook。
当然,真实系统里不一定会出现两个服务直接共享核心表、互相改状态这么夸张的情况。
更常见的问题是:两个服务之间同步调用越来越密,接口粒度越来越细,一次需求经常要两边同时改、同时测、同时发布。
这种情况下,虽然架构图上画的是两个服务,但它们在演进节奏上没有真正分开。
判断一个服务是否独立,不只是看它有没有单独部署,也要看它能不能独立负责事实源、契约、发布、回滚和排障。
如果两个服务长期高频协同发布、互相等待、故障排查也总是绑在一起,那它们更像是一个业务能力被拆成了两个运行单元,而不一定是两个真正独立的逻辑服务。
逻辑服务 = 谁负责 + 负责什么事实 + 对外承诺什么契约 + 怎么独立发布和排障
负责人、契约、发布和排障方式还可以慢慢补齐;事实源一旦放错,后面很难只靠改代码修回来。
因为那时候数据归属、接口契约、发布节奏和故障排查都会被绑在一起。
一个商品详情页可能要展示商品基础信息、库存状态、营销活动、评价摘要、推荐内容、配送信息。
于是很容易有人说:这不就是一个 product-detail-service 吗?
如果它只是为了让前端少调几次接口,临时把多个服务的结果拼一下,那我认为不值得新建长期逻辑服务。
但如果商品详情是一个长期稳定的业务场景,它有稳定契约,被 App、H5、PC、小程序、开放 API 多类调用方复用,而且里面有明确的编排、降级、缓存、排序、派生读模型,那么它可以成为一个「场景聚合服务」。
-
聚合多个业务域的数据;
-
对展示字段做裁剪;
-
做场景级缓存;
-
做降级兜底;
-
做派生读模型;
-
屏蔽上游服务的拓扑复杂度。
-
直连商品库、库存库、营销库;
-
绕过业务服务契约;
-
沉淀商品、价格、库存、订单这类核心业务规则;
-
把本该属于业务域的事实搬到聚合层。
最危险的是,为了改得快,把本该属于业务域的规则写进聚合层。时间一长,业务域服务会退化成数据接口,真正的业务规则反而都留在聚合层。
你以为自己在解耦前台体验,实际上是在把业务事实搬到体验层。
前几次需求可能会变快,因为聚合层离页面最近,改起来最顺手。但规则一旦从领域服务漂到聚合层,后面再想说清楚价格、库存、订单这些事实到底归谁负责,就会越来越难。
App、H5、PC 字段不同,要不要每端一个 BFF?
BFF 的边界应该看调用方族、渠道族、权限上下文、会话模型和发布节奏,而不是屏幕大小。
同一业务线里,App、H5、PC 只是展示字段略有不同,长期负责团队一致、权限模型一致、发布节奏一致,那更适合放在同一个 BFF 逻辑服务里,用能力切片或运行入口表达差异。
-
契约不一样;
-
权限边界不一样;
-
SLO 不一样;
-
发布节奏不一样;
-
长期负责团队不一样;
-
生命周期不一样。
它可以做展示 DTO、协议适配、登录态、设备信息、渠道上下文、端侧字段裁剪。
但它不应该决定价格怎么算、库存怎么扣、订单状态怎么流转、权益是否生效。
我反对的不是微服务,而是在负责人、事实源、契约、发布和排障方式都没讲清楚之前,就先把一个名字变成长期服务。
一个能力如果要成为独立逻辑服务,至少要回答清楚这些问题:
如果这是一次服务治理评审,我会要求提案里至少写清楚这几项:
service-boundary-proposal:
contract: 对外稳定 API 或事件契约是什么
operations: SLO、告警、日志、链路追踪、runbook 怎么维护
alternative: 为什么能力切片或部署方案承载不了
offset: 是否有存量服务需要吸收、转入口、保留兼容层或下线
它是在逼团队把「我想拆」翻译成「这个边界真的能长期负责」。
-
一个已有能力切片里的新功能;
-
一个新的能力切片;
-
一个新的 API 入口;
-
一个新的 MQ Consumer;
-
一个新的 Job;
-
一个新的部署单元;
-
一个临时兼容层。
如果没有明确的独立边界收益,就不要引入分布式系统成本。
-
多一次网络调用;
-
多一份契约维护;
-
多一套发布链路;
-
多一份监控告警;
-
多一个故障边界;
-
多一个数据一致性问题;
-
多一个责任归属问题。
如果收益只是「代码看起来更干净」,这笔账大概率不划算。
服务不应该乱拆,但这不代表所有东西都塞进一个大泥球。
-
类目;
-
商品档案;
-
属性;
-
品牌;
-
价格;
-
上下架;
-
搜索索引;
-
商品侧展示数据。
它们可能属于同一个大业务域,但不一定共享同一套领域语言、变化节奏和运行诉求。
能力切片要足够小,小到能表达清晰业务能力;也要足够大,大到能承载一个完整业务不变量。
太细,会制造高频调用、循环依赖、同步发布和分布式事务。
因为把一个粗切片拆成两个,通常比把一堆拆碎的服务重新揉回正确边界容易。
真正麻烦的地方不在拆分当天,而在后面重新收敛时要处理调用链、数据归属、发布节奏和责任边界。那时候每一项都会变成迁移成本。
因为组织里永远有新需求,永远有临时项目,永远有人觉得「再建一个服务最快」。
-
同一个负责团队;
-
同数据源;
-
同业务语义;
-
强同步调用;
-
高频协同发布;
-
没有独立 SLO;
-
出问题时需要同一批人一起排查;
-
下游依赖的只是接口形态,不是真正的独立业务能力。
-
保留为独立逻辑服务;
-
吸收到目标服务,变成能力切片;
-
转成已有能力切片的运行入口;
-
只保留兼容 API 或事件契约,内部实现迁走;
-
等调用方迁移后下线。
如果某个业务域已经拆出了太多服务,那么新增一个服务时,最好同时说清楚一个抵消项。
新增 1 个服务,同时明确 1 个服务要吸收、转入口、保留兼容层或下线。
它是在提醒团队:服务数量不是资产,边界清晰才是资产。
稳定场景契约 + 跨域聚合 + 不拥有事实源 -> 场景聚合服务
只是 API / MQ Consumer / Job 差异 -> 运行入口
如果只是某个业务能力的新接口、新字段、新 Job、新 Consumer,优先放回已有切片。
比如资源隔离、扩缩容、超时、重试、并发、限流、告警、凭证权限,这些很多时候应该由部署单元表达。
只有当它真的具备明确负责人、事实源、契约、独立发布和排障方式,并且能力切片和部署方案都无法承载时,才值得新建服务。
shouldCreateService(capability):
if onlyEntrypointChanged:
if existingCapabilitySliceCanOwnIt:
if newCapabilitySliceCanOwnIt:
if deploymentPlanCanSolveRuntimeNeed:
and hasIndependentReleaseAndDebug:
同一业务能力的 API、MQ Consumer、Job 共享同一套规则和事实源,却拆成三个服务。
每个页面一个长期聚合服务,或者 App、H5、PC 只是字段不同就拆三套 BFF,最后体验层变成新的业务规则中心。
多个服务共享同一批核心表、Redis key、MQ topic,甚至共享高权限连接串。
如果现在没有明确负责人、事实源、契约、发布和排障方式,大概率以后也不会自动长出来。
提前建服务不会自动长出独立边界,反而会先引入一套发布、监控、契约维护和排障成本。
这篇呢,Z哥和你分享了我对「服务边界治理」的一点思考。
核心不是反对微服务,而是反对把所有工程差异都包装成服务边界。
-
服务不是代码分组单位,而是负责人、事实源、契约、发布和排障方式都清楚的边界。
-
API、MQ Consumer、Job 是运行入口,不天然是服务。
-
容量、隔离、扩缩容诉求,优先用部署方案解决。
-
场景聚合可以拥有场景契约,但不能拥有业务事实。
-
BFF 服务调用方体验,不服务业务事实。
-
能力切片要能承载完整业务不变量。
-
默认不新建服务,不是反微服务,而是避免吞下不必要的分布式成本。
拆服务前,先讲清楚负责人、事实源、契约、发布和排障方式。
原创文章,转载请注明本文链接: https://zacharyfan.com/archives/1628.html
关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描二维码~

定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。
如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。
如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。