购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.1 基本论点,别输在起跑线

3.1.1 从4方面认知软件系统

1.软件系统的涌现效应

软件系统的第一个特性就是涌现性,或者称为涌现效应。软件是一系列实体和这些实体之间的关系所构成的集合。那么,涌现是何含义?涌现是指软件系统最终呈现出的功能要大于组成软件系统的各个实体的各自功能之和。

古希腊哲学家亚里士多德的名言“整体大于部分之和”,是古代朴素整体观最有价值的遗产,至今仍是现代系统论的一个重要原则,这与涌现效应有异曲同工之妙。可以从以下两个视角来理解涌现。

第一,系统的每个部件如果独立存在,其功能并没有实际价值,因此显得微不足道。但把它们拼装在一块儿,就会由若干没有价值的0突然变成100,这即是在功能上的涌现。

第二,大家应该听说过技术奇点这个词,技术奇点是一个假设的未来时间点,技术增长变得无法控制和不可逆转,从而导致人类文明发生不可估量的变化 。对于AI大模型而言,其参数在达到几十亿时,模型智力会突然出现一个向上的跳跃,这就是跨过奇点之后的能力突变。所谓大模型的玄学,正是如此。

很多现象就发生在眼前,对我们来说却如同雾里看花,难以捉摸。在人类科学高度发达的今天,仍旧无法用数学或者其他公式解释和预测涌现效应,这就是软件系统的神秘之处。

软件系统最关键和最明显的涌现物一定是功能,既包括意料之中的,又包括意料之外的。意料之中的不难理解,即指满足业务需求的功能。意料之外的功能,可以有很多种类,例如,可以是进入一种未设想到的分支,体现出其他的运行结果(或现象),也可以是一些异常,当然也包括软件Bug和难以预测的运行故障,从本质上讲,Bug和故障也是软件系统的特有组成部分。

除了功能之外,系统还会涌现出性能等各种质量属性,以及其他附加价值。大家应该对此有所体会,做系统时没有觉得某个功能到底会怎么样,在交付使用后,需求方才会发现计划外的、潜在的价值,并决定以后应当如何延伸。这是系统带来的附加值,是对其他方面扩展的一种牵引力(或是拉动)。

2.软件动态性

软件系统有2种状态:一种是静态,另一种是动态。静态指的是在架构设计中所看到的功能态,动态指的是进程部署和启动之后的运行态。在动态中,又分为变化态(也就是过渡态)和稳定态。

如何更深刻地理解软件变化态和稳定态呢?可以用汽车发动机打个比方,启动的瞬间,发动机处于变化态,启动后运行平稳了,进入日常行驶时就是稳定态,也可以称为常态。软件系统有一个设计难点,设计者常用视图去描述所设计系统的功能组件,E-R图、组件和连接器图、部署图……各种视图无处不在,但很难通过这些模型去描绘变化态。这其实如同绘画跟视频的差距,任何软件设计视图,都是偏向于描绘稳定态下的结构,但是从开始运行到进入稳定态的过渡过程,很难通过静态视图描述。

要描述变化态,常用的工具是曲线,但是曲线多是基于横纵坐标。例如,横坐标是时间,纵坐标可能是电压、转速、温度等重要的指标值。曲线能描绘某指标的动态性,但是并不具备全面描述模型的能力。架构设计所呈现出来的内容,若能“动静结合”,在模型视图的基础上,有侧重地描述出值得关注的动态,体现关键要素的变化,是更高阶设计能力的体现。

软件动态性还有一个解读视角是强调软件的变化。时间可以改变一切,过去看重的方法,一段时间后可能会被自己否定。“唯一不变的是变化”,这条真理名言很值得架构师和技术决策者铭记。架构师在设计时,对实现与设计完全一致会抱有过高的信心,因此时常会陷入应当投入更多精力的误区。开发团队会认为详细设计已经覆盖了所有的方方面面,这也是一种错觉。实际情况是,事物的发展总和想象的不一样,始料未及的需求变更、沟通中的信息盲点、某种客观限制、某人古怪而拙劣的代码等问题不时出现,而且周而复始,永远无法杜绝。

