当前位置: 首页 > news >正文

架构设计三原则

作为程序员,很多人都希望成为一名架构师,但并非简单地通过编程技能就能够达成这一目标。事实上,优秀的程序员和架构师之间存在一个明显的鸿沟——不确定性。

编程的本质是确定性的,也就是说,对于同一段代码,无论由谁编写,在何时执行,其结果应该是确定的(尽管有可能存在bug)。相比之下,架构设计本质上是不确定的。同一系统,不同公司的架构可能存在较大的差异,但最终都能正常运转。在面对多种可能性时,架构师需要进行选择,而这种选择往往会让人陷入两难的境地。

例如:

是否选择业界最先进的技术,还是选择团队目前最熟悉的技术? 是否选择Google的Angular或Facebook的React? 是否选择MySQL或MongoDB? 对于这些问题,架构师需要依赖自己的经验和直觉进行决策,因为架构设计领域并没有一套通用的规范来指导架构师。但是,通过研究架构设计的发展历史和多个公司的架构发展过程,可以发现一些共性原则,即合适原则、简单原则和演化原则。在遵循这些原则的基础上,架构师可以做出最好的选择,克服不确定性。

合适原则

合适原则宣言:“合适优于业界领先”。

很多优秀的技术人员都怀有强烈的技术情结,他们总想通过挑战自我,实现甚至超越业界领先水平,从而在年终KPI绩效总结中留下自己的印记。然而,这种做法往往会导致失败。在互联网行业,我见过许多“亿级用户平台”的失败案例,例如某个几个人规模的业务团队,雄心勃勃地想要和腾讯QQ一较高下,但最终却以失败告终。为什么会这样呢?

实现任何梦想都需要脚踏实地的付出。这里的“脚踏实地”主要体现在以下几个方面。

1.将军难打无兵之仗。 大公司的分工比较细,每个小系统都可能由一个小组负责。但在大部分公司,整个研发团队可能只有100多人,某个业务团队可能只有十几个人。在这种情况下,想要完成类似于几十人团队才能完成的事情,并且还要做得更好,难度可想而知。

2.罗马不是一天建成的。 业界领先的很多方案并不是一群天才某个时期灵机一动就做出来的。它们经过了数年的发展才逐步完善和初具规模。如果没有足够的积累和历练,靠拍脑袋或者头脑风暴是无法和真正的实战相比的。

3.冰山下面才是关键。 业界领先的方案大多是“逼”出来的。随着业务的发展和量变导致质变,新的问题出现了,已有的方式无法应对这些问题,需要用新的方案来解决。通过不断的创新和尝试,业界领先的方案才得以形成。

如果没有类似于腾讯那样海量用户积累、大量的人力资源、优秀的业务场景,想要建立一个“亿级用户平台”就注定会失败。真正优秀的架构应该是在企业当前的人力、条件、业务等各种约束下设计出来的,能够将资源合理地整合在一起并发挥出最大的功效,并且能够快速落地。许多BAT公司的架构师到了小公司或创业团队却无法创造出成果,因为他们缺乏大公司平台、资源和积累的支持,他们只是生搬硬套大公司的做法,这样的做法成功的概率非常低。

因此,真正优秀的架构设计应该是在实际条件下的切实可行的设计,将资源合理利用,并能够快速落地。成功的架构设计需要充分考虑公司当前的资源、团队能力和业务需求,不断优化和改进,不断学习和尝试新的方法和技术,以适应日益变化的市场和业务环境。同时,我们也需要认识到,技术的发展需要时间,需要不断的积累和经验,只有在大量的实践中才能不断提升自己的技术水平。

简单原则

简单原则宣言:“简单优于复杂”。

确实,在软件领域,过度追求复杂性往往会导致设计出来的系统难以维护、扩展和调试,进而影响整个团队的工作效率。因此,我们需要转变思路,不要将复杂性作为评价架构的主要指标,而是要以系统的实际需求和问题为出发点,遵循合适原则、简单原则、演化原则,以最小的复杂度满足系统的需求。简单来说,就是要“追求简单”。

追求简单不等于简单粗暴,而是在不降低系统性能、稳定性和可扩展性的前提下,以最少的设计和实现来满足需求。这需要架构师具备全面的技术能力和对系统的深刻理解,能够对复杂性进行有效的把控和分解,从而将系统的设计和实现变得更加简单明了。

