1.3 团队与发布节奏:微服务的隐性成本
1.2 节定了边界,团队的气氛甚至有点兴奋。真正让大家冷静下来的,是负责人在白板上画的一张成本表。这一节算的是人的账:微服务落地之后,工作方式会怎么变、哪些成本在拆之前根本看不见、以及我们是怎么把生活拉回正轨的。
先说结论:康威定律会兑现
有种说法是系统架构会自动向组织结构对齐,拆成三个服务后五个人怎么分工,这才是最先要回答的问题。我们的分工是用户域 1 人、商品域 2 人、订单域 2 人,网关兼职。听起来挺整齐,但第一个月就出了事:用户域那个同事请了三天婚假,登录系统线上出个紧急 bug,没人能改——不是不愿意,是这三个月只有他一个人碰过那部分代码。
对策是轮值和代码权共享:每个服务至少保证两个人有独立改过的经验,每月轮换一次责任域。代价是每月一周的效率损失,换来的是任何单一服务不会因为一个人掉线而停摆。
制度落地前我们先做了三个月的影子期:每个域 still 由主负责人写代码,但每个 PR 强制由另一域的人 review。三个月后大家都摸过别的域的代码,轮值切换时几乎无感。这个过渡期看似浪费,实际上是后面所有轮值制度能跑起来的地基。
看不见的成本一:联调环境
单体时联调只需要本地起一个进程。拆成三个服务后,本地完整跑起来需要三个进程加 MySQL 加 Redis,新同事入职第一天光搭环境就耗掉半天。我们后来用一份 docker-compose 固定本地依赖:
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: devpass
ports: ["3306:3306"]
redis:
image: redis:7
ports: ["6379:6379"]
etcd: # 服务注册用,3.1 节细讲
image: bitnami/etcd:3.5
ports: ["2379:2379"]
三个业务服务仍然用 go run 本地起,compose 只管中间件,这样改代码不需要重启容器,调试体验接近单体。这个取舍很重要:让 compose 尽量轻,开发循环才快。
新员工入职我们给一份清单:clone 仓库、make up、make seed(灌入测试数据)、跑 healthz,十五分钟内必须看到三个服务的健康检查通过。这条硬指标的目的是倒逼环境脚本保持可用——脚本一旦年久失修,第一个受害者就是新人,而新人往往不会开口说。
轮值表之外还要排发布日历。三个域的发布窗口错开:用户域周二、商品域周三、订单域周四,每次发布后留 24 小时观察期。大促前一周进入冻结期,任何人要破窗必须拉群 vote。这条秩序听起来死板,却在第一次大促前救了我们一命:商品域一次发布带出一个慢查询,恰好落在冻结期前两小时,回想一下如果赶上大促当天性能曲线,另一个故事我们现在大概不愿意讲。
联调的另一半痛苦是跨服务特性开发。比如下单要同时改订单和库存,两边各开一个分支,本地就得同时跑两个版本的服务。我们的土办法是特性开关:改动隐藏在配置后面,默认关闭,联调时本地打开,互不影响主干分支。上线的顺序也固定下来——先发提供方,再发消费方,两步之间留出观察窗口。
看不见的成本二:发布节奏的错位
单体时一次发布就是一个二进制,所有人天然同步。拆开后,商品域想一天发三次,订单域一周发一次还得拉上财务 review,节奏根本对不齐。我们磨合出一个发布节奏协议:
| 维度 | 单体时期 | 微服务时期 | 变化 |
|---|---|---|---|
| 发布单位 | 全站一个包 | 按服务独立发 | 解耦 |
| 发布窗口 | 每周三晚 | 各域自选,错峰 | 灵活但需协调 |
| 回滚粒度 | 整站回滚 | 单服务回滚 | 精确 |
| 跨服务联调 | 编译期发现 | 需要契约测试 | 变重 |
| 紧急修复排队 | 3.2 小时 | 单域 40 分钟 | 明显改善 |
| 平均缺陷定位 | 一次堆栈 | 需查链路 | 变慢 |
表格最后两行是关键:微服务确实把发布和回滚变快了,但代价是联调和定位变重。想要保住“定位变快”的收益,必须配套链路追踪和统一日志,这也是为什么后面第五篇要花整整一篇讲 CLS 和云监控——不是选型偏好,是被逼的。
回滚的速度是实打实的红利。单体回滚要整体发版,微服务下单域回滚就是切回上一个镜像,我们的回滚脚本只有十几行:
# 快速回滚:拉镜像历史列表,切回上一版本并健康检查
SERVER=$1
LAST=$(curl -s http://registry.internal/tags/$SERVER | tail -2 | head -1)
ssh deploy@$SERVER "docker pull $LAST && systemctl restart $SERVER"
# 等 30 秒做健康探测,失败就再回退一层
看不见的成本三:on-call 与告警膨胀
拆分第一个月,告警数量直接涨了四倍。不是因为故障变多,是因为每个服务都在各自告警,同一个 MySQL 抖动会触发三个服务的连锁告警。大半夜被电话吵醒,爬起来发现是另一个域的问题,这种事发生两次就足以下决心治理。
补充一个反直觉的观察:告警变多的初期,团队第一反应往往是加更多监控来“覆盖更全”,结果是告警进一步膨胀。正确方向恰恰相反,是先减后增——先消除噪音,再逐条评估新增监控是否值得一条告警通道。每个告警都应当对应一个明确的动作:半夜被叫醒之后,你爬起来能做什么?如果答案是重启服务,那告警应当触发自动重启而不是叫醒人。
我们做了两件事:一是告警归并,同一类告警五分钟内只通知责任域;二是制定 on-call 轮值表,每周一个域的主负责人。告警通道最后收敛到腾讯云告警平台,维度统一之后才真正能睡好觉。这部分在云监控与自定义指标一节展开。
告警治理还有一条经验:先砍再建。我们把历史告警按触发频次排序,凡是每周误报超过三次的规则直接停掉,停的时候定下复核日期。两周后告警量从每天四十多条降到十条以内,而且没有一条真正的故障被漏掉——被漏掉的感觉会在第一周就显现。
一个容易低估的账:机器成本
三个服务各自要至少两台实例做高可用,网关再两台,瞬间就是八台机器的常态开销。拆分初期服务都很瘦,为了控制成本我们先用低配机型顶着,赶上腾讯云 CVM 新用户特惠囤了一批时长,等流量上来再逐台升降配。这一阶段的经验后来沉淀成第五篇的成本治理复盘:闲置实例和过度预留是微服务时代最容易被忽视的浪费。
我们也认真算过机器账的临界点。按当时的业务量,单体两台机能扛住,拆成八个实例后每台平均利用率只有两三成。为什么还愿意付这个钱?因为业务连续性的价值远高于机器费:大促期间任何一个服务单独出问题,影响面被限制在单域,而不是像单体那样全站陪葬。这笔钱本质上是买故障隔离,而不是买性能。
踩坑:联调地狱
最狼狈的一次是订单服务升级接口,按约定商品和用户都同步更新,结果用户域的同学请病假没来得及发,订单先上了。当天下午商品服务的调用方开始 500,排查半小时才定位到是接口契约不一致。教训是:任何跨服务的接口变更,必须在契约层先让消费方测试通过再上线,后来我们所有跨域接口都强制走 protobuf 定义加自动化回归。
那段时间我们整理出一个经验法则:拆分的头三个月,团队的有效产出会下降三成左右,因为联调、契约、环境这些额外开销集中在这个阶段爆发。负责人提前把这段 buffer 给老板打过招呼,所以季度 review 时大家没有被怀疑在摸鱼,这一点很重要——隐性成本不被看见的时候,人会背锅。
小结
微服务的隐性成本集中在三块:联调环境与契约联调、发布节奏与回滚粒度的再协调、告警与 on-call 的膨胀。对应的解法是固定轻量的本地编排、发布节奏协议化、告警归并加轮值。还有一个不能省的动作:把头三个月的产能下降提前同步给决策方,让隐性成本被看见。把这些账算清楚再动手,团队才不会在拆分三个月后开始怀疑人生。
最后给一个可操作的时间线:第一个月只做目录整理与契约定义,第二个月单_service 灰度搬迁(先拆订单域,它与外部交互边界最清晰),第三个月补齐监控与回滚机制。我们实际走完用了三个月零一周,比计划多出的一周花在联调环境脚本上——这恰好印证了前面说的,环境类债务最容易被低估。
延伸阅读
- 1.4 技术选型表:Go 框架与云组件对齐 —— 下一节看我们具体选了什么
- 5.1 日志收集:CLS 统一日志平台 —— 联调变重后的定位利器
- 5.4 成本治理:闲置实例与降配复盘 —— 机器账怎么算回来
