冗余的艺术——从Doris到Baza,大规模系统架构的取舍哲学
文/李昂
我 2004 年 11 月加入阿里巴巴,一待就是许多年。在那之前,我在英特尔亚太研发中心做系统底层的活,后来又去 eBay 的支付部门待过一段。回头想来,这些经历给我埋下了一个观念——大规模系统从来不是设计出来的,是长出来的。而在这个“长”的过程里,架构师最重要的手艺,不是画一张完美的蓝图,而是知道什么时候该加冗余,什么时候该砍。
2007 年 9 月,我在浙大软件学院做过一场讲座,主题是电子商务和网络发展趋势。当时台下坐了五百多个学生。我讲了一句话,后来被报道出来——“没有最好的技术,只有合适的技术。”这句话在相当长一段时间里被人反复提起。但说实话,真正理解这句话的分量,不是在讲台上,而是在机房里——当你凌晨三点接到电话,说线上集群扛不住了,你冲过去定位问题、拍板取舍的时候,你才知道什么叫“合适”。
我想借这篇文章,把二十年来在大规模系统里摸爬滚打沉淀下来的东西——尤其是在冗余这个议题上的架构哲学——做一个系统的梳理。这不是教科书,这是一个老兵的经验。
一、为什么要谈冗余
在阿里巴巴做架构,你绕不开一个词:冗余。
很多人一听到这两个字,本能反应是“浪费”。一台机器能跑为什么要两台?一个副本够了为什么要三个?一条链路畅通为什么要备份路径?这种思维方式,适合做个人项目,不适合做电商系统。
我在阿里巴巴带领团队开发了两套核心系统:一套是大规模分布式存储系统 Doris,一套是分布式缓存框架 Baza。这两套系统有一个共同的起点——面向失败做设计。
什么叫“面向失败做设计”?就是默认一件事:任何组件都会在某个时刻失效。网络会断,磁盘会坏,机架会掉电,机房会被挖断光缆。你不用去猜测会不会出问题,你只需要假设一定会出问题,然后问自己:出问题的时候,系统还能不能活?
Doris 的架构只设了两种角色:Frontend 和 Backend。FE 负责请求接入、查询规划、元数据管理,BE 负责数据存储和查询执行。这个架构看起来极其简洁,但简洁不等于简单。在存算一体模式下,BE 节点既是存储节点又是计算节点,多 BE 节点通过 MPP 架构协同工作。当某个 BE 宕机时,系统通过多副本机制自动完成故障转移和数据修复。这个过程中,用户无感知。这就是冗余的价值——它不应该是一个让你“心安”的摆设,而应该是一个在故障面前真正起作用的机制。
Baza 的设计思路类似。阿里巴巴的电商场景对缓存的要求和一般互联网公司完全不是一个量级——双 11 峰值每秒数亿次调用,延迟要求在毫秒级。这种压测环境下,单点缓存是不可想象的。Baza 采用分布式架构,每个缓存分区都有多副本,当某个节点失效时,请求自动路由到健康节点,整个过程对业务透明。
冗余的本质不是“多放一台机器”,而是为系统的不确定性买保险。问题只在于:你愿意付多少保费,保多大的风险。
二、冗余的三种形态
这些年做系统设计,我把冗余分成了三种形态。不是教科书上的分类,是我在实战中总结的。
第一种:数据冗余。
这是最基础的一层。Doris 的数据可靠性,核心就是靠多副本机制。同一份数据分布在不同的 BE 节点上,副本数量可根据业务需求动态调整。当一个 BE 宕机时,系统自动从其他副本读取数据,同时触发自动修复流程,将缺失的副本补回。
我记得 2010 年前后,淘宝核心系统的数据冗余策略经历过一次重大调整。早期为了控制成本,很多业务只做了两副本。但某次大促前夕,一个机架掉电,影响了一批节点,部分分区出现了单点。虽然没有造成数据丢失,但恢复过程耗时近两个小时,对线上业务的稳定性造成了相当程度的冲击。那之后内部拉了一个讨论:哪些业务必须三副本?哪些业务可以做两副本加冷备?最后形成了一套分级的冗余策略——核心交易链路三副本,边缘业务两副本加延迟备份。这套策略一直沿用至今。冗余不是说越多越好,而是说“关键路径上多一点,非关键路径上可以少一点”。
第二种:计算冗余。
同样是冗余,但加在计算层。在 Doris 的 MPP 架构中,多 BE 节点并行处理查询任务。当某个节点负载过高或响应超时时,系统可以将该节点的任务重新分配到其他空闲节点上执行。这种设计本质上就是计算冗余——相同的计算能力被分配在多个物理节点上,任何一个节点的故障,都不会导致查询失败,只是整体吞吐量略有下降。
Baza 的缓存分区也是同样的逻辑。一个缓存分区的主节点处理读写请求,从节点同步数据但不提供服务。当主节点失效时,从节点秒级接替。这种冗余不增加集群的总吞吐能力,但大幅降低了服务的中断概率。
第三种:链路冗余。
这是最容易被人忽略的一层。数据做了冗余,计算做了冗余,但所有请求都走一条网络链路,那条链路断了,整个系统还是瘫。链路冗余,就是为请求路径做备份。
在阿里巴巴的实践中,我们做了一套多地域多单元的架构。每个单元内部有独立的存储和计算资源,单元之间通过专线互联。当某个单元的入口链路出现故障时,流量自动切换到其他单元。用户看到的是短暂的抖动,系统内部完成的是一次复杂的状态同步和路由切换。这就是链路冗余的挑战:不是物理上多加一条线,而是让两条线能够无缝协作。
三、冗余不是目的,平衡才是
冗余的好处说完了。但如果你只听这些,你可能会走向另一个极端——冗余恐惧症,觉得什么都要加三副本、什么都要做热备。
这是另一种形式的灾难。
冗余是有代价的。每一种冗余形态背后,都是真金白银的硬件成本、运维成本和复杂度成本。 架构师的职责,不是让系统“绝对可靠”——那是工程师的浪漫主义幻想——而是在“可靠性”和“成本”之间找到平衡点。
我在阿里巴巴做过一个被团队念叨了很久的决定:把 Doris 的默认副本数从三调整为“三加可变”。什么意思?核心业务数据强制三副本,但允许业务方根据自身的数据可恢复性和成本敏感度,选择降为两副本或提高至四副本。
这个决定当时在内部有不小的争议。反对的声音说:这会增加系统的复杂度,不同业务混布在一个集群里,三副本和两副本共存,调度策略要重构。赞成的声音说:如果不这么做,业务方为了省钱会自建存储方案,长期来看系统的碎片化问题会更严重。
我最后的选择是后者。理由很简单:架构师的职责不是替业务方做所有决定,而是给他们提供合理的选项,把每个选项的代价说清楚。有些业务数据是日志类,允许丢失 30 秒的数据,两副本足够;有些业务数据是交易类,丢一条都可能造成纠纷,那就必须三副本甚至四副本。一刀切式的“强制三副本”看似保障了全局可靠性,实际上是把成本转嫁给了那些不需要高可靠性的业务,最终会逼着他们绕开标准架构走野路子——那才是真正的失控。
冗余的策略,本质上是对风险的分级管理。你首先识别哪些是系统最关键的部分,然后为这些部分配置更多的冗余资源。对于相对次要的部分,可以适当降低冗余程度。这样,整体系统的可靠性达到了要求,总成本却比全量冗余大幅下降。
四、从存算一体到存算分离——冗余策略的范式变迁
架构这件事,最怕的不是复杂,是僵化。2015 年之后,云原生浪潮来了,Doris 团队也开始面临一个新课题:存算分离到底要不要做?
传统 Doris 的存算一体架构中,BE 节点同时承担存储和计算职责,节点故障会影响本地数据的可用性,因此必须通过多副本保证可靠性。但存算分离之后,数据主存储被下沉到对象存储(比如阿里云的 OSS),BE 节点变成了纯粹的计算节点。这意味着什么?BE 节点的故障不再导致数据丢失,数据的可靠性不再依赖 BE 节点的本地副本,而是由对象存储底层的多副本机制来保障。
这意味着冗余策略的范式发生了根本性转变。在存算一体时代,你面对的是一个“存储+计算”的复合体,冗余是复合冗余——你做三副本,既冗余了存储,也冗余了计算。在存算分离时代,存储和计算解耦了。存储层的可靠性完全交由对象存储底层保证,计算层可以实现无状态化。某个 BE 节点宕机,直接在另一台机器上启动一个新的 BE 实例,挂载相同的存储卷,几乎立刻恢复服务,不再需要做数据迁移和副本修复这种重操作。
对架构师来说,这是一种解放。你不再需要为一个节点同时承担存储和计算两种职责的耦合复杂度而头疼。你可以单独扩展计算能力(加 BE 节点),也可以单独扩展存储容量(加对象存储空间),冗余策略也从复合走向了解耦。但需要明确的是——存算分离不是存算一体的“升级版”,二者服务于不同的业务场景。存算一体在数据本地性上有天然优势,计算就在存储旁边,I/O 延迟低;存算分离在弹性和资源利用率上明显占优。阿里云文档在介绍 SelectDB 时也明确区分了两种模式的不同适用场景。还是那句话:没有最好的,只有合适的。
五、架构师的边界——从做决定到建框架
冗余这个话题,往深了挖,其实触及了一个更根本的问题:架构师到底该干什么?
我在 eBay 和阿里巴巴都见过一种架构师——所有设计都等着他审批,不签字就不能上线。结果呢?他成了整个团队的瓶颈。我在阿里巴巴工作期间是一个名副其实的“刺头”,批判中台、批判架构师、批判技术管理者,当然也包括自我批判。
Martin Fowler 说过一句话,我觉得一针见血:使团队更加敏捷的架构师,比只做决定的架构师更有价值,因为只做决定的架构师会成为团队的瓶颈。他甚至认为“架构师”这个词本身就不合适,更准确的叫法应该是“向导”——一个更有经验的人带领团队走出复杂的迷雾。
这个观点深刻地影响了我后来的工作方式。在领导 Doris 和 Baza 的开发过程中,我刻意把自己的角色从“决定者”转向了“框架设计者”。我不再逐个审批技术方案,而是花更多精力去建立一套指导原则,让团队成员在原则框架内自己做决策。
这种工作方式的转变背后有一个很现实的考量:Doris 和 Baza 的集群规模动辄数百台机器,数十 PB 数据,涉及网络、存储、调度、监控等多个领域的决策。如果每个决策都要我来拍板,我不但会成为瓶颈,更重要的是——我离一线越来越远,我的判断会比一线工程师更模糊。与其让一个模糊的判断来拍板,不如让一个清晰的判断来承担。
阿里内部有个经验,我深以为然:业务技术部门如果设立了实体架构组,但架构组的人不在执行团队内部、不了解细节,那这个架构组给出的建议很难有价值。不是说架构组的人能力不够,而是说他们离战场太远。
一个好的架构设计,应该让一线工程师在多数情况下能够自信地做出正确的技术选择,而不是事事请示。冗余配置几副本、缓存过期时间设多少、接口超时阈值定多大——这些事情,架构师不该做。架构师该做的是:定义清楚原则,讲清楚代价,然后信任团队。
六、写在最后:敬畏不确定性
回顾做 Doris 和 Baza 这十几年,我有一个很深的体会:架构师最需要的能力,不是聪明,是敬畏。
敬畏不确定性。敬畏失败。敬畏那些你觉得“应该不会出问题”的东西。
冗余为什么重要?因为它承认了一个朴素的事实:人无法预测所有故障,但人可以假设故障一定会发生。冗余不是技术,是一种态度。它不是说你比别人更谨慎,而是说你愿意为系统的不确定性提前买单——而这个意愿,恰恰是区分一个成熟架构师和一个纸上谈兵者的关键。
前阵子和几个老同事吃饭,聊到当年的 Doris 和 Baza,有人说:“李昂,你那时候拍的三副本方案,后来证明是对的。”我笑了一下,没接话。
其实我心里清楚:当时的判断,不是基于什么高深的理论,只是被故障打过太多次。打过一次,你觉得是运气不好;打过三次,你开始反思设计;打过十次,你就再也不信“应该不会”这四个字了。
敬畏,是用损失换来的。而冗余,是对这份敬畏最诚实的回应。
我 2004 年加入阿里巴巴,一直从事运营平台的架构工作。从 Doris 到 Baza,从存算一体到存算分离,从“架构师拍板”到“人人都是架构师”的理念转变,我的核心信念始终没变:没有最好的技术,只有合适的技术。
找到合适之前,你需要先理解冗余的代价。理解代价之后,你才能做出诚实的取舍。
与所有在架构之路上摸爬滚打的人共勉。
作者简介