ELI5 · 说人话
微服务不是「把代码拆小」。
是把发布这件事拆开——每支队伍不用等别人,自己就能上线。代价是:本来一次函数调用的事,现在要走网络。
先分清楚
不是并列的两个词。微服务是分布式的一种,而且是被人逼出来的那一种。
所以:先按分布式那套把「网络会骗人」这件事处理干净(超时、重试、幂等、最终一致),微服务只是在这之上多加了一条按业务切的规矩。
解决什么
单体系统长到 200 人一起改的时候,会同时冒出这三个症状。
一个仓库、一个包,谁改都得等下一个发布窗口。改一行文案,等一周。
一个导报表的功能把内存吃爆,同一个进程里的下单、支付跟着一起躺下。
只有支付扛不住,却要把整个包复制 3 份。另外两块跟着白占内存。
反过来说,它不解决这三件事:代码写得烂、需求天天变、系统本来就慢。拆开之后这些只会更明显——因为现在跨了网络。
最难的一步
判断标准只有一条:一个需求进来,动几个服务?
所以边界要按业务能力划:订单、库存、支付、结算。不要出现 user-service / dao-service / common-service 这种按技术切的名字——那是把一个函数调用改成了三次网络请求,纯亏。
怎么解决
这八样东西,是拆开之后凭空多出来的成本。少一样,第 5 个服务上线那天就会还回来。
一道门
API 网关。所有请求先进这道门:认身份、限速、按路牌转发。外面永远只看见一个地址。
一本花名册
注册与发现。机器会挂、会重启、会扩容,地址天天变。谁上线自己去登记,别人按名字找。
一个开关箱
配置中心。改一个超时时间不该重新发版。开关和代码分开放,改完立刻生效。
两种说话方式
要立刻拿到答案就直接调;发短信、算积分这种不急的,扔进消息队列。能异步就异步。
一排保险丝
超时、重试、熔断、降级。下游一慢就先掐断,返回兜底结果。没有超时的调用是定时炸弹。
三块仪表盘
日志、指标、链路。每个请求带同一个 traceId 走完全程。查不出来的系统等于没上线。
各自的库
一个服务只准动自己的库,别人只能走接口。跨服务的事用消息补偿,别指望一个事务。
一条流水线
20 个服务就是 20 条流水线和 20 套容器编排。手工部署撑不过第 5 个服务。
最容易死的一次
支付变慢 5 秒,不会只是「支付慢」。调它的人全在那儿等着,线程占满,网关也进不去了。
三件套一起才有用:超时让等待有尽头,熔断让失败不再重复付出代价,降级让用户看到一句人话而不是白屏。再加一条舱壁——支付的线程池和查询的线程池分开,谁满谁自己受着。
出事之后
单体里看一份日志就够了。拆开之后,一次「下单慢」散在五台机器的五份日志里。
点一下看每一段
traceId 要在网关生成、每一跳都往下传,日志里带上它。这是第一天就要做的事,不是出事之后再补——补的时候你手里只有八个日志文件和一堆对不上的时间戳。
别踩
这不是微服务,是分布式单体:改一张表要通知十个团队,好处一个没拿到,坏处全占了。一个服务一个库,别人只能通过接口拿数据。
服务数量该跟团队数量走,不跟模块数量走。一个团队能端到端负责 1~3 个服务;再多就是每天在八个仓库之间来回切窗口。
订单叫库存、库存叫支付、支付叫风控……每多一跳,可用性就乘一次 0.999,延迟就加一次。同步链路别超过 3 跳,后面的改成发消息,让下游自己去处理。
网络超时不代表对方没做,重试就会做第二遍——用户被扣两次钱。所有写接口都要带幂等号,同一个号第二次进来直接返回上次结果。
「语言随便选」听着很美,代价是八套监控、八套发布脚本、八种线上问题。定一套主栈,例外要申请。
动手
先不拆在单体里把边界画出来:订单、库存、支付各成一个模块,互相只准调对方的接口,不准直接摸对方的表。在一个进程里都分不清的边界,拆出去只会更乱。
先修路网关、注册中心、配置中心、链路追踪、流水线——八件套先跑通,拿单体自己当第一个用户。基建没好就拆服务,等于在泥地里盖房。
只切一块挑最痛的那块先搬出去——发布最频繁的,或者最吃资源的。老代码用绞杀者模式:新流量走新服务,老路留着,稳了再删。一次只切一个。
演一次事故上线前把支付服务 kill 掉,看下单还能不能走降级路径。没演练过的降级等于没有降级——真出事那天你才发现兜底逻辑三个月前就被改坏了。
最后一句实话:能不拆就别拆。单体 + 清楚的模块边界 + 一条像样的流水线,足够撑一家公司到五十个研发。等到「上线要排队」真的疼了再拆,那时候你已经知道该沿哪条缝切。
分布式,是机器装不下逼出来的;
微服务,是人挤不下逼出来的。