此外,简单也不意味着架构师可以忽略细节和考虑不周。实际上,追求简单需要更加注重细节和全局的把握,要通过精心的设计和实现来避免各种潜在的问题和风险。只有在这样的基础上,才能够设计出真正合适的、简单的、可演化的软件架构。

软件领域的复杂性体现在两个方面:

1.结构的复杂性

结构复杂的系统几乎毫无例外具备两个特点:

  • 组成复杂系统的组件数量更多;

  • 同时这些组件之间的关系也更加复杂。

我以图形的方式来说明复杂性:

2个组件组成的系统:

alt

3个组件组成的系统:

alt

4个组件组成的系统:

alt

5个组件组成的系统:

alt

为了避免这些问题,我们需要尽量简化架构。简化架构并不意味着牺牲可用性或者功能,而是在保证功能和可用性的前提下,尽量减少组件的数量和组件之间的关系复杂度。

一个好的架构需要在多方面权衡,如系统可用性、开发效率、维护成本、可扩展性等等。因此,在进行架构设计时,需要从多个角度出发,考虑系统的实际需求和限制条件,逐步迭代优化设计方案,而不是一开始就试图设计出最完美的方案。

另外,一个好的架构设计需要在实践中不断迭代和优化。系统运行过程中会出现各种各样的问题,需要不断地对架构进行调整和优化。同时,随着业务的发展和变化,架构设计也需要随之变化,及时做出调整和优化,才能够保证系统的稳定性和可持续发展。

2.逻辑的复杂性

意识到结构的复杂性后,我们的第一反应可能就是“降低组件数量”,毕竟组件数量越少,系统结构越简。最简单的结构当然就是整个系统只有一个组件,即系统本身,所有的功能和逻辑都在这一个组件中实现。

不幸的是,这样做是行不通的,原因在于除了结构的复杂性,还有逻辑的复杂性,即如果某个组件的逻辑太复杂,一样会带来各种问题。

逻辑复杂的组件,一个典型特征就是单个组件承担了太多的功能。以电商业务为例,常见的功能有:商品管理、商品搜索、商品展示、订单管理、用户管理、支付、发货、客服……把这些功能全部在一个组件中实现,就是典型的逻辑复杂性。

逻辑复杂几乎会导致软件工程的每个环节都有问题,假设现在淘宝将这些功能全部在单一的组件中实现,可以想象一下这个恐怖的场景:

  • 系统会很庞大,可能是上百万、上千万的代码规模,“clone”一次代码要30分钟。

  • 几十、上百人维护这一套代码,某个“菜鸟”不小心改了一行代码,导致整站崩溃。

  • 需求像雪片般飞来,为了应对,开几十个代码分支,然后各种分支合并、各种分支覆盖。

  • 产品、研发、测试、项目管理不停地开会讨论版本计划,协调资源,解决冲突。

  • 版本太多,每天都要上线几十个版本,系统每隔1个小时重启一次。

  • 线上运行出现故障,几十个人扑上去定位和处理,一间小黑屋都装不下所有人,整个办公区闹翻天。

  • ……

不用多说,肯定谁都无法忍受这样的场景。

但是,为什么复杂的电路就意味更强大的功能,而复杂的架构却有很多问题呢?根本原因在于电路一旦设计好后进入生产,就不会再变,复杂性只是在设计时带来影响;而一个软件系统在投入使用后,后续还有源源不断的需求要实现,因此要不断地修改系统,复杂性在整个系统生命周期中都有很大影响。

功能复杂的组件,另外一个典型特征就是采用了复杂的算法。复杂算法导致的问题主要是难以理解,进而导致难以实现、难以修改,并且出了问题难以快速解决。

以ZooKeeper为例,ZooKeeper本身的功能主要就是选举,为了实现分布式下的选举,采用了ZAB协议,所以ZooKeeper功能虽然相对简单,但系统实现却比较复杂。相比之下,etcd就要简单一些,因为etcd采用的是Raft算法,相比ZAB协议,Raft算法更加容易理解,更加容易实现。

综合前面的分析,我们可以看到,无论是结构的复杂性,还是逻辑的复杂性,都会存在各种问题,所以架构设计时如果简单的方案和复杂的方案都可以满足需求,最好选择简单的方案。《UNIX编程艺术》总结的KISS(Keep It Simple, Stupid!)原则一样适应于架构设计。

