实现从程序员到架构师的转变,是许多开发人员的职业梦想。然而,这个转变并不是简单地提升编程技能就能实现的,因为在优秀的程序员与架构师之间存在一个关键的差异,那就是对“不确定性”的处理。在编程领域,一段代码的执行结果通常是有预期的——无论是谁编写,无论何时执行,给定相同的输入,预期的输出应该是一致的(这里的“一致”并不意味着“无错误”,错误的输出也是一种预期的一致性)。
而在架构设计中,情况则大不相同。对于相同的需求,不同的公司可能会设计出完全不同的系统架构,且这些架构最终都可能正常运行;面对同一设计问题,不同的设计师可能会提出不同的解决方案,而这些方案看似都合理。与编程不同,架构设计缺少严格的语法约束,更多的是在众多可能性中进行选择和决策。
当架构师面对技术选择时,常常会处于一个难以抉择的位置。例如,他们可能会在采用业界领先技术与使用团队熟悉技术之间犹豫不决;选择最新技术可能面临实施风险,而选择熟悉技术则可能限制未来技术的演进。同样的困境出现在选择开发框架时,是使用 Google 的 Angular 还是 Facebook 的 React 呢?Angular 提供了丰富的功能,而 React 则显得更为灵活。数据库的选择也是一个问题,是继续使用团队熟悉的 MySQL,还是转向更适合某些业务场景的 MongoDB?对于打造电商网站,是简单模仿淘宝的成熟架构,还是探索全新的设计?
这些决策困境的根源在于,架构设计领域缺乏一套普遍适用的规范来指导决策,架构师通常需要依赖于个人经验和直觉来进行设计,这使得架构设计有时被视为一项充满神秘色彩的工作。面对不断变化的业务需求和层出不穷的技术新品,以及各式各样的设计哲学,似乎难以有一套固定的规则适用于所有架构设计情景。
然而,通过对架构设计历史的学习、分析多家公司(如QQ、淘宝、Facebook)的架构演变过程以及研究多个互联网公司的架构设计,我们可以发现一些普遍适用的设计原则,包括:适宜性原则、简洁性原则和可演化性原则。遵循这些原则有助于架构师在面对众多选择时,做出最合适的决策
合适原则
合适原则宣言:合适优于业界领先
技术人员往往对技术抱有深厚的情结,追求在方案或架构设计中超越甚至达到业界领先水平,以此展现自己的能力,并在年度绩效评估中骄傲地提及自己的成就,如“设计的XX方案达到了Google的技术水平”或“性能测试结果超过了阿里的YY方案”。然而,现实中大多数基于这种思维方式设计的架构最终可能会失败。互联网行业内不乏“亿级用户平台”项目的失败案例,比如2011年,一个小团队雄心勃勃地采用业界最先进的技术挑战腾讯QQ,最终以失败告终。
失败的根本原因在于,即便是最宏伟的梦想也需要脚踏实地地实现。这种实际性主要体现在以下几个方面:
资源限制(将军不打无准备之仗):大公司的分工细致,专门的团队负责单独的小系统,而多数公司的研发团队规模有限,期望用有限的人力资源去实现或超越大团队的成果,难度极大。
时间积累(罗马不是一天建成的):业界领先的解决方案往往是经过多年发展逐步完善的,而非短时间内的突发灵感或加班熬夜就能完成。例如,阿里的中间件团队从2008年成立至今,已经发展多年,他们面对的挑战和所取得的成就是长时间积累的结果。
业务驱动(冰山下面才是关键):很多业界领先的方案是由特定的业务需求驱动下产生的。Google的GFS之所以诞生,是因为Google面临的庞大数据处理需求,而非仅仅是因为Google的工程师比其他公司的更聪明。
简单原则
简单原则宣言:简单由于复杂
在软件架构设计的领域,技术细腻和复杂度往往被视为技术实力的体现。历史上的伟大发明,如瑞士钟表、瓦特蒸汽机、莱特兄弟的飞机,以及摩托罗拉的手机,都是随着时间变得更加精细和复杂。这种逐步精进的过程促使我们在架构设计时追求精美和复杂,认为只有这样,才能将技术能力体现得淋漓尽致,将架构提升至艺术品的高度。
另外,软件架构与建筑架构之间的表面相似性使我们倾向于将建筑的审美观念应用于软件架构之上。我们对长城的雄伟、泰姬陵的精致、悉尼歌剧院的艺术性以及迪拜帆船酒店的奢华赞叹不已,因此对于自己参与打造的软件架构,也期望它能够展现出宏伟、精美、艺术和豪华等特质。
同时,团队内部的压力和外界评价标准也可能推动我们向着更加复杂的方向努力。在很多情况下,一个方案的复杂度被视为评价其价值的一个标准。例如,在设计主备方案时,采用心跳机制可能会被认为太过简单,而引入使用ZAB协议的ZooKeeper进行主备决策,则可能被视为更加高端的解决方案,尽管ZAB协议的复杂性意味着很少有人能完全理解它。
软件领域的复杂性体现在两个方面:
1.结构的复杂度
2个组件组成的系统

