@qidiandasheng
2022-08-07T21:20:58.000000Z
字数 7047
阅读 603
架构
解决复杂度带来的问题,复杂度来源包括:高性能、高可用、可拓展性、(低成本,安全,规模)
系统无中断地执行其功能的能力,代表系统的可用性程度,是进行系统设计时的准则之一。
正确预测变化、完美封装变化。
预测变化的复杂性在于:
需要设计变化层和稳定层之间的接口:
应对变化的方案是提炼出一个“抽象层”和一个“实现层”。抽象层是稳定的,实现层可以根据具体业务需要定制开发,当加入新的功能时,只需要增加新的实现,无须修改抽象层。这种方案典型的实践就是设计模式和规则引擎。
可用研究一下适配器模式装饰模式
客户端安全主要在于代码混淆、接口加密等
规模带来复杂度的主要原因就是“量变引起质变”,当数量超过一定的阈值后,复杂度会发生质的变化。
随着业务的发展,规模带来的常见复杂度有:
(1)业务功能越来越多,调用逻辑越来越复杂;
(2)数据容量、类型、关联关系越来越多。
规模问题需要与高性能、高可用、高扩展、高伸缩性统一考虑。常采用“分而治之,各个击破”的方法策略。
合适原则、简单原则、演化原则
合适优于业界领先。
没那么多人,却想干那么多活,是失败的第一个主要原因。
没有那么多积累,却想一步登天,是失败的第二个主要原因。
没有那么卓越的业务场景,却幻想灵光一闪成为天才,是失败的第三个主要原因。
真正优秀的架构都是在企业当前人力、条件、业务等各种约束下设计出来的,能够合理地将资源整合在一起并发挥出最大功效,并且能够快速落地。这也是很多 BAT 出来的架构师到了小公司或者创业团队反而做不出成绩的原因,因为没有了大公司的平台、资源、积累,只是生搬硬套大公司的做法,失败的概率非常高。
简单原则宣言:“简单优于复杂”。
结构的复杂性
结构复杂的系统几乎毫无例外具备两个特点:组成复杂系统的组件数量更多;同时这些组件之间的关系也更加复杂。
逻辑的复杂性
逻辑复杂几乎会导致软件工程的每个环节都有问题,假设现在淘宝将这些功能全部在单一的组件中实现,可以想象一下这个恐怖的场景:
系统会很庞大,可能是上百万、上千万的代码规模,“clone”一次代码要 30 分钟。
几十、上百人维护这一套代码,某个“菜鸟”不小心改了一行代码,导致整站崩溃。
需求像雪片般飞来,为了应对,开几十个代码分支,然后各种分支合并、各种分支覆盖。
产品、研发、测试、项目管理不停地开会讨论版本计划,协调资源,解决冲突。
版本太多,每天都要上线几十个版本,系统每隔 1 个小时重启一次。
线上运行出现故障,几十个人扑上去定位和处理,一间小黑屋都装不下所有人,整个办公区闹翻天。
所以架构设计时如果简单的方案和复杂的方案都可以满足需求,最好选择简单的方案。
演化原则宣言:“演化优于一步到位”。
对于建筑来说,永恒是主题;而对于软件来说,变化才是主题。软件架构需要根据业务的发展而不断变化。设计 Windows 和 Android 的人都是顶尖的天才,即便如此,他们也不可能在 1985 年设计出 Windows 8,不可能在 2009 年设计出 Android 6.0。
软件架构设计演化过程:
首先,设计出来的架构要满足当时的业务需要。
其次,架构要不断地在实际应用过程中迭代,保留优秀的设计,修复有缺陷的设计,改正错误的设计,去掉无用的设计,使得架构逐渐完善。
第三,当业务发生变化时,架构要扩展、重构,甚至重写;代码也许会重写,但有价值的经验、教训、逻辑、设计等(类似生物体内的基因)却可以在新架构中延续。
(1)设计最优秀的方案。不要面向“简历”进行架构设计,而是要根据“合适”、“简单”、“演进”的架构设计原则,决策出与需求、团队、技术能力相匹配的合适方案。
(2)只做一个方案。一个方案容易陷入思考问题片面、自我坚持的认知陷阱。
(1)备选方案不要过于详细。备选阶段解决的是技术选型问题,而不是技术细节。
(2)备选方案的数量以 3~5个为最佳。
(3)备选方案的技术差异要明显。
(4)备选方案不要只局限于已经熟悉的技术。
常见的方案质量属性点有:性能、可用性、硬件成本、项目投入、复杂度、安全性、可扩展性等。
可扩展性架构的设计方法很多,但万变不离其宗,所有的可扩展性架构设计,背后的基本思想都可以总结为一个字:拆!
常见的拆分思路有如下三种:
流程 > 服务 > 功能
例子
TCP/IP 协议栈:
流程
对应 TCP/IP 四层模型,因为 TCP/IP 网络通信流程是:应用层 → 传输层 → 网络层 → 物理 + 数据链路层,不管最上层的应用层是什么,这个流程都不会变。
服务
对应应用层的 HTTP、FTP、SMTP 等服务,HTTP 提供 Web 服务,FTP 提供文件服务,SMTP 提供邮件服务,以此类推。
功能
每个服务都会提供相应的功能。例如,HTTP 服务提供 GET、POST 功能,FTP 提供上传下载功能,SMTP 提供邮件发送和收取功能。
例子二
学生信息管理系统
面向流程拆分
展示层:负责用户页面设计,不同业务有不同的页面。例如,登录页面、注册页面、信息管理页面、安全设置页面等。
业务层:负责具体业务逻辑的处理。例如,登录、注册、信息管理、修改密码等业务。
数据层:负责完成数据访问。例如,增删改查数据库中的数据、记录事件到日志文件等。
存储层:负责数据的存储。例如,关系型数据库 MySQL、缓存系统 Memcache 等。
面向服务拆分
将系统拆分为注册、登录、信息管理、安全设置等服务,最终架构示意图如下:
面向功能拆分
每个服务都可以拆分为更多细粒度的功能,例如:
注册服务:提供多种方式进行注册,包括手机号注册、身份证注册、学生邮箱注册三个功能。登录服务:包括手机号登录、身份证登录、邮箱登录三个功能。
信息管理服务:包括基本信息管理、课程信息管理、成绩信息管理等功能。
安全设置服务:包括修改密码、安全手机、找回密码等功能。
不同的拆分方式,将得到不同的系统架构,典型的可扩展系统架构有:
当然我们可以在系统架构设计中进行组合使用:
C/S 架构、B/S 架构
划分的对象是整个业务系统,划分的维度是用户交互,即将和用户交互的部分独立为一层,支撑用户交互的后台作为另外一层。
MVC 架构、MVP 架构
划分的对象是单个业务子系统,划分的维度是职责,将不同的职责划分到独立层,但各层的依赖关系比较灵活。
逻辑分层架构
划分的对象可以是单个业务子系统,也可以是整个业务系统,划分的维度也是职责。虽然都是基于职责划分,但逻辑分层架构和 MVC 架构、MVP 架构的不同点在于,逻辑分层架构中的层是自顶向下依赖的。典型的有操作系统内核架构、TCP/IP 架构。例如,下面是 Android 操作系统架构图:
分层架构设计最核心的一点就是需要保证各层之间的差异足够清晰,边界足够明显,让人看到架构图后就能看懂整个架构,这也是分层不能分太多层的原因。否则如果两个层的差异不明显,就会出现程序员小明认为某个功能应该放在 A 层,而程序员老王却认为同样的功能应该放在 B 层,这样会导致分层混乱。如果这样的架构进入实际开发落地,则 A 层和 B 层就会乱成一锅粥,也就失去了分层的意义。
分层结构的另外一个特点就是层层传递,也就是说一旦分层确定,整个业务流程是按照层进行依次传递的,不能在层之间进行跳跃。好处在于强制将分层依赖限定为两两依赖,降低了整体系统复杂度。
但分层结构的代价就是冗余,也就是说,不管这个业务有多么简单,每层都必须要参与处理,甚至可能每层都写了一个简单的包装函数。以用户管理系统最简单的一个功能“查看头像”为例。查看头像功能的实现很简单,只是显示一张图片而已,但按照分层分册架构来实现,每层都要写一个简单的函数。但如果我直接最上面一层访问最底层的数据层,就可以省却中间的冗余设计。
分层架构的优势就体现在通过分层强制约束两两依赖,一旦自由选择绕过分层,时间一长,架构就会变得混乱。
分层架构另外一个典型的缺点就是性能,因为每一次业务请求都需要穿越所有的架构分层,有一些事情是多余的,多少都会有一些性能的浪费。当然,这里所谓的性能缺点只是理论上的分析,实际上分层带来的性能损失,如果放到 20 世纪 80 年代,可能很明显;但到了现在,硬件和网络的性能有了质的飞越,其实分层模式理论上的这点性能损失,在实际应用中,绝大部分场景下都可以忽略不计。
微服务的架构理念要求“快速交付”,相应地要求采取自动化测试、持续集成、自动化部署等敏捷开发相关的最佳实践。如果没有这些基础能力支撑,微服务规模一旦变大(例如,超过 20 个微服务),整体就难以达到快速交付的要求,这也是很多企业在实行微服务时踩过的一个明显的坑,就是系统拆分为微服务后,部署的成本呈指数上升。
微服务的坑:
服务划分过细,服务间关系复杂
服务划分过细,单个服务的复杂度确实下降了,但整个系统的复杂度却上升了,因为微服务将系统内的复杂度转移为系统间的复杂度了。
服务数量太多,团队效率急剧下降
调用链太长,性能下降
由于微服务之间都是通过 HTTP 或者 RPC 调用的,每次调用必须经过网络。一般线上的业务接口之间的调用,平均响应时间大约为 50 毫秒,如果用户的一起请求需要经过 6 次微服务调用,则性能消耗就是 300 毫秒,这在很多高性能业务场景下是难以满足需求的。为了支撑业务请求,可能需要大幅增加硬件,这就导致了硬件成本的大幅上升。
调用链太长,问题定位困难
系统拆分为微服务后,一次用户请求需要多个微服务协同处理,任意微服务的故障都将导致整个业务失败。然而由于微服务数量较多,且故障存在扩散现象,快速定位到底是哪个微服务故障是一件复杂的事情。下面是一个典型样例。
没有自动化支撑,无法快速交付
没有服务治理,微服务数量多了后管理混乱
客户端是否可以提供这样一层微服务基础设施层框架,比如Spring Cloud 项目,涵盖了服务发现、服务路由、网关、配置中心等功能;
基础设施搭建优先级:
微内核架构(Microkernel Architecture),也被称为插件化架构(Plug-in Architecture),是一种面向功能进行拆分的可扩展性架构,通常用于实现基于产品的应用。
微内核架构包含两类组件:核心系统(core system)和插件模块(plug-in modules)。核心系统负责和具体业务功能无关的通用功能,例如模块加载、模块间通信等;
微内核的核心系统设计的关键技术有:插件管理、插件连接和插件通信。
插件管理
核心系统需要知道当前有哪些插件可用,如何加载这些插件,什么时候加载插件。常见的实现方法是插件注册表机制。
插件连接
插件连接指插件如何连接到核心系统。通常来说,核心系统必须制定插件和核心系统的连接规范,然后插件按照规范实现,核心系统按照规范加载即可。
插件通信
插件通信指插件间的通信。虽然设计的时候插件间是完全解耦的,但实际业务运行过程中,必然会出现某个业务流程需要多个插件协作,这就要求两个插件间进行通信。由于插件之间没有直接联系,通信必须通过核心系统,因此核心系统需要提供插件通信机制。
OSGi 框架的逻辑架构图:
模块层(Module 层)
模块层实现插件管理功能。OSGi 中,插件被称为 Bundle,每个 Bundle 是一个 Java 的 JAR 文件,每个 Bundle 里面都包含一个元数据文件 MANIFEST.MF,这个文件包含了 Bundle 的基本信息。例如,Bundle 的名称、描述、开发商、classpath,以及需要导入的包和输出的包等,OSGi 核心系统会将这些信息加载到系统中用于后续使用。
生命周期层(Lifecycle 层)
生命周期层实现插件连接功能,提供了执行时模块管理、模块对底层 OSGi 框架的访问。生命周期层精确地定义了 Bundle 生命周期的操作(安装、更新、启动、停止、卸载),Bundle 必须按照规范实现各个操作。
服务层(Service 层)
服务层实现插件通信的功能。OSGi 提供了一个服务注册的功能,用于各个插件将自己能提供的服务注册到 OSGi 核心的服务注册中心,如果某个服务想用其他服务,则直接在服务注册中心搜索可用服务中心就可以了。
服务类的业务发展路径是这样的:提出一种创新的服务模式→吸引了一批用户→业务开始发展→吸引了更多用户→服务模式不断完善和创新→吸引越来越多的用户,如此循环往复。在这个发展路径中,技术并没有成为业务发展的驱动力,反过来由于用户规模的不断扩展,业务的不断创新和改进,对技术会提出越来越高的要求,因此是业务驱动了技术发展。
除非是开创新的技术能够推动或者创造一种新的业务,其他情况下,都是业务的发展推动了技术的发展。
对于架构师来说,判断业务当前和接下来一段时间的主要复杂度是什么就非常关键。判断不准确就会导致投入大量的人力和时间做了对业务没有作用的事情,判断准确就能够做到技术推动业务更加快速发展。那架构师具体应该按照什么标准来判断呢?
基于业务发展阶段进行判断,这也是为什么架构师必须具备业务理解能力的原因。不同的行业业务发展路径、轨迹、模式不一样,架构师必须能够基于行业发展和企业自身情况做出准确判断。
参照他人的架构演进,将自己的架构一步设计到位我觉得这本身就是个伪命题。
首先淘宝自己的架构是在持续的演进中的,可能技术的变革、业务的创新、硬件性能的提升等都会迫使架构产生变化,没有所谓最优解。
技术和架构是不能脱离业务来谈的,否则我们怎么去衡量它们的价值和收益呢。世界上没有两片相同的叶子,淘宝的业务在结构、体量和形态上往往和很多企业有很大的差异。
针对于架构实践,另一个不能避免问题就是,管理和成本。不同的架构设计解决问题的广度和深度不同,相应的带来的管理复杂度和人力物力的成本也不同。
结合激进、保守、跟风,作为架构的实践者,必须及时跟进新的技术体系,同时需要慎重考虑引入新的内容,要想清楚它的必要性、能在短期带来的什么收益、能解决什么问题,同时还需要以观察者的角度来看业界大厂的实践,同时思考他们为什么要这么做,对于我们以后改进设计很有帮助
对于架构演进来说是有成本的,在准备改变之前还要想明白的一个事就是这么做的成本是什么,会给我们带来什么样的收益,当前的团队规模是否能稳定驾驭
互联网业务发展一般分为几个时期:初创期、发展期、竞争期、成熟期。
不同时期的差别主要体现在两个方面:复杂性、用户规模。
业务复杂性
初创期
快速上功能,抄、买
发展期
1.堆功能期
2.优化期(系统优化、重构、分层)
3.架构期(拆分子系统)
竞争期
原来系统数量越来越多,到了一个临界点后就产生了质变,即系统数量的量变带来了技术工作的质变。主要体现在下面几个方面:
1.重复造轮子
2.系统交互一团乱麻
技术工作主要的解决手段有:
1.平台化
2.服务化
成熟期
此时技术上其实也基本进入了成熟期,该拆的也拆了,该平台化的也平台化了,技术上能做的大动作其实也不多了,更多的是进行优化。但有时候也会为了满足某个优化,系统做很大的改变。例如,为了将用户响应时间从 200ms 降低到 50ms,可能就需要从很多方面进行优化:CDN、数据库、网络等。这个时候的技术优化没有固定的套路,只能按照竞争的要求,找出自己的弱项,然后逐项优化。在逐项优化时,可以采取之前各个时期采用的手段。
用户规模
用户量增大对技术的影响主要体现在两个方面:性能要求越来越高、可用性要求越来越高。
以手机 App 为例,首先,我们分析一下 App 的复杂度主要来源是什么?通常情况下,App的主要复杂度就是可扩展,因为要不断地开发新的需求;高性能和高可用也涉及,高性能主要和用户体验有关;高可用主要是减少崩溃。其次,再看 App的架构需要遵循架构设计原则么?答案是肯定需要。刚开始为了业务快速开发,可能用“原生 +H5”混合架构;后来业务发展,功能更复杂了,H5 可能难以满足体验,架构又需要演进到“纯原生”;如果业务再发展,规模太庞大,则架构又可能需要演进到“组件化、容器化”。以上通过手机 App 的为例说明这套架构设计理论是通用的,有兴趣的同学可以按照这种方式分析一下企业应用,会发现这套理论也是适应的。