在架构设计中,我们应该遵循KISS原则,尽量让架构设计简单而易于理解和维护。另外,我们还可以通过以下几种方式来避免复杂性问题:

将系统分解为小的组件。每个组件都应该尽可能地简单,只实现一个功能。这样可以避免单个组件变得过于复杂,同时也方便组件的维护和升级。

  • 采用标准化的组件。通过采用标准化的组件,可以避免重新发明轮子,也可以减少对复杂组件的依赖。

  • 避免过度设计。过度设计往往会导致代码过于复杂,难以维护。在设计时,应该尽量避免不必要的设计,只实现必要的功能。

  • 尽量采用简单的算法。尽量使用简单的算法,可以使系统更加易于理解和维护。对于一些比较复杂的算法,可以考虑采用现成的库,避免重复造轮子。

  • 借鉴已有的成功经验。在架构设计时,可以参考已有的成功案例,借鉴其中的经验和教训,避免犯类似的错误。

总之,架构设计是一门艺术,需要不断地思考和实践,才能设计出简单、易于理解和维护的系统。

演化原则

演化原则宣言:“演化优于一步到位”。

软件架构从字面意思理解和建筑结构非常类似,事实上“架构”这个词就是建筑领域的专业名词,维基百科对“软件架构”的定义中有一段话描述了这种相似性:

从和目的、主题、材料和结构的联系上来说,软件架构可以和建筑物的架构相比拟。

例如,软件架构描述的是一个软件系统的结构,包括各个模块,以及这些模块的关系;建筑架构描述的是一幢建筑的结构,包括各个部件,以及这些部件如何有机地组成成一幢完美的建筑。

然而,字面意思上的相似性却掩盖了一个本质上的差异:建筑一旦完成(甚至一旦开建)就不可再变,而软件却需要根据业务的发展不断地变化!

  • 古埃及的吉萨大金字塔,4000多年前完成的,到现在还是当初的架构。

  • 中国的明长城,600多年前完成的,现在保存下来的长城还是当年的结构。

  • 美国白宫,1800年建成,200年来进行了几次扩展,但整体结构并无变化,只是在旁边的空地扩建或者改造内部的布局。

对比一下,我们来看看软件架构。

Windows系统的发展历史:

alt

如果对比Windows 8的架构和Windows 1.0的架构,就会发现它们其实是两个不同的系统了!

Android的发展历史:

alt