软件的这种变化,本质上是(对任何事物都存在的)偶然复杂性。例如,进屋子就需要开门,就会有灰尘被带进来,能够控制灰尘的因素对于我们人类的日常生活来说完全是偶然的,与气温有关、与空气质量有关、与开门的速度有关。偶然二字,强调了不存在绝对意义上的方法去杜绝它的发生。对于软件而言,偶然复杂性指非可控因素会带来无法预测的触发条件,导致软件系统的非预期变化。

一旦出现变化,只能不断应对。在不断变化的情况下持续修复、完善软件的过程,要求技术设计工作本身必须保持灵活性、连贯性。如果说很多企业的架构部门没有达到预期成果,很多情况下原因在于他们的架构工作太过静态了,面对突如其来、不可预计的变化,缺乏调整和演变能力。

3.软件的复杂性

软件系统的复杂性主要有两方面:表面复杂性和本质复杂性。

(1)表面复杂性。名字即代表含义,因此不需要再解释其概念。例如,图3-1所示为两个系统各个端口的连接关系,左边连接关系的连线很乱,给人感觉很复杂,但实际上只是一种表面复杂性。经过梳理,立刻呈现出右边有序的、相对简单的状态。软件设计的一个主要职责就是梳理各种关系,识别系统的表面复杂性,进而去简化它、破解它。

图3-1 两个系统各个端口的连接关系

(2)本质复杂性。也可称为客观复杂性。只要向软件系统添加功能或质量属性,就是在添加矛盾,会驱动本质复杂性的发生。

本质复杂性并不难理解,大型系统的规模大、投入大、参与方多,利益团体协作关系复杂,对于团队硬实力、软实力的要求极高。随着系统数量增多,技术决策的难度也将呈倍数增加:追求微服务的灵活性,其代价是增加系统通信开销以及管理复杂度;提高安全性,除了牺牲性能外,还可能会降低用户体验;加强质控流程,会影响交付速度。这些即是本质复杂性的具体例子。

软件设计与开发属于知识型、创造性的工作,过程中存在大量的主观性发挥。这也是本质复杂性的来源,其结果是很多工作难以量化评价,潜在问题没有被揭示出来,成为“水面下的冰山”,最终造成进度未达预期、系统故障等各类(问题)结果。

4.极端斯坦与黑天鹅现象

极端斯坦指个体能够对整体产生不成比例的影响。从统计学角度来说,极端斯坦是因个例而导致统计结果的巨变。

用10个人的体重和财富来举例,如图3-2所示。以他们的体重作为统计对象时,即使某个人体重再大(例如150千克),也不可能把全部10个人的平均体重拉高特别多。所以,体重不符合极端斯坦理论。当统计对象换成财富值时,可以看到,只要有1个人是超级大富翁(例如拥有上亿资产),就可以把这个群体的平均值拉高上万倍甚至更多。即个体财富的影响力足以影响整个群体的结果。所以,财富符合极端斯坦理论。

图3-2 10个人的体重与10个人的财富

极端斯坦带来的一些社会性现象,包括个体价值的突破性和事物发展的跳跃性,一夜暴富、赢家通吃、富可敌国……正是这方面的代表。极端斯坦多数时是贬义词,暗指人们的生活(甚至是生存)会受到单个事件、意外事件、未知事件和未预测到事件的统治。极端斯坦是造成黑天鹅现象发生的理论基础。

复杂系统平台的特点,其实是与黑天鹅现象相吻合的。例如,一个环节的问题会被多级放大,演变为严重故障,一次故障可以引发管理层地震,一个小Bug可能会导致大家都无法承受的结果。

一个病毒可以感染成千上万的计算机,一句话可以在互联网软件上瞬间被传播、炒作。这就是软件世界中的极端斯坦。如果没有软件病毒,要破坏一群设备,只能挨家挨户提个锤子去砸;如果没有网络通信系统,即使喊破嗓子也只能将语言传递给周边10米半径内的人。如果说在古代社会时,人类生活在平均斯坦 的世界,那么电子技术将我们带入极端斯坦的世界。

