仓库源文

.. Kenneth Lee 版权所有 2020

:Authors: Kenneth Lee :Version: 1.0

架构设计和实施的对齐和同步问题

本文是这个文档的逻辑延续:

    :doc:`早期架构设计问题`

上面这个逻辑写得高大上,但操作起来其实是有很多具体问题要解决的,没有这些操作策 略,实际写作架构方案的同学会觉得不知道如何下笔。本文回答一下在实际实施中的具体 问题如何解决。

首先我们要明白,一个真正的架构设计是必然涉及很多产品,部门,团队的。有不同意见 也是肯定的。你非要“严谨”,这个东西就不用做了。你说你要开发某个版本,和开发经理 沟通了吗?他们的人力排得过来吗?他们的开发预算能按时到位吗?就算到位了HR能按时 招得到人吗?Marketing肯卖这个版本吗?你去找他们沟通,但你的方案十划没有一撇,讨 论什么都是扯淡,而且你跟开发经理讨论好了,Marketing一定同意吗?你自己的态度轻如 鸿毛,你叫得动谁?

架构设计是滚雪球,我们架构团队自己先滚雪球,达成一致,然后去和开发团队妥协,滚 大第二个雪球,然后再用这个大雪球去碾压Marketing,然后去碾压FAE,碾压预研,碾压 金主……这是做架构的常态。架构设计一开始就准备好了要去妥协的,如果你非要有洁癖, 要求所有人都“一致”,你不用做了。所以,我们设计方案的时候总是站在一个“无私”的角 度上来进行设计,让其他人反对你就是和自己的核心利益作对,这样你妥协的余地就大。 而在做“无私”的设计的时候,逻辑限制是第一位的。大家都想要钱,但钱不能从天上掉下 来的,你总得是个产品吧?软件产品是个软件,总得编程吧?编程总得用一个编译器吧? 编译总会产生二进制吧?有了二进制总会在一个ABI兼容的环境上使用吧?……这些就是逻辑 ,谁都越不过去。你可以说我不用gcc而用llvm,我们无所谓,但前面那个逻辑还是改变不 了,但你强调用gcc,我们接受后,它就变成一个限制了,这个限制会拒绝我们链接llvm的 库,这可能又有人反对了。你说两个编译器都支持吧,很可能开发和测试团队就找你拼命 了。架构团队的工作很多时候不是自己有什么观点,而是保证其他团队的观点冲突完了, 我们的逻辑还是能做出一个能赚钱的产品来(当然,也可能是其他收益)。

所以,我们写架构设计,不要担心写错,你任何时候都是在有限信息上做决定的,只要没 有明显的信息缺失,你就要猜和做判定,这是前提。但你先要保证你的逻辑是通的, 后面 有人反对,你可以妥协,但妥协也不能跨越逻辑啊。这样我们就有一个越来越复杂的逻辑 沙盘,在实际执行的时候,我们遇到问题,我们就知道我们向哪个方向去妥协了。

做架构设计需要有“公心”,架构团队不是独立于其他人的团队,你不能“责怪”其他团队, 你就是他们,代表他们所有人做决定(甚至你不一定需要他们完全同意),如果你站在你 一个小团队的角度上想问题,你就失职了。

好了,这是架构设计的其中一面。理解这一面,你至少应该可以动笔了。但只有这一面远 远不够,我们需要考虑另一面:架构设计不能妥协的部分。

很多团队可以没有架构设计,他们收到需求后,就开始做PoC(Prove of Concept),写一 些基本的代码,做一个几台机器的小集群,写几个前端,几个后端,看能跑通了,就开始 正经收集需求,每个具体的工程师解决一组具体的问题,比如提供鉴权啦,日志啦,可靠 性冗余啦,集群变大的时候就近做Cache啦等等,这种开发被具体的需求驱动,我把这种开 发模式叫做“没有架构设计”,或者“放养式构架”。

这种模式就好像开一条大船没有船长,这些人肯定不会撞到看得见的礁石上,但等到闯入 风险海域的时候,你看见什么都不好使了。

我们做架构设计的目的就是提前看到比较远的风险,比如你的硬件肯定是一代代做的,第 一代卖出去了,第二代能否直接兼容第一代?能否让客户的机房混用你的多代设备?要做 到这一点,你就会引入设计约束:新硬件后向兼容。但这个约束说来容易,在现实中其实 非常困难,因为让硬件完全后向兼容,你的硬件的改进就受限了,如果修改你前面接口中 一个bit,就可以提升30%的性能呢?如果要同时支持两种新旧的接口需要增加芯片30%的 DIE面积呢?

