.. Kenneth Lee 版权所有 2020
:Authors: Kenneth Lee :Version: 1.0
架构设计入门知识
下个月要给一个内部论坛讲一个主题,关于架构设计的,90分钟左右,我用本文来组织一 下逻辑。也算是这个专栏写了这么久的一个阶段总结。
在下面的描述中,我们通过像下面这样的引用发表一些感想:
.. note:: 这是一段感想
这不在整个文档的逻辑上下文中,读者们的阅读思路可以越过它们,维持在没有些感想的 逻辑链上。
首先,本演讲不是想介绍架构设计一步一步如何做,细节的技术手段,完整的理论框架这 些问题。这些问题在一次演讲中也不可能覆盖。本演讲更多时候是想给很多从细节设计中 走过来,对什么是架构设计只有过一些模糊的,不成形想法的工程师,建立一些基本的认 识,理解架构设计的必要性,以及如何看待很多具体的技术对这种必要性的帮助。
从信息论的角度,本演讲不是要提供新的信息,而是要帮助听众已经拥有的信息。这恰恰 也是架构设计的作用:架构设计不是要取代设计,而是在整理设计的组织,让设计的力量 最终指向目标。
以我个人的经验,刚刚开始进入架构设计的工程师,常常对架构设计的整个认识都是错误 的,这些错误的认识会让他们越走越远,甚至最终破坏架构。这是本演讲希望提供的帮助 。我希望通过这些思路上的帮助,让我们身边的工程师更多从架构的角度考虑问题,因为 没有这样的文化氛围,我们很难真正合作起来,也无法让我们多个技术可以持续发展和积 累下去,更无法面对未来的挑战。
我在架构上的很多经验,源自对自己设计过程的反思,以及对后进的工程师的指导,所以 在介绍的过程中不免流露一些“好为人师”的感觉,我并非觉得自己就比听众们高明,只是 如果我不用这样的描述方法,我的讲述就会变成一些经典教材那样——它里面说的东西都是 对的,但听众没有从中获益。这更非我的本意。
架构设计是一个非常抽象的概念,从不同的角度有不同的理解。我们先从一个比较高的角 度来抽象一下这个问题。我们用画画来做个类比,这样比较直观。在画画的时候,如果我 们要画一个人,我们不会一开始就把眼睛的全部细节画出来:
.. figure:: _static/眼睛素描.jpg
因为这样不一定能和我们要画的整个人对得上:
.. figure:: _static/人物素描框架.jpeg
如果我们要画的整个人的姿态是下面这幅图的样子的,上面那个眼睛整个都是错的,因为 角度就不对。
软件和硬件的设计同样存在这样的问题,一个软件,一个硬件,或者一个包含软件硬件的 产品,包含非常多的细节,如果我们一开始就开始“画眼睛”,那么很可能我们经过一个很 长的开发周期,最后发现我们做错了。比如你花了无数的功夫对每个模块进行存储优化, 把运行内存的需求压缩到只有1M,结果最后发现内存在这个设备中的成本只占0.1%,你这 些工作就白干了。这还只是浪费工作量,可能你设计一个复杂的加密手段,但加密后你的 设备就不能运行用户自己的软件了,你这个产品也就废了。
画家可以从一个细节看到一个整体,比如,专业的画家可能从上面那个眼睛,就能意识到 这个人是正面对着观察者的视角的,但不一定是坐着还是站着,说不定他还能从眼睛的放 松程度,能知道这个人不是躺着。一个系统架构师也能从一个产品的特定细节看到这个产 品的全体。比如一个芯片构架师可以从系统总线协议猜到芯片的加工可能用到的制程,进 而猜到这个芯片可以实现的最大内存带宽。一个软件架构师可以从某个API产生了对“设备” 这个概念的关联,从而知道使用这个API的带宽会被限制在一个设备之内,无法被Scaling 。
所以,构架设计工作是一种“形而上”的工作。架构师用细节控制整体。好比画家用线条控 制整个人。架构的描述语言并非它表面表述的严格意思,它是用这个细节去反映某个整体 的打算。当架构师说某个API应该是什么样的,和具体实现的工程师眼中的这个API本身是 什么样的,两人的着眼点是不同的,具体实现的工程师仅仅是为了解决这一层面把参数传 递过去的问题。而架构师设计一个API,他的目的是在这个位置上设计一个“约束”,导致其 他细节设计在这个位置上被限制住,最终不会偏离整个设计目标。
架构上说某个API是get(int),我们可能不在乎实施的时候变成get(int, int)或者增加一 个get2(int, void * ),因为实施这些修改的成本不高(后面我们讲关联的时候再来讨论这 个问题),我们说get(int)只是保证这个地方会有一个“接口层”,具体一两个变化,并不 重要。但如果这个设计上下文是在关注一组已经交付的二进制软件,这个就是大的变化, 因为这时任何一点变化,都会导致整个接口和原来不一样。这就是形而上设计的特征,它 不再具有形而下那种“器”的特点,在器这个角度觉得重要的东西,在它这里不一定重要, 反过来,在器这个层面觉得不重要的东西,在它这里可以很重要。因为两者指向的设计目 标是不同的。
| 深入一点说,这就是《道德经》中说的名和道的区别。
| “道”是包含全部细节的一个整体,而“名”是我们抽象出来的一个特征。
| “名”永远都不会包含“道”的全部细节。我们设定一个约束,
| 我们说XXX(...)都可以,这表示了无数的变数,最后我们就没法交流了。
| 所以我们用了一个“名”,一个形而下的器,一个“具体情形”,
| 来描述这个东西“大致”是这样的,你把它理解为“器”,
| 非要认为它“就是这样”的,这样就把自由度都做没了,
| 你就失去做架构的作用的。
|
| 很多管理者希望把架构工作做成工具,或者把架构过程做成“条条框框”,
| “最佳实践”。但架构工作恰恰就是不能这样干的,因为架构本来就是发
| 明跳跳框框的方法,是发现某个具体情形下,能否设定,如何设定条条
| 框框的一个过程。
|
| 在架构设计的过程中,新的约束不断被发现和发明,
| 主要矛盾会变成次要矛盾,架构提取的“名”也会随之不同,
| 进而策略也要跟随变化,跟随变化就是架构这个工作存在的原因。
| 如果你希望在架构设计之前就设定固定的方法,那么架构就无法
| 发挥我们引入架构设计这个工作原本想起到的作用了。
也就是说,架构师和具体实现工程师设计的东西看起来很可能是完全一样的,但他们的设 计目标可以是完全不同的。就如同上面的两幅素描,可能架构师和实现工程师都会画“眼睛 ”,但架构师关心的是“整个人是什么样的,眼睛的位置和角度是在这个地方”,实现工程师 关心的是“知道眼睛在这个地方和这个人的年龄和心理状态后,眼睛的睫毛如何表达,深度 ,阴影如何刻画”。而且,在实现工程师具体去设计以后,架构图上的那些线,是要逐步消 隐的。
| 说起来,我这里写的这个提纲,也是一种“架构”:
| 我的演讲90分钟,包含更多的细节,但我怎么保证我的演讲可以聚焦,
| 并完整传递一个逻辑给听众?我就需要写一个更短的逻辑,
| 在这个短逻辑中保证我的表述是自洽的。否则我可能絮絮叨叨90分钟,
| 其实什么东西都没有传递出去。而有了这个框架,我中间借题发挥,
| 举很多的例子,深入讲某个例子的细节,都不会偏离我要讲述的主题。
|
| 从架构这个角度说,我的描述如果不能对准同一个主题,
| 这个“产品”整体就不呈现有效的“功能”。
具体对于每个架构设计,对于不同层次的架构设计,我们可能有不同的方法来“绘制”(这 个整体总结),但基础原理都是这样的:用粗线条先勾勒总体逻辑。
作为“形而上”的设计,架构设计的给人的感觉没有那么瓷实,但架构设计仍是一种设计, 它不是脱离逻辑而存在的。我在很多的架构评审或者分析会上,会听到这样的说法:这个 地方说得不清楚,我们后面有详细介绍,如果您关心,我们跳到后面去给您讲?
这样说就是不懂设计了,用上面那张人体架构图来说,我跟你说你这个人的框架没有设计 好,你说我们来看看眼睛?——我不看眼睛,我就要在这层抽象上看到逻辑通了——这才是设 计。设计就必须有逻辑链。否则架构设计就没有意义了,我们还不如直接设计细节。
架构设计是下图示意的一个连续的过程:
.. figure:: _static/架构选择路径.jpg
.. note::
这个问题还有更多的细节可以补充,但再这里加就离开这里讨论的逻辑链了,
我独立写出来::doc:架构设计的大忌:我没错
本节小结:理解架构是一种形而上的逻辑非常重要,因为架构语言中说的一个东西不是那 个东西本来的意思,而是对细节的预判和无数经验的总结。所以我们必须理解,没有足够 的细节经验,是不能成为架构师的。这种经验甚至不一定是某种细节设计的经验,而是对 细节反馈的经验。
作为形而上的设计,我们要理解架构,就需要有足够高的抽象思维能力。这一点能力,在 座的各位,应该都是有的。因为我们这么多年的教育,本来就是在教这个能力,所以每位 大学生毕业生都应该有这个能力。我要提出这一点,是要避免各位在我们后面讨论抽象逻 辑的时候觉得我说得太玄,或者觉得自己还没有这部分知识,要先去“学习”一下才开始深 入思考我讨论的问题,这样我就无法把我的观点传递给你们了。
如果各位(unlikely)是混文凭出来的,我没有什么办法。但我想大部分的人,可能仅仅 学了很多具体的推理方法,没有有意识去这些推理可以被“抽象”为形而上的逻辑。我想提 醒各位的是:不要被这些抽象的概念吓住了,其实这些东西你本来就懂的。
从形而上的角度来说,我们通过“逻辑”进行预判和交流。所有的“逻辑”,本质上是约束的 引入。比如,我们说“你肯定饿坏了,来,吃点面包”。这个逻辑设定了一个判断上的约束 :“你饿了”。如果这一点不成立,这整个逻辑就不成立。你认定了“你饿了”这个约束,你 的“设计”——“吃点面包”——才是成立的。设计需要有约束才能进行设计。
我们做任何一个设计,都必然来自一个约束。就算你写一个“Hello World”,你都引入了一 个“约束”:在我运行这个程序的时候,在屏幕上出现点有规律的东西,证明我们能控制这 个系统。
我们在白纸上画一个人,它也有一个约束:“画个人”。画个人是我们后面做所有的事情的 基础,我们不是画猫画狗,不是画正弦曲线和坐标系。有些约束我们甚至不一定会注意它 :比如我们是用笔来画它的,不是用刀子来刻它的。但这个无法深究,因为我们关注的东 西总是有限的。我们关注的是,我们建起一个逻辑链,可以说:“因为要这样,所以我这样 了”,我们这里提到的“要这样”是什么。
“画个人”是我们的约束,这个人坐着还是站在,是我们的“自由”,但我们选择了一个“自由 ”,我们就增加一个约束了:如果我们决定让这个人坐着,这个人的是手和脚的距离就被约 束了。整个“画人”的过程,本质上是不断增加约束的过程,当这个“人”被画好了,所有的 约束就变成了“事实”,不可改变了。在我们这个讨论空间中,它是“最高约束”,因为你也 不能改变它了。架构设计,是管理这个约束加入的过程,因为先引入的约束,会控制我们 后面引入的约束。由于整个实施的时间线很长,我们要避免不受控制的约束增加过程,导 致后面我们怎么都无法加入剩下的逻辑,最后这个目标就达不到了。
.. note:: 这里理念从“函数式编程”的角度特别好理解,参考: https://zhuanlan.zhihu.com/p/173631835。 架构设计就是函数带参时的情形,细节设计就是Curry化的过程, 每个细化就是消除一个参数维度,收窄整个自由空间。
.. figure:: _static/curry化示意.jpg
f本身是对自由度的一个约束(图中曲面的范围),当我们收窄它其中一个维度(
比如把z收缩为4,甚至可以是某个范围),剩下的逻辑就会简单,但自由度也就
更低了。
用画人来类比的话,我们先约束整个人的“姿态”和环境光源的特点(点光源还是面光源等 )和位置,我们就决定了眼睛,手脚的姿态,透视和光影了,这样画眼睛和手脚的时候, 我们知道如何刻画细节。但如果我们先画眼睛和手脚,各自有自己的姿态和对光源的理解 ,这个眼睛和手脚放到一起,这就不像是个人了,这个“画人”的目标就永远不能达成了。
这种一个约束,控制了其他约束的情形,在架构的语言中,就称为“关联”。关联就是说, 如果我修改了一个逻辑的“约束”,其他逻辑的约束也会跟着更改,那么,这两个逻辑,就 存在“关联”。否则,我们就认为他们没有“关联”。
关联不是我们一般工程师想象的那样,互相有函数调用,有消息通讯就叫关联(这些只是 关联的一种)。关联是个哲学的概念,常常出现在语义上的,比如提升速度的要求,导致 应用方法的改变,介质层和会话层也有关联。我们关注某些东西有没有关联,我们关注的 是在我们讨论的那个逻辑角度,我们是否在乎它们的“共同修改”。
协议分层,就是一种典型的分离关联的方法。比如TCP/IP的网络(IP)和链路(link)层,IP 层只知道IP地址,反正我告诉你我要发到这个IP地址去,其他东西我一概不知,所以你用 令牌环还是Ethernet层我的逻辑都是通的(约束是IP层接口),无论你是UDP还是TCP通讯 ,都和MAC地址无关,我也不知道你在传输数据的时候需要有一个Token在介质上分发。但 丢开这些信息不管,只要你能在link层上向我保证每个数据报是无损的(但可丢失),基 于这个约束,我就有足够的“依赖”(其实就是约束的别名)来建立我的逻辑了,我可以和 对端用端口区分应用,可以用滑动窗口来保序来防丢失,可以用特定的原语进行连接建立 。这都是看我要把某些约束放在我这一层,还是放在别人那一层。而决定这一点的,是有 哪些重叠的约束,我们通过分解多个独立的逻辑链,保证每个逻辑链的约束都不多,这样 我们保证这个逻辑链的自洽的成本最低。
.. figure:: _static/协议分层.jpg
分层以后,下层多一个“路由”的概念,不会改变高层原来那些“握手”,“鉴权”这些概念, 下层和高层在各自的“约束”和“依赖”中可以独立构成完整的逻辑,而不需要每次都把逻辑 细分下去。
架构设计本质是对约束和关联的控制,让不同的“目标”承载在不同的逻辑上,减少跨逻辑 的关联。比如你当然可以把“低功耗控制”这个目标放到TCP层,但耗电多少这个约束是在物 理层手上的,你要建立这个逻辑链,就要从物理层拿到这个约束,TCP和物理层的“关联”就 加重了。但如果你完全把低功耗放在物理层,TCP的流被分散在每个SerDes链路上的,物理 层对此无能为力,它也没法把功耗降下来。这样,我们就可能需要一个Channel的抽象(我 这里只是比喻,不是现实),让IP“仅”知道合并一些数据流有助于功耗的降低……
.. figure:: _static/关联示意1.jpg
这个整个设计过程,脱离具体的具象就架构设计谈架构设计,根本没有结果,我们之所以 这样分解模块、约束和关联,完全看我们面对的具体约束是什么。这里,起关键作用的, 是我们的“目标”,也就是那个“画人”的根本约束。除了这个根本约束,其他的约束都是设 计者自己引入的。我们要避免作茧自缚,就必须不断对比这个“目标”,看看什么真的来自 这个“目标”,哪些只是我们图建逻辑的方便,自己强行引入的。所以,如果你没有学会其 他深入的架构方法,那就保证你学会两点:
第一,永远把你的原始目标,放在你的设计的最前面。时时刻刻对比你是否针对了这个目 标进行了设计。
第二,无论你做什么层面的抽象,保证你的逻辑在这个层面上逻辑自洽。换句话说,任何 时候,都要问:我这个逻辑,是否是在我设定的约束和条件下,最好的选择,是否所以可 能发生的情形都被考虑到了。后面一点是基础,这解决你的逻辑是否有破绽,你做一个 get(int)的函数,这个int所有可能的取值,都应该在你的考虑范围内,而不是仅仅考虑 int>0的情形。但另一方面,int>0这个约束同样可以来自你高层约束,而不是来自这个变 量的自然约束。
前面说过,我们架构设计的作用,就是降低每个独立设计逻辑的依赖和难度。而这个难度 ,就在这里这个“逻辑自洽”上。你基于IP这个抽象做TCP这个协议,下面到底是Token Ring 还是CSMA/CD,你完全不用管,你的逻辑链要穷举所有的可能性就是比较容易的,否则你 根本无法保证你的逻辑链是严密的,因为要穷举的可能性太多了。
如果再考虑技术发展(比如现在SSD存储很贵,明年技术进步,整个成本变了呢?),工程 师的能力逐步提升,开发阶段和销售阶段的要求不同,行业,政策等态势的变化,你就会 发现,做一个“严密”的逻辑链,是非常非常困难的事情。我们只是在降低风险,不是在找 一种“必然”,但没有这种基本的降风险设计,说要做成一个产品,完全是瞎扯蛋。
本节总结:我们要保证一个设计是设计,必须首先保证这两点:对准目标,逻辑自洽。不 能保证这两点,你的所谓设计就只是梦游,还不如直接进行形而下的具体设计。而且,这 还不仅仅是浪费时间,而是引入无效约束以后,完全限制了整个构架的的发展。
DFD/STD方法是软件发展早期,存量约束很少的时代的产物,现在用它的人已经比较少了。 我们可以不用,但它包含的原理其实是非常重要的,对于我们建立构架思想,或者在做具 体的模块级的设计仍是必须掌握的基础能力。
前面说过,构架设计本质上是找到核心约束(目标),然后基于这个目标引入后续的约束 。那么,对于计算机系统,它的核心约束是什么呢?
答案是:把一种形态的信息,转化为另一种形态的信息。
播放电影的一种实现是把视频编码转化为显存数据和音频数据。
客户习惯学习是把用户的购买和浏览信息,加上商品的分类转化为用户的兴趣商品分类
电梯控制是把多个不同层的上下请求转化为电梯的运动和停止控制信号
……
这是计算机作为一种“信息系统”的唯一作用。
除此以外,没有其他核心的控制要素。
电影编码一定要H.264吗?一定要VC-1吗?对用户来说不关心,关键是能把视频数据转化为 图像。图像一定要用电视吗?一定要用液晶屏吗?也不是核心约束,你可以再选。唯有把 视频变成看得见的东西,才是核心约束。
客户习惯学习用户的购买信息需要用网络传输吗?还是直接把硬盘连过来拷贝?还是到了 晚上夜深人静的是时候InfiniBand同步?这都是自由度,不自由的是你总得把客户看过的 商品,看了多久,用的IP地址或者ClientID给我,这我才能从中提出这个兴趣出来。
这是DFD方法的核心。DFD方法是从这个角度来设计约束的:用户要我把什么转化为什么? 我有足够的数据进行这种转化吗?不够的时候,我从哪里补上这些数据?这种转化的逻辑 ,可以分成多少个独立的逻辑?这些逻辑可以分别留在哪些层次、模块里面独立管理?
这就会产生DFD图,所有数据逻辑都通了,剩下的逻辑就都是“我拥有这几个数据,如何转 化为他们要的那几个数据”的问题了,这整个就是个数学问题。
.. note:: 应该说,我们大部分时候做架构最好的进展, 就是能把一个问题变成一个数学问题, 因为这样这个问题就可以做到最严密了。 比如下面提到的状态机问题,就是典型的数据问题提取, 如果我们能建立这个模型,基本上我们就不怕有什么错了。 但真正让人害怕的是比如指令集,虚拟化这种设计, 状态和条件无数,几乎就没有数学方法来完整判断我们做 的是不是就真的没有破绽了。
而状态机方法是DFD方法之外一个完全独立的方法。一般用来处理接口破绽的:DFD建模完 成后,你就要设计外部接口,而外部接口是否可靠呢?你只分析了你这样提供功能是可以 给用户返回正确的结果的?但是否有情形导致你的输出是不对的?用STD方法你可以穷举所 有可能的输入和你当时所处的状态,这样你对你的逻辑链是否有效就比较有把握了。
DFD和STD是脱离所有进一步“自由”之外的核心约束。它和你用什么语言啦,怎么分模块啦 ,接口是消息还是调用啦,这些人为约束没有任何关系,你爱怎么设计就怎么设计。这种 在现在这个“后软件时代”是不太合适的,现在的软件太完善了,你决定选择用mysql来存数 据,你就得接受用一个X protocol来连接它,选定了x protocol,你就要选语言,就得选 OS平台,然后你的输入就得满足关系数据库来描述你的持久化数据……在系统层面,现在你 的依赖都是成片成片的。这个时候,你还纸上谈兵地说“我的核心依赖是……”就很假了,你 的物理依赖其实一开始就已经被决定了,这时自由度更高的4+1视图方法就更容易被接受了 。
但即使我们有4+1视图方法,我们仍有必要好好去学习一下DFD/STD方法。因为这是4+1方法 的基础,它仍是我们整个信息系统真正的核心约束的提取方法,比如前面这个问题,mysql 真的不满足我们的需要,我们可以换掉mysql,换掉关系型数据库,换掉os的,这是左右我 们创新的基础,4+1是下一层的方法,它接受了“人为约束”是约束,不考虑打破这个约束, 但DFD方法是真正在控制一个“期望”(用户需求)如果被自由设计的时候,可以如何创新, 如何改变现在的约束。
4+1视图是后软件时代的产物,就是软件已经极度丰富了,你随便做点什么,都是在已有的 软件的基础上做的,而且常常你还不能一次想好怎么做,都是见一步,走一步的。这需要 另一种控制架构的方法。
4+1视图是把最关键4个全局控制拿出来,然后一个功能,一个功能拿上去推演,看约束和 逻辑会不会冲突。所以它不叫“五大视图”,因为Use Case不算一个完整的逻辑。Use Case 只是一个持续补充的,不完整的“需求列表”。我们用这些单个的需求去试试,我们的引入 的设计逻辑,是否是可以完整自洽的。这个“完整自洽”是要落实在另外那四个视图上的。
4大视图在关注设计的哪些方面呢?首先,逻辑视图,我又把它称为概念空间建模,这其实 关注的是用户接口。所以我有个建议:如果你的逻辑抽象能力不那么强,就先别去学那么 多复杂的方法,你直接试试写用户手册。
你想办法用真心服务客户的态度,给使用者说明怎么用你这个系统。真心服务客户的态度 不是让你学电商那些机器人一样的客服,态度一流,就是不给你解决问题。真心服务客户 是把客户当战友,战友的阵地守不住,你也一样死。这样你才会深怕他听不懂,而不是故 意用你那些专业术语在他面前装逼。
概念空间建模很多时候不需要详细到用户手册这个层次,比如我这里这个文档也是概念空 间建模:
:doc:`../概念空间分析/binfmt`
我重点抓住的是一个异构的程序在原来Linux对同构的程序在进程线程管理,内存占用,异 常和中断处理,调试等问题上是否还有原来的意思。这如果你还分不出哪些概念才是左右 发展的,那些是细节,那么用户手册是最容易实施的控制概念空间的方法。
你要介绍一个数据库,什么是用户,密码,连接,数据库,表,行,列,条件,index……你 自然是要说清楚的,这个东西不通,你的代码怎么可能可以通?
你要介绍一个加速器,什么是设备,虚拟设备,上下文,通道,引擎,请求,响应,同步 ,异步……这些你不用心说清楚,你怎么可能说得明白请求你一个虚拟设备复位,会影响多 少个应用程序?
这个设计是躲不过去的。如果你这个逻辑没有说清楚,直接就做设备初始化,软件上下文 分配,中断处理,等你把这些功能都细化起来,你跟我说你能保证逻辑不冲突?你开玩笑 吧?
第二个我们关注的视图是开发视图,这是开发和维护的组织。最基础的一个模型是部件分 解。前几天我评审了一个硬件设计,要求在总线上设计一个“地址分配器”,我问,你这个 分配器工作在哪里?他说,可以在总线交换机上,也可以在其中一个服务器上。
我就说,这就是两个交付了:在总线交换机上,这你要求总线交换机有一个CPU,可以跑这 个程序,这个系统的OS应该是RTOS,我们就要做RTOS的选型,这个分配器必须和这个RTOS 绑定升级,而在每个节点上那个,必须是一个节点应用,很可能是一个服务器OS的Daemon 或者内核模块。后者我们有自由度可以频繁升级,前者不行。两个都支持就要用短板做约 束,这个地址分配算法必须相对稳定……开发视图就是抓这种逻辑的。
但部件分配仅仅是开发视图最容易的部分。现代大型商业软件这个部分最难的是这些部件 的升级和组合关系:一个系统包含多个部件,把维护时间拉长到几年,每个部件都会发生 升级,升级以后是否可以互相匹配?版本数量减少,用户升级自由度降低,版本数量增加 ,维护成本大力增加,修改一个Bug,要测试120种可能组合,每个测试先要加载12个小时 网络才能运行起来,你怎么玩?
这个问题常常是我在架构设计中工作量最大的部分,很多架构师碰到这个问题就退了,就 开始“抽象”了,忽略版本,用一条虚伪的线和抽象的框框来代表这些变体,实际情形就是 到时谁要得急就做谁的,等到最后展开无数组合的时候发现搞不定了,就只能解释“就是这 样的”了。所以你看到很多产品只能撑住POC,为了性能可以换个kernel,换个编译器,换 个库,“性能提升30%,支持A功能,B功能……”,其实这种市场转眼就会丢失,根本没法维护 。复盘的时候说:“都是因为用户不肯用我某个版本……”,扯吧,你整个交付逻辑就不通。
比如这种:
.. 市场演示构架图.jpg
这种图形是市场部用来和小白客户沟通的,你拿来当“设计”?你设计什么了?为了让市场 人员可以吹这个牛,架构师是要把所有可能的版本组合全部推演出来,变成开发部的工作 量的。市场吹一句话,是开发部几百人月的投入,你架构师也他么只吹一句话?那怎么可 能可以处理这些组合呢?结果肯定是只能有一个组合可以用,其他组合都纯YY么。
这种把市场吹的分层逻辑,变成一组版本和版本直接的配套关系,就是开发视图要解决的 关键问题。
第三个视图是处理视图。这个模型一般用来分析并行化,一个业务逻辑,有些逻辑是必然 串行的,你加CPU,加节点,加加速器是没有用的。这种东西,你总得合并在一个流中。还 有些业务不是这样的,你如何分解逻辑?哪部分分解到什么节点上?
这是个纯算法问题,我这里倒没有什么可以补充的。但可以看到,它其实也是一个比较核 心的抽象逻辑,和需求强相关,受人为引入约束的关系也是比较有限的(当然没有DFD那种 约束那么硬,因为它不是个功能问题,而是个性能问题)
这个视图一般没有什么工具,我自己做出来大部分时候都是ER图(ER图其实可以画成对象 图的)和大批的文字说明。
最后一个视图是部署视图,我个人对这个视图用得不多,因为一般前面三个模型已经完全 限制这个视图的选择了,这其实没有设计的必要。当然,这完全看你进程做的是什么设计 ,对于比如“数据中心建设”这样设计来说,部署视图的价值就会大于开发视图。
可以看到,4+1视图方法其实是一种学术上不那么严格的实用方法,但它却是我们进行一个 大型系统需要考虑问题的最基本约束考量。否则,想想你可以如何规整和逻辑完整地考量 你一个设计到底缺少了哪一环?怎么能确定你的系统可以持续用下去?对于每个具体的设 计,可以你会使用其中一部分视图,或者你会引入其他视图,比如可靠性设计,安全性设 计,这些都需要额外的视图来表达的。视图本质是一个独立的角度,保证我们最开始要解 决的问题:某些问题,一开始进入细节,我们必然会走偏,会留下巨大的破绽(安全性是 最典型的情况)。
架构的目的是在混沌中制造规律,使我们可以控制庞大复杂的系统(参考:
:doc:设计的减熵原理
),自然发展的东西总是混沌而没有规律的,好比一个树林,肯
定是看不到一点平地的,而人控制自然,就会磨平这种复杂性,会制造平直的地面,见方
见圆的房子,这样人的脑子可以对它们有很简单的预期。简单是设计的目的,复杂是“专业
”的无可奈何。这时很多从设计进入架构的工程师意识不到的问题。
在本演讲中,我们初步为听众点出架构设计的关注重点是什么,这一点我认为比掌握一些 具体的工具使用方法重要得多。作为形而上的设计,方向错了,完全起的就是反作用。我 希望这个演讲,能让更多的工程师感受到架构设计的重要性,在进行设计的时候,总从架 构的角度做一些建模。如果你实在觉得头绪太多,那就从用户手册开始,至少先抽象出来 ,你在一个什么上下文中中,对外的依赖是什么,你提供了什么功能……你看到了整体,你 才能看得到你的细节,否则你总在你的细节中出不来,这终究是走不远的。
这个前提,首先其实你是的心确实是在这个设计上,而不是给你的同事,领导“证明”你不 差,你很厉害,不是你的错误,你没有功劳也有苦劳……这些小聪明,能帮你一时,终究不 能帮你做成真正的难事。人生苦短,去日苦多,你终究会后悔的。
最后推荐一本书吧:in nek:推荐一本学习架构设计的书。