3.1.2 技术债务与架构适应度

1.技术债务与债务奇点

当功能需求与质量属性出现矛盾时,是用更多的时间一次做好,还是先做妥协?一旦选择妥协,就积累(或留下)了技术债务。掌控技术债务,是三大架构基本活动(具体见4.5节)之一。做妥协并非下策,但要积极捕获技术债务、主动记录和跟踪技术债务,并在合适的时机和预算下偿还技术债务,避免技术债务积累对系统造成的风险。所谓偿还,并非是对错误的整改,而是指假设回到最初,资源和时间都充足的条件下会采取的方式。

3.1.1节讲解了技术奇点的概念,既然有技术奇点,当然也存在债务奇点。事物发展到达了奇点后,会突然地发生巨变,可能是能力的向上涌现,也可能是向下的坍塌与毁灭。当质量属性问题积累到了奇点,软件系统立刻变得千疮百孔、不堪一击。换句话说,债务奇点意味着质量底线,如果底线被打破,则无法再保证(业务需求交付与系统质量属性)两者间的平衡关系。

因此,积极地管理债务是整个架构保持可持续发展、演进的重要原则,这需要架构师能够保持合理的风险偏好,善于审时度势,既有洞察力,又不缺乏执行力。能将技术债务控制在合理的范围内是高阶能力的体现。

2.架构债与架构适应度函数

如果将技术债务进一步分类,可分为代码债和架构债。对技术债务的广泛研究和工具实现,较为集中在代码债方面,但是用于发现代码债的工具和方法(例如代码质量检查、代码漏洞检测)通常不适用于发现架构债。架构债涉及非局部关注点,因此更难以被根除。在这个领域,有一个专业词汇叫作“架构适应度函数”,可以此进行架构有效性检验。

使用适应度函数检测架构债,具有大量的成例可以参考,常见的包括:一是不稳定的接口,方法是搜索具有大量依赖项且经常与其他文件一起修改的问题;二是违法模块化,方法是搜索一起频繁变更的、两个或多个结构上相互独立(意指彼此之间不存在相互依赖关系)的文件;三是不健康的继承,如果父类依赖子类,或是一个调用者同时依赖父类和子类,则属于此类架构债;四是循环依赖或者派系依赖,对一组紧密相连的文件进行搜索,发现其中是否出现循环关系,这需要一点算法技巧;五是包循环,检查存在相互依赖关系的多个包。

适应度函数给人的感觉有点像是执行某个测试动作的函数,这其实是错误的理解。测试是一种正确与否的直接判定,而适应度函数很多情况下是用于架构特征的评估、衡量,体现了演进性、持续性、动态性。除了检查开发人员是否保留了架构特征,适应度函数存在的意义还在于帮助架构师解释什么方案更好,以及何时能达到目标。具体方式可以是使用适应度函数执行真实运行数据采集的任务,了解性能、可靠性、安全性等质量需求的实际达成情况。此时,适应度函数履行的是监控职责,有点像是对系统做健康程度评估。

另外,适应度函数还可以用于衡量程序方法的复杂度,帮助开发人员提升代码规范。

3.技术暗债与假设

管控技术债务是架构活动的重要组成部分,此处更多指相对可见的债务。下面讲解一种称为暗债(dark debt)的技术债务。

暗债是更为潜在的技术债务。暗债具有偶然性,与系统的混沌现象 相生相伴。暗债产生时并不被立即发现,不会妨害开发工作,还会越过测试防线,最终造成生产系统异常。

在设计、开发、测试新功能的过程中,工程师不免会做出了一系列假设,这可能包括:架构是正确有效的,拓扑结构不会改变,网络是安全且稳定的,带宽是够用的,接口是可用的且延时很小,存储空间和数据库性能不是瓶颈,有了解系统且尽职尽责的管理员。对于任何软件交付者来说,成本和工期永远是第一要素,对于非工程师可控的范畴,只能按照经验大致去判定。因此,做这些假设是客观必然的。