所以,为了做到这个,你可能需要把你承诺“不变”和“可变”的逻辑分解出来,把“不变”的 部分做成你架构约束。这是在“治未病”。这就是“有架构设计”和“没有架构设计”的区别。 你完全靠需求来驱动,肯定不会考虑这种问题的,架构设计的作用就是提前考虑这种问题 ,在早期就开始抽象整个生命周期上的变化。所以架构设计总是从“概念空间”开始建模, 因为概念空间是最难改变的,你决定每个设备分成多个虚拟设备,以虚拟设备为单位分配 给虚拟机,这个东西无论你的虚拟设备是通过消息访问还是mmio访问,都很难改变。但具 体这个消息是同步的还是异步的,这个东西就比较容易改变了,至于函数中加一个参数和 减一个参数,就更容易了——虽然这种容易也建立在很多技巧的基础上。架构设计是通过概 念空间建模,找到整个发展中最“难变”的部分,基于这个点来做约束,让这个约束得以坚 持,从而收获那个未来的利益。

所以,你看,我们做架构设计都是做未来,做未来天然就会和现在的利益冲突。而且这个 冲突,没有人会和你平衡。因为考察整个利益关系中,只有架构师这个职责是为未来负责 的。如果你只遵循我前面的“道法自然”的思想,人家说啥你都可以妥协,那么,你这个“未 来”,就没有人给你背书了。

这样,架构设计就变成“没有架构设计”了。架构师的价值就不存在了。

所以我给一个产品团队服务的时候,我首先会去确认这个产品团队的管理组织是否认为自 己“有问题”,是否知道自己“强敌环伺”。如果这个组织觉得自己的开发一切都正常,“做得 挺好的”。那我是不会留在那里的。如果你按部就班被需求驱动就能让你觉得安全,一切都 Perfect,你不需要架构师。架构师只能给你没事找事。

换句话说,架构师是为你的现状做出改变的,你自己都认为没有必要改变,那我们就不要 在这里浪费时间了。

这样,架构设计就需要一个妥协的底线。我们还是讨论前面这个例子,我们希望我们做出 一个架构约束,实现未来硬件可以在用户现场混用。那我做H1硬件配合S1软件的时候,我 就会给出H1_if,保证S1对H1的最小依赖。现在我做H2,我要求H2兼容H1_if,新功能做在 H1_if+上,然后我开发S2支持H1, H2。这里,我增加了设计难度,但我买来了这个收益: H2可以直接取代H1使用。这个是否真的收益,其实要看市场域的,但我们假设这个有受益 ,我们在架构设计中坚持了这一点,买下这个未来收益。

但执行的时候这个可能很困难,假如我们没有做到,我们可以妥协,我们退一步:H2_if无 法兼容H1_if,S1无法支持H2硬件,但我们可以保证S2兼容H1和H2。这样收益没有那么大, 但至少用户还是可以混用H1和H2的(但需要升级软件)。

好,假如你还是做不到。我们再妥协:H1必须使用S1,H2必须S2,两个解决方案还是可以 混布。至少H1S1和H2S2可以互通(比如假定是一个RDMA协议,两者高层互通协议得通吧? )。

但这个也做不到呢?那你的架构设计就失败了,你连“放养式架构设计”都不如,那个没有 获得H1和H2混布的好处,至少也没有浪费时间在跟着你“设计未来”,养了你这么个废物, 开发额外的代码。

不少号称的架构师都是这样的废物,他们做的“抽象”,“封装”经不起被封装的接口的改变 ,或者那个位置根本就不发生实例的改变,他们说一些说了等于没说的概念和原则,这些 概念或者原则的效果要不和放养式设计没有区别,要不就只起反作用。这些就是做了个架 构的“样子”。但架构需要“收益”背书,不是一个“样子”。

要避免这个问题,就是设计功力的问题了。但作为一种实践,我的意见就是:在你的架构 设计文档最前面确定好你要解决的问题,说明你的设计想要得到的核心收益是什么,你的 所有设计(包括你后面的妥协),必须用这个核心收益作为底线。你可以多放几个这样的 核心收益,这样你妥协的机会就大很多,搞不定一个,可以看看能否搞定其他的,等你所 有这些收益都“妥协”完了,没人赶你走,你自己……看你要不要勉强了。