( http://www.dappworld.com/wp-content/uploads/2015/09/Android-History-Dappworld.jpg)

同样,Android 6.0和Android 1.6的差异也很大。

软件架构需要根据业务发展不断变化,所以在做架构设计时应该采用迭代的方式,而不是一步到位。这样做的好处是,可以让架构师随着业务的变化逐渐深入了解业务需求,同时也可以避免设计出过度复杂、不切实际的方案。

另外,预测和分析的确是不可靠的,但是我们可以采用一些技术手段来帮助我们应对变化。比如,可以采用微服务架构,将系统划分成若干个小服务,每个服务都可以独立开发、测试、部署和扩展。这样一来,当业务需求发生变化时,只需要修改涉及到的服务,而不用对整个系统进行修改。此外,还可以采用容器技术,通过容器化应用程序来实现快速部署和扩展,以应对业务变化带来的挑战。

总之,软件架构需要根据业务发展不断变化,我们应该采用迭代的方式来设计架构,并采用一些技术手段来帮助我们应对变化。同时,我们也需要明确,软件架构永远不可能一劳永逸,只有不断地学习和改进,才能保持软件架构的健康和持续性。

考虑到软件架构需要根据业务发展不断变化这个本质特点, 软件架构设计其实更加类似于大自然“设计”一个生物,通过演化让生物适应环境,逐步变得更加强大:

  • 首先,生物要适应当时的环境。

  • 其次,生物需要不断地繁殖,将有利的基因传递下去,将不利的基因剔除或者修复。

  • 第三,当环境变化时,生物要能够快速改变以适应环境变化;如果生物无法调整就被自然淘汰;新的生物会保留一部分原来被淘汰生物的基因。

软件架构设计同样是类似的过程:

  • 首先,设计出来的架构要满足当时的业务需要。

  • 其次,架构要不断地在实际应用过程中迭代,保留优秀的设计,修复有缺陷的设计,改正错误的设计,去掉无用的设计,使得架构逐渐完善。

  • 第三,当业务发生变化时,架构要扩展、重构,甚至重写;代码也许会重写,但有价值的经验、教训、逻辑、设计等(类似生物体内的基因)却可以在新架构中延续。

因此,架构师在设计软件架构时,需要始终关注业务需求,不断调整和改进架构以适应业务的发展。架构设计不应该是一次性的,而是一个持续演化的过程。同时,要注重简单性,避免过度复杂的架构设计,这样才能更好地满足业务需求,提高系统的可靠性和可维护性。

小结

以上的这三个原则都是为了应对“不确定性”而提出的,帮助架构师在复杂、快速变化的业务环境下做出更好的决策。

需要注意的是,这些原则并不是刻板的规则,而是指导思想。在实践中,架构师需要根据具体情况进行权衡和调整。有时候,一个复杂的方案可能确实更适合当前的业务需求;有时候,一个一步到位的方案也可能更符合业务发展的需要。架构师需要根据自己的经验和专业知识,综合考虑各种因素,做出最适合当前业务的决策。

最后,需要强调的是,架构设计并不是一项孤立的技术活动,而是需要和业务、运维、开发等各个环节协同配合的。只有将架构设计和整个软件开发、运维流程无缝衔接,才能真正实现架构的价值,为业务的成功提供坚实的支持。

本文由 mdnice 多平台发布

相关文章:

架构设计三原则

作为程序员,很多人都希望成为一名架构师,但并非简单地通过编程技能就能够达成这一目标。事实上,优秀的程序员和架构师之间存在一个明显的鸿沟——不确定性。 编程的本质是确定性的,也就是说,对于同一段代码&#xff0c…...

Android 性能优化——ANR监控与解决

作者:Drummor 1 哪来的ANR ANR(Application Not responding):如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR) 错误。如果应用位于前台,系统会向用户显示一个对话框。ANR 对话框会为用户提供强制退出应用的选项…...

Machine Learning-Ex3(吴恩达课后习题)Multi-class Classification and Neural Networks