既然是大致的经验,那么假设终归是假设,假设变成假象只是概率问题。

对于假设还有一个问题必须重视,即时间因素。软件开发过程中的任何假设,极易被后续的各类迭代淹没,因此,久远之前所做的假设更是危机重重。

软件系统中的各个因果关系相互作用,可能产生不可预见的结果。架构设计的目的当然是打造可用的生产系统,规避暗债和假设的影响。但是问题在暴露前是不可见的,任何人不可能将异常与故障一网打尽。一个人无法将所有环节(和细节)都装入大脑,如果程序出现Bug,可以将主责归咎于程序开发者,而暗债和假设则超出了个人工作职责能够控制的范畴,其最终爆发的后果并不能归咎于构建和维护系统的某人。即从更抽象的层面来看,不良行为是复杂系统平台的天然属性。

3.1.3 架构与系统故障相关度

IT工作中最刻骨铭心的记忆是什么?我可以毫不犹豫地给出答案——系统故障。

故障与错误是复杂系统的必然产物,那么,通过学习架构知识,提升架构实践水平,对避免系统故障能起到立竿见影之效么?笔者对记录的若干次系统故障进行了整理分类,归结为如下四类,编号为A类~D类。现在做一个架构与故障相关程度的分析,相关度越高者,通过改善架构避免故障的可能性更高,即效果越好,反之则可能性更低。

1.A类——静态错误类

(1)测试系统与生产系统的环境不一致。测试环境中缺少真实数据,有些细节处功能点测不出真实效果,或者测试环境字典表内数据和生产不一样,系统运行在测试环境时一切正常,进入生产环境则不行,测试用例覆盖不了此类问题。

(2)大查询导致应用数据库的压力过大(操作系统的CPU使用率高或者用户请求响应时间过长),造成系统瘫痪。此类原因通常在于,未建立合适的表索引造成SQL性能不佳;或者大查询不应该使用业务库,应该将此类功能放到数据统计平台上做;抑或是未做客户查询限制等。

(3)时间太紧导致测试不足和上线准备不充分。例如,需求临时变更,上线当天加塞了一些内容,导致来不及组织有效测试,在业务需求方压力下只能凭经验认为可以上线,或者时间太紧上线步骤准备不足,上线单遗漏了某个隐蔽的配置变量等。

(4)对于诡异的输入参数,没有在检查与处理中覆盖各类可能的异常情况。例如,多个参数进行运算后出现意料之外的异常结果,导致后续程序运行失败,未能完成业务处理。

可以通俗地将静态类错误理解为系统有Bug(也包括无法满足并发性能要求等问题)。对于这些较为细节的问题,依靠架构能力的提升并非良策,更好的解决办法应该是诉诸技术管理,能在技术评审环节或测试与质控工作管理等方面“做些文章”为佳。

2.B类——动态变化类

(1)重试风暴。某个服务超时没有返回结果,这样的失败引起了用户重试,用户越重复点击功能,服务承载的压力越大,结果可想而知。

(2)雪崩效应。一处堵塞蔓延到全平台,造成大面积故障。这样的场景,读者或许并不陌生,尤其是在抢票、商城做秒杀活动时。

(3)维稳陷阱。为了保证系统在大型节假日、重大会议、赛事举办期间稳定运行,特意避免在这样的日子前做程序发版。但是,笔者遇到过的情况是,没有发版恰恰会导致故障爆发。要弄清其中缘由,只有使用逆向思维。反过来想象,发版时有何动作会掩盖一些问题?答案是发版会重启应用,是重启动作掩盖了某些隐藏的问题。经过排查发现,某些系统存在缓慢的内存泄漏问题,每周发版时重启应用,“泄漏的内存”会被清零,因此这个Bug并不会导致故障。一旦程序持续运行时间达到2周,内存泄漏积累到一定程度,可用内存空间被填满,整个系统立刻瘫痪。这是个挺有意思的案例。