3个组件组成的系统

4个组件组成的系统

5个组件组成的系统

结构上的复杂性存在的第一个问题是,组件越多,就越有可能其中某个组件出现故障,从而导致系统故障。这个概率可以算出来,假设组件的故障率是 10%(有 10% 的时间不可用),那么有 3 个组件的系统可用性是(1-10%)×(1-10%)×(1-10%)= 72.9%,有5 个组件的系统可用性是(1-10%)×(1-10%)×(1-10%)×(1-10%)×(1-10%)=59%,两者的可用性相差 13%。
结构上的复杂性存在的第二个问题是,某个组件改动,会影响关联的所有组件,这些被影响的组件同样会继续递归影响更多的组件。还以上面图中 5 个组件组成的系统为例,组件 A修改或者异常时,会影响组件 B/C/E,D 又会影响 E。这个问题会影响整个系统的开发效率,因为一旦变更涉及外部系统,需要协调各方统一进行方案评估、资源协调、上线配合。结构上的复杂性存在的第三个问题是,定位一个复杂系统中的问题总是比简单系统更加困难。首先是组件多,每个组件都有嫌疑,因此要逐一排查;其次组件间的关系复杂,有可能表现故障的组件并不是真正问题的根源。
2.逻辑的复杂度
在面对架构的复杂性时,我们的直觉可能会告诉我们通过减少组件数量来简化结构,基于这样的思维,最极端的简化就是把所有功能都整合到一个单一的组件里,即让整个系统仅由一个组件构成。理论上,这看起来确实是最简单的架构。
然而,这种方法实际上是行不通的,因为这里忽视了一个关键因素——逻辑复杂性。当一个组件包含了过多的功能,它的逻辑就会变得极其复杂。以一个电商平台为例,它可能涵盖商品管理、搜索、展示、订单处理、用户管理、支付处理、物流以及客户服务等众多功能。将所有这些功能压缩到一个单一的组件中,就会造成逻辑上的高度复杂性。
逻辑复杂性带来的问题几乎会影响到软件工程的每个阶段。想象一下,如果一个平台如淘宝将所有功能集成在一个组件中,将会面临以下情形:代码规模庞大到几百万到几千万行,光是复制代码一次可能就需要半小时;几十上百的开发人员共同维护这套代码,一行代码的错误可能导致全站崩溃;持续不断的需求导致频繁的代码分支和合并,以及无休止的会议讨论和资源协调;过多的版本迭代导致系统频繁重启;线上问题定位和解决时,人手不足以应对,造成混乱。
那么,为何在电子领域,复杂的电路设计一旦确定下来就能带来更强大的功能,而在软件架构中复杂性却造成诸多问题呢?这是因为电路设计完成并投入生产后就基本固定下来,其复杂性主要在设计阶段;而软件系统在部署后,还需要不断地根据新的需求进行修改和更新,复杂性贯穿软件的整个生命周期,对系统的维护和扩展带来持续的挑战。
以 ZooKeeper 为例,ZooKeeper 本身的功能主要就是选举,为了实现分布式下的选举,采用了 ZAB 协议,所以 ZooKeeper 功能虽然相对简单,但系统实现却比较复杂。相比之下,etcd 就要简单一些,因为 etcd 采用的是 Raft 算法,相比 ZAB 协议,Raft 算法更加容易理解,更加容易实现。
演化原则
演化原则宣言:演化优于一步到位
软件架构的设计过程实际上与自然界中生物的演化过程有着惊人的相似性。就像生物适应环境并逐渐进化一样,软件架构也需满足当前的业务需求,并在后续的使用中经历不断的迭代和优化。这一过程涉及保留高效的设计元素,修正那些存在缺陷的部分,纠正错误,并剔除无用的元素,以达到架构的持续完善。
同时,随着业务需求的变更,架构需要进行相应的扩展、重构乃至彻底重建。虽然代码可能被重新编写,但是其中蕴含的宝贵经验、教训、逻辑和设计理念——相当于生物的基因——应当被保留并继承至新的架构之中。
架构师在设计架构时,应当遵循这种演化式的思维模式,避免一味追求庞大复杂的系统或盲目模仿大公司的架构策略。重要的是要针对当前业务的实际情况和面临的主要挑战来设计合适的架构,快速实施以满足业务需求,随后根据业务的演进对架构进行持续的优化和迭代。
即便是资源充足的大公司团队,在开发新系统架构时也应采取演化的策略,而不是期望一开始就能设计出完美无缺的系统。因为业务环境的快速变化使得即使最强大的团队也难以完全预见未来所有可能的业务变动和需求。
更多内容参考:
原文作者:二进制跳动;如有侵权,请联系管理员删除。