目录 1. Multi-class Classification 1.1 Dataset 1.2 Visualizing the data 1.3 Vectorizing Logistic Regression 1.3.1 Vectorizing the cost function(no regularization) 1.3.2 Vectorizing the gradient(no regularization&#…...

【Java】SpringBoot事务回滚规则

SpringBoot事务回滚规则SpringBoot事务回滚规则SpringBoot事务回滚规则 在SpringBoot中,如果一个方法被声明为Transactional,则会开启一个事务。如果这个方法中的任何一个步骤失败了(比如抛出了异常),则该事务将会回滚…...

使用cocopod就那么容易

第一节、配置coopod 打开终端替换ruby镜像源,系统自带的镜像源(gem sources --remove https://rubygems.org/)被墙挡住了或者(https://ruby.taobao.org/)已过期。需替换成新的镜像源。 1).先查看已有的镜像是否是:ht…...

第14届蓝桥杯C++B组省赛

文章目录A. 日期统计B. 01 串的熵C. 冶炼金属D. 飞机降落E. 接龙数列F. 岛屿个数G. 子串简写H. 整数删除I. 景区导游J. 砍树今年比去年难好多 Update 2023.4.10 反转了,炼金二分没写错,可以AC了 Update 2023.4.9 rnm退钱,把简单的都放后面…...

面向对象编程(进阶)3:方法的重写

目录 3.1 方法重写举例 Override使用说明: 3.2 方法重写的要求 3.3 小结:方法的重载与重写 (1)同一个类中 (2)父子类中 3.4 练习 父类的所有方法子类都会继承,但是当某个方法被继承到子类…...

2023年第十四届蓝桥杯Java_大学B组真题

Java_B组试题 A: 阶乘求和试题 B: 幸运数字试题 C: 数组分割试题 D: 矩形总面积试题 E: 蜗牛试题 F: 合并区域试题 G: 买二赠一试题 H: 合并石子试题 I: 最大开支试题 J: 魔法阵【考生须知】 考试开始后,选手首先下载题目,并使用考场现场公布的解压密码解…...

APIs --- DOM事件进阶

1. 事件流 事件流指的是事件完整执行过程中的流动路径 任意事件被触发时总会经历两个阶段:【捕获阶段】和【冒泡阶段】 事件捕获 概念:从DOM的根元素开始去执行对应的事件(从外到里) 捕获阶段是【从父到子】的传导过程 代码&…...

awk命令详解以及使用方法

awk命令详解以及使用方法 awk 是一种文本处理工具,它可以逐行扫描文本文件,根据用户指定的规则进行匹配和处理,并输出结果。awk 的名称来自于三位创始人 Alfred Aho、Peter Weinberger 和 Brian Kernighan 的首字母缩写。 awk 通常用于处理以…...

vue-router3.0处理页面滚动部分源码分析

在使用vue-router3.0时候,会发现不同的路由之间来回切换,会滚动到上次浏览的位置,今天就来看看这部分的vue-router中的源码实现。 无论是基于hash还是history的路由切换,都对滚动进行了处理,这里分析其中一种即可。 无…...

走心Python实战应用:【requests+re 模块】快速下载原shen图片

人生苦短,我用python 这次给大家带来的是模块实战 以便大家理解学习 觉得写的好的话,可以给我多多点赞鸭~ 走心Python实战应用:【requestsre 模块】快速下载原shen图片一、理解Python requests 模块二、requests 方法三、ruqusets 模块实战…...

Comparable和Comparator的使用

在Java中,Comparable和Comparator都是用来实现对象排序的接口。 Comparable Comparable是一个内部比较器接口,它允许在类定义时对该类进行自然排序。当实现了Comparable接口的类的对象列表被传递给Collections.sort()方法时,该方法将使用该…...

【OJ每日一练】1121 - 耐摔指数

文章目录 一、题目🔸题目描述🔸输入输出🔸样例二、思路解析三、代码参考作者:KJ.JK🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🍂个人博客首页: KJ.JK 💖系列专栏:OJ每日一练 一、题目 🔸题目描述 x星球的居民脾气不太好,但好在他…...

vue项目Agora声网实现一对一视频聊天Demo示例(Agora声网实战及agora-rtc-vue使用,新增在线预览地址)

最终效果 在线预览地址 一、声网简介---->请查看官网 二、声网注册---->请自行百度(创建音视频连接需要在Agora注册属于您的appid) 三、具体实现视频聊天步骤 1、 实现音视频通话基本逻辑 1、创建对象 调用 createClient 方法创建 AgoraRTCCli…...

集成时间序列模型提高预测精度

使用Catboost从RNN、ARIMA和Prophet模型中提取信号进行预测 集成各种弱学习器可以提高预测精度,但是如果我们的模型已经很强大了,集成学习往往也能够起到锦上添花的作用。流行的机器学习库scikit-learn提供了一个StackingRegressor,可以用于…...

(详细)《美国节日》:某月的第几个星期几

目录 一、题目描述: 二、思路: 1、给定 年月日,如何知道这天是星期几? 2、已知这个月的第一天是星期几,如何知道第三个星期一是几号? 3、最后一个星期一 三、思路总结 四、代码 一、题目描述&#xf…...

架构设计的历史背景

架构设计的历史背景 在探讨架构设计的历史背景时,了解软件开发进化的历史是一个重要的起点。了解软件开发的演变过程可以帮助我们更好地理解架构设计的起源和发展。现在,让我们来简要回顾一下软件开发的历史,并探索软件架构出现的背景。 首先…...

C#,初学琼林(06)——组合数的算法、数据溢出问题的解决方法及相关C#源代码

1 排列permutation 排列,一般地,从n个不同元素中取出m(m≤n)个元素,按照一定的顺序排成一列,叫做从n个元素中取出m个元素的一个排列(permutation)。特别地,当mn时,这个排列被称作全…...

MySQL数据库——绘制E-R图:数据库概要设计阶段

在MySQL数据库的概要设计阶段,绘制E-R图是非常重要的一步。E-R图(实体关系图)是一种图形化的工具,用于描述数据库中实体之间的关系。 以下是在MySQL数据库概要设计阶段绘制E-R图的步骤: 确定实体:在MySQL数…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

使用分级同态加密防御梯度泄漏

抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

Android15默认授权浮窗权限

我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...