架构设计能力与此类问题关联程度较高。例如,在部署设计上做好泳道资源隔离;在防御机制上做好熔断机制,做好高可用、高并发等质量属性方面的架构设计,都可以有效规避此类故障。

3.C类——能力与机制类

(1)没有灰度发布环境,或没有红黑部署能力。

(2)监控中缺少对内存变化趋势的监控,Redis运行导致占用的内存一直在增长,没有被发现,最终引发服务宕机。

(3)虽然有回退机制,但是实际出故障时,操作起来很烦琐,需要很长时间。

(4)共享Maven的多个项目组之间没有信息同步机制,某个组拉取了新版本的三方程序包,影响了另外一个组的程序。

此类问题造成的故障,跟架构工作有一定的关系,例如,完全可以将具备灰度环境视为一种特殊的质量属性,将此列为部署架构的职责。另外,此类问题与技术管理上重视程度的关联性也较大。

4.D类——随机偶发类

(1)使用Java OSGi框架开发的系统,一直以来都是可以正常启动的,某天突然鬼使神差地出现了无法加载全部Class的问题。其原因是前几天有一个新加入的依赖库(JAR包)莫名其妙地打乱了OSGi原来的包加载顺序,因某些依赖冲突问题,导致启动后有些Class未加载成功。

(2)订单系统给支付系统发送扣账交易,时而会丢失。排查很久才发现,原因在于(两个系统中间的)负载均衡软件上的配置策略给交易加了时序设置。以两笔交易为例,先发的交易先到则一切正常,而到达顺序与发出顺序不同的,会被支付系统视为是风险交易而拒绝接收。

(3)操作系统的配置文件,使用的名称服务器(NameServer)是外网地址,按照生产地址管理要求,网络团队上线前进行网络优先级调整,启用了内网的名称服务器作为主服务(超时失败后,才会使用外网名称服务器解析作为备用机制)。系统上线后,所有操作系统找不到配置的名称服务器,大量节点之间的连接因此出现超时故障。但因为备用机制的存在,超时后又能继续连接,几乎所有人被误导,将原因定位为程序问题,导致最终原因很久才被发现。

(4)运行在WebSphere的WAR包,在浏览器中加载显示页面超级慢,前方为客户做演示的业务人员已经抓狂。排查很久,发现War的解压文件是Root权限,启动WebSphere的用户对WAR包中的Class文件及目录没有写权限,导致每次访问都要重新编译Java文件来生成Class。

此类问题更难以察觉,一旦发生,解决时间会很长。因此,即使这几个例子来自早年工作中遇到的问题,但依旧记忆深刻。解决这类问题的人,通常并非是技高一筹的架构师,而是对于系统的环境、场景更为熟悉的一线工程师。总体来说,此类故障与架构工作的关联度一般。

通过以上分析可见,在系统建设与故障防御工作领域中,要保有客观的心态,架构并非解决所有问题的银弹。架构对于系统很多方面的影响,实际上是潜移默化的,既是间接的,又是无法客观衡量的。难以直接证明成效,这正是开展架构工作的难点。以笔者实践经历来看,企业老板们对系统故障的解决方案,大多聚焦在开发、测试能力或质量管理流程,能够反思架构方面投入不足的,实属凤毛麟角。

这些难点所造成的影响,正是架构领域灰犀牛效应 的体现,对于架构师的发展而言是致命的。企业更能注意到的是直接结果,对于“架构到底是什么,对业务有何帮助”的问题,很多业务出身的老板们要么不愿相信,要么将信将疑。即使有些企业对软件架构有较高的认可度,但在时间方面的忍耐度却十分有限,此时,架构工作的价值如同一张时光信用卡,通常几个月后(这张卡)就透支了。 psb9yGdMqyPorXBDarjRn6z12VC77oe93DKtFJ8K2p6XZu3GwAkc+165kQqUPSmQ

点击中间区域
呼出菜单
上一章
目录
下一章
×