ELI5 · 说人话
分布式不是「把系统做得更强」的技巧。
是一台机器装不下也不敢只有一台,被逼着拆开。代价是——你从此再也不知道对面到底怎么了。
先问为什么
分布式解决的从来不是「不够快」,是这三件单机根本办不到的事。
100 TB 数据、每秒 50 万次写。市面上最贵的单机也接不住,只能摊到很多台上。
硬盘会坏、机房会断电。一台机器一年总有几小时是死的——你得允许死一台,站还开着。
用户在东京,机房在美国东部。什么都不干,光跑一个来回就要 160 毫秒。只能把机器搬过去。
三条都不占,就别拆。一台好机器加一个从库,能顶到你想象不到的规模。
拆开那一刻
单机里,你调一个函数,它要么返回、要么抛错,两种结果。
跨机器调用有第三种:没消息。而「没消息」是最要命的那种。
这就是分布式的地基问题:你分不清「它死了」和「它只是慢」。后面所有花哨的机制,本质上都在跟这一件事较劲。
断线那一刻
两个机房之间的网线断了。两边都还活着,都还能收单,就是听不见对方。这时候你必须提前替系统做好选择。
点一个看看
别把这当成整个系统的一次表态。同一个系统里,不同功能选不同的——账户余额要正确,浏览记录能用就行。企业级的水平,就体现在能不能把这个刻度分清楚。
解法总纲
Raft、Kafka、分库分表、TCC……名词有几百个,动作只有四种。任何方案拆开看,都是这四把刀的组合。
同一份东西存好几份
机器一定会坏,所以任何数据至少三个副本,摆在不同机架、不同机房。死一台,站不抖。
代价:几份之间总要对表,这就引出了下一把刀。
吵起来就投票,少数服从多数
几份数据不一样时,谁说了算?摆奇数台,过半数同意才算数——这就是 Raft,也是 etcd 永远是 3 台或 5 台的原因。
过不了半数的那一边,自己闭嘴。
切成几块,各管一段
一份数据放不下,就按用户 ID 切成 16 份,每台只管自己那份,各查各的。
代价很实在:跨片的查询、排序、事务全部变难。所以这刀最后再拔。
同一件事做几遍,结果都一样
既然分不清超时是哪种,那就放心重试——前提是每笔请求带一个唯一编号,服务端认号不认次数,第二次来直接把第一次的结果原样返回。
这叫幂等,是所有跨机器写接口的及格线。
最常问的那一题
单机数据库一句 ROLLBACK 就撤了。跨四个服务,前两步已经在别人家的库里落了地——你撤不了,只能反着做一遍。
这叫 Saga。三条铁律:每一步都要能被反着做;每个反向动作也要幂等(补偿本身也会重试);补偿失败要有人工兜底,别让它自己无限转圈。
至于两阶段提交(2PC)——它把所有参与者锁在原地等协调者发话,协调者一挂全体僵死,所以业务系统里基本不用。
会咬人的地方
这些不是「优化项」,是不做就一定出事的东西。玩具和企业级的差别全在这一节。
叫脑裂。恢复后两份数据都改过,谁也不是对的。对策:当主的条件是拿到过半数选票,拿不到的那一半必须自己下线——宁可少一半算力,也不能有两个主。租约(lease)要设得比故障检测超时还短。
机器的墙上时钟会漂移,还会被 NTP 往回拨。永远别用时间戳判断因果。对策:用单调递增的版本号、序列号,或者带因果关系的逻辑时钟。真要靠时间(比如 Spanner),得先花钱买原子钟。
线程全堵在那儿等它,谁也接不了新活,一层层往上传染。对策四件套:超时(等 200 毫秒就撒手)、熔断(连续失败就先别打了)、舱壁(给每个下游单独的线程池,别共用)、降级(推荐位加载不出来就显示默认的)。
单机看日志就行,分布式里日志散在 12 台机器上,还对不上号。对策:入口生成一个 traceId 一路透传,日志、指标、链路三样都带上它。这套东西要在第一个服务拆出去的当天就建好,不是等出事再补。
对策:每份数据只有一个主人服务,别人只能通过它的接口改,自己那份是只读副本。再配一个天天跑的对账任务,把不一致的挑出来——不是防止不一致,是保证发现得比用户早。
落地顺序
先别拆回到第二节那三条:装不下、挂不起、离太远。一条都不占就别动。拆早了,你会花 80% 的时间处理网络问题,而不是做业务。
按业务切切的标准是:拆完每个服务能自己把一件事做完。要是每次请求都得叫上另外五个服务,你只是把函数调用换成了网络调用——更慢、更容易坏、还多了一堆超时要处理。
写死它怎么坏每个依赖都要提前写清楚:超时多少、重试几次、失败了返回什么。写不出降级方案的依赖,就不该是依赖。顺手把 traceId 和监控铺上,别等出事再补。
主动杀上线前自己 kill 一台看看,拔一次网线试试。没演练过的容灾方案等于没有——真出事那天你会发现主从切换的脚本三个月前就跑不通了。
最后一句实话:微服务是组织规模逼出来的产物,不是技术追求。三个人的团队拆十二个服务,是在给自己挖坑。
单机系统里,出故障是意外;
分布式系统里,出故障是日常。