万字长文探讨可信构架之道(上)

举报
技术火炬手 发表于 2020/09/08 14:26:31 2020/09/08
【摘要】 软件架构是一个系统开发生命周期中最前端的部分,也是最关键、核心的部分。它决定了后续代码的走向,决定了项目的走向,有时候甚至能决定一家公司的成与败。

架构是什么?简单来说就是架子的结构,譬如动物、人的骨架,建筑物的结构框架等。架构影响什么?架构会影响最终物品的形态和质量。类比软件系统亦如此,在这个二进制的世界里,程序员扮演着上帝的角色,为这个世界创造出不同的东西。那么,作为根本,重要性不言而喻,这就需要我们思考如何合理地进行物体设计、架构设计。

软件技术领域的发展历程也是系统架构的演变过程,从单体、分布式系统、SOA架构,再到更细粒度的微服务化、服务网格、云原生架构等。架构设计的初衷也是为了应对各类场景的特有的高并发、高性能、稳定、易维护及可扩展性等。架构是多维的,可以有多种方法对其进行描述。架构设计依赖人的思考和判断,是一种抽象的结构,它由软件的各个组成部分和这些部分之间的依赖关系构成。经验丰富的设计人员能把握更多构架细节,对于程序架构具有较强的可预见性,让设计更加合理、充分。

软件系统架构对功能性需求影响不大,但架构的优劣决定了系统的非功能性需求即质量属性或简称为“能力”,决定了系统品质的好坏,决定了软件交付的可靠、可测试、可维护、可扩展及可部署性等。事实上,在任何架构甚至是一团糟的架构之上,都可以实现应用的功能性需求。因此即便是成功的系统,其内部架构也可能往往是一个大泥球,不合理的架构通常导致系统紧耦合、玻璃心、难以改变、没有头绪,甚至关于架构的规模如何?系统的性能如何?程序容易修改吗?系统的部署模型是怎么样?系统的响应如何?一切都是那么难以回答、难以定论 … …

一. 介 绍

软件架构设计可从宏观上说明系统的组成与特性,是经系统性思考、权衡之后在现有资源约束下的最合理决策, 最终明确的系统骨架包括子系统、模块、组件,以及它们之间的协作关系、约束规范、指导原则。覆盖需求、技术栈、成本、组织及架构、可扩展性、可维护性等。

1) 系统性思考的合理决策:比如技术选型、解决方案等;

2) 明确的系统骨架:明确系统有哪些部分组成;

3) 系统协作关系:各个组成部分如何协作来实现业务请求;

4) 约束规范和指导原则:保证系统有序,高效、稳定运行。

软件架构设计要完成两项工作,一是分析,二是设计。分析是分析需求,设计则是设计软件的大致结构。很多的方法论把分析和设计两种活动分开,但其实两者是很难区分的,做分析的时候会想到如何设计,而思考如何设计反过来又会影响分析的效果。可以说,两者之间是相互联系和不断迭代的。

在架构设计范畴中对于业务的理解和经验积累同样重要,当短期的需求与整体设计冲突时,有些设计师会偏向于当前的设计,缺乏对整体的思考,对未来的思考,而腐化了系统整体应有的架构。当程序员跟产品经理开撕,这个需求不能做,那个需求调整需要很长时间。其中也折射出架构设计的不合理可能。

二. 架构即未来

软件架构是一个系统开发生命周期中最前端的部分,也是最关键、核心的部分。它决定了后续代码的走向,决定了项目的走向,有时候甚至能决定一家公司的成与败。架构的目标是保证系统的高可用、可扩展、可伸缩、安全等一系列的指标,好的开始相当于成功了一半。

软件架构是系统的顶层结构,要着眼于全局,包括硬件、操作系统、网络环境,以及从立项到维护之间的所有过程(需求、设计、编码、部署、维护及迭代)。架构决定子系统之间的关系、分层与通讯方式、公共设计原则/风格、功能需求与非功能需求的优先级与取舍原则等。架构是多种结构的体现,就像建筑物的结构会随着观察动机和出发点的不同而有多种含义一样,软件构架也表现为多种结构,常见的软件结构有:模块结构、逻辑或概念结构、进程或协调结构、物理结构、使用结构、调用结构、数据流、控制流、类结构等。

优秀的架构可促进项目的良性发展,设计之初将远的近的都考虑进来,最后会使得项目朝着好的方向发展,降低项目所投入的时间、金钱、人力成本。坏的角度来看,对于多变的软件而言,有一句话说的好,计划跟不上变化。需求不确定性与人,也与业务相关。架构的变化往往成本很高,好的架构可使变化发生在局部而不影响整个系统。架构和设计的可扩展决定了需求变更、业务增加时付出的成本;标准化、规范化、可传承有利于提升团队的效率;“播种,施肥,除草”,播种时的付出会影响除草的成本,而这个成本可能是数倍甚至数十倍的,把初始做的更好后面才能更省心。

架构的分类上可分为业务架构、应用架构、技术架构,部署架构。业务架构是生产力,应用架构是生产关系,技术架构是生产工具。业务架构决定应用架构,应用架构需要适配业务架构并随之不断进化,同时依托技术架构、部署架构而最终落地。

1. 架构设计的目标

架构设计的目的是为了解决软件系统的复杂度带来的问题,对系统的高可用、高性能、可扩展、安全、伸缩性、简洁等做系统级的把握。常规来说功能需求决定业务构架,非功能需求决定技术构架,变化案例决定构架的范围。功能需求定义了软件能做什么,根据业务需求来设计业务构架,以使得未来的软件能够满足客户的需要。非功能需求定义了一些性能、效率上的约束、规则,技术构架要能够满足这些约束和规则。变化案例是对未来可能发生的变化估计,结合功能和非功能需求就可确定一个需求的范围,进而确定一个构架的范围。

结构面的考虑因素

1) 需求的符合性:正确性、完整性;功能性需求、非功能性需求;

2) 总体性能(内存,数据组织和内容,任务,网络操作,关键算法,硬件和其他接口影响);

3) 运行可管理性: 便于控制系统运行,监视状态,错误处理,模块间通信与可维护性;

4) 与其他系统接口兼容性;

5) 与网络、硬件接口兼容性及性能;

6) 系统安全性;

7) 系统可靠性;

8) 业务流程及信息的可调整性;

9) 使用方便性;

11)架构样式的一致性.

注:运行时负载均衡可以从系统性能、可靠性方面考虑。

架构设计的目标

确定系统边界,确定系统在技术层面上的做与不做。

确定系统内模块之间的关系,依赖关系及模块的宏观输入与输出。

确定指导后续设计与演化的原则,使后续子系统或模块设计在规定的框架内继续演化。

确定非功能性需求目标,涉及如下:

可靠性:系统对于用户的商业经营和管理来说极为重要,因此必须非常可靠。

安全性:软件系统所承担的交易的商业价值极高,系统的安全性非常重要。

可扩展:  在用户的使用率、用户数快速增长下保持合理的性能,适应用户的市场扩展可能性,以及包括对系统进行功能和性能的扩展。

可定制:  同样的一套软件,可以根据客户群的不同和市场需求的变化进行调整。

可维护:  一是排除现有错误,二是将新的需求反映到现有系统中去。一个易于维护的系统可以有效地降低技术支持的费用。

客户体验:软件系统必须易于使用。

市场时机:以最快速度争夺市场先机非常重要。

组织结构方面

1) 开发可管理性:便于人员分工、利于配置管理、大小的合理性与适度复杂性;

2) 可维护性:与运行可管理性不同;

3) 可扩充性:系统方案的升级、扩容、扩充性能;

4) 可移植性:不同客户端、应用服务器、数据库管理系统;

5) 需求的符合性.

2. 设计的思维模式

架构设计的本质是管理复杂性。抽象、分层、分治和演化思维是架构设计师征服复杂性的四种根本性手段。

A.抽象化设计思维

抽象是对某种事物进行简化表示或描述的过程,抽象让我们关注要素,隐藏额外细节。抽象能力的强弱,直接决定我们所能解决问题的复杂性和规模大小。架构设计应摒弃具体细节,抓住软件最上层、优先级最高、风险最大的那部分需求。

合理地使用抽象可提升设计的简单性,改善软件开发的质量。通常使用到两种抽象方法,基于过程的抽象和基于数据的抽象。基于过程做抽象时,会将待解决的问题分解为一个个小的子问题,每一个子问题分别由一个独立的模块、函数、类等来完成。良好抽象的系统在其中某一个部分的实现被替换的情况下,不需要修改设计仍然能正常工作。基于设计良好的抽象可组合构建出功能更加强大和复杂的系统。基于数据抽象的方法可以将复杂数据结构的使用和其构造分离,通过使用“抽象数据”的方式,用户可以通过明确定义的一系列接口对其进行访问和操作,隐藏对象的内部特征,对外部环境透明。而不适合问题本质的抽象方式不仅会影响软件设计的简单性,也可能给软件的可维护性带来负面影响。

B.分层的设计模式

分层技术在计算机领域中有着悠久历史,分层优势在于:上层的逻辑无需了解所有底层逻辑,它只需要了解和它邻接层的细节。TCP/IP协议栈就是通过不同的层对数据进行层层封包,不同层间的耦合度明显降低。通过严格的区分层次,大大降低了层间耦合度。分层原则对软件进行结构上的划分,定义了结构的不同部分的职责,总体思路并无特别,只是将系统进行有效组织的方式,在完成分层之后软件架构已经清晰化了。

为了构建一套复杂系统,可把整个系统划分成若干个层次,每一层专注解决某个领域的问题,并向上提供服务。有些层次是纵向的,它贯穿所有其它层次,称为共享层。分层也可以认为是抽象的一种方式,将系统抽象分解成若干层次化的模块。

C.问题分治化思维

分而治之也是应对和管理复杂性的一般性方法。对于一个无法一次解决的大问题,一般先把大问题分解成若干子问题,如果子问题还无法直接解决,则继续分解成子子问题,直到可以直接解决的程度,这个是分解(divide)的过程;然后将子子问题的解组合拼装成子问题的解,再将子问题的解组合拼装成原问题的解,这个是组合(combine)的过程。

D.演化式架构思维

架构既是设计出来的,同时也是演化出来的,在设计中演化,在演化中设计,一个不断迭代的过程。架构师除了要利用自身的架构设计能力,同时也要学会借助用户反馈和进化的力量,推动架构的持续演进,这个就是演化式架构思维。能够不断应对环境变化的系统,才是有生命力的系统,架构的好坏,很大部分取决于它应对变化的灵活性。所以具有演化式思维的架构师,能够在一开始设计时就考虑到后续架构的演化特性,并且将灵活应对变化的能力作为架构设计的主要考量。

软件架构需根据业务的发展而不断变化。如果没有把握这个本质,在做架构设计时就很容易陷入试图一步到位的误区,期望不管业务如何变化架构都稳如磐石。业务的发展和变化总是很快,实践中遵循演化优于一步到位的原则。实践中可以参考:首先设计出的架构要满足当时的业务需要。其次,架构要不断地在实际应用过程中迭代,保留优秀的设计,修复有缺陷的设计,改正错误的设计,去掉无用的设计,使得架构逐渐完善。当业务发生变化时,架构要扩展、重构,甚至重写;代码也许会重写,但有价值的经验、教训、逻辑、设计等却可以在新架构中延续。严格的对待每一次的迭代,确保计划的完成、确保软件的质量、确保用户的需求得到满足,这样才是正统的迭代之路。

最开始的设计一定只是一个原始架构,但对于后续的架构设计而言非常重要。迭代设计也可称之为增量设计,每一次的迭代都是在上一次迭代的基础上进行的,迭代将致力于重用、修改、增强目前的架构,以使架构越来越强壮,得到一个稳定的架构。

3. 典型设计实践

软件模式通过定义一组互相协作的软件元素来解决软件架构设计问题,是实现架构设计的具体手段。架构设计需以全局的视角统筹计算、数据、存储、通讯等来综合考虑与权衡。

架构模式有助于定义程序的基本特征和行为。譬如对于系统结构设计使用层模式,对于分布式系统使用代理模式,对于交互系统使用MVC模式。模式本就是针对特定问题的解,一些架构模式很自然让程序适应大规模,有些让程序变得灵巧敏捷。可结合需求的特点和目标来采用相应模式设计架构。设计模式是从代码层面提炼出来的一种总结,可使代码的耦合度达到最大限度的分离,从而可使代码更好的被复用,更易被替换,更好的拥抱需求的变化。

1) 设计简单化原则

【Keep It Simple】"简单要比复杂有效",这就是简单设计模式的基本思路。一个复杂的架构不论是测试还是维护、以及后期的开发迭代都是困难的,也会导致沟通成本的上升,架构应尽可能的简单明了,用最简单的方案来解决问题。

无论是结构还是逻辑的复杂都会存在各种问题。架构越简单,稳定性就越好,这也是行业的共识。简单的架构并不等于实现简单,简单的架构需要设计者花费大量的心血,也要求设计者对技术有很深的造诣。简单的架构设计有助于加快开发团队对架构的理解。简单意味着问题不会非常的复杂,架构是解决需求的关键,无论需求再怎么复杂多变,总可以找出简单稳定的部分,以稳定的部分作为基础,再根据需要进行改进扩展,以解决复杂的问题。其次,简单性还体现在表示的简单上。也体现在系统内层次、模型、动静、流量、链路、读写、时间(异步化)等不同维度、粒度上的抽象、设计的简化。

“弹性的设计”满足需求变更的背后往往所付出的是复杂的设计。解决问题的大方向应是将复杂问题简单化,但在具体实施设计过程中,很多人可能会将简单问题复杂化,在设计模式的运用上易犯这个错误,如何尽可能的做到设计的简单明,落实到每个类/模块的具体实现上要真正能体现系统事物的本质,本质特征只有一个,代码越接近它,表示设计就是简单明了,越简单明了,承载的系统就越稳定、可靠、可信。

2) 高内聚、低耦合

高内聚、低耦合是软件工程中的概念,是判断设计好坏的标准,是架构设计最主要的目标。具有更好的重用性、维护性及可扩展性,软件设计原则是其实现的指导方针。设计中通常用耦合度和内聚度作为衡量模块独立程度的标准,划分模块的一个准则是高内聚低耦合。从模块来看,高内聚是尽可能使类的每个成员方法只完成一件事,低耦合是减少类内部一个成员方法调用另一个成员方法。从类的角度看,是减少类内部对其他类的调用。从功能模块来看,是减少模块之间的交互复杂度即横向:类与类之间、模块与模块之间,纵向:层次之间;尽可能内容内聚,数据耦合。

降低依赖,解除耦合

  • 反向依赖

只从上往下依赖,将公共的重复功能的模块抽取出来。公共模块必须足够的功能单一,不能有其他业务的逻辑判断在里面。在整个模块依赖关系里,应该是一棵树状结构的关系图,而不是一个网状的关系图。

  • 配置解耦

每个模块的动态属性,设定配置项,可使用配置中心进行实时更新并生效。

  • 权限解耦

功能的权限控制及安全验证,原来是与业务代码集成。在服务网格化架构中,权限功能划分归属到通讯边车模块,业务和技术面通过API接***互。

  • 流量解耦

服务网格架构下,流量的控制下沉到边车通讯模块。支持流量拆分,服务之间的流量支持隔离性。

  • 数据解耦

保证模块之间的数据不相互影响,同一个模块的冷热数据解耦。系统运行时间长了后也会积累大量的数据,为保证系统的性能稳定,要减少数据量太大造成的性能降低。

  • 扩容解耦

好的架构设计需具备横向扩展能力,只通过增加硬件的方式就能提高系统性能。

  • 部署解耦

支持快速试错、灰度发布。同一个模块先部署升级几台服务器到新版本,重启完成后流量切入后即可验证当前的部署是否有问题,无问题就继续部署其他的节点,如有问题则马上回滚到上一个版本。

  • 动静解耦

当同一个模块的瞬间有非常高并发时,纯粹的流量解耦仍不够。不能让前端流量冲击后面真正的关键处理功能,需要更细的流量解耦,实现静态、动态资源访问分离。

可靠的系统是高度模块化(不暴露无关接口),最小侵入(常规使用无需继承之类的强耦合关系),易集成到完全不同类型的代码库(比如尽可能使用可移植代码而非直接调用平台API),对外部环境有极少的假定,极力与其他系统的实现细节解耦。

3) 接口的设计原则

接口,可以理解为契约,一种约定。系统各个模块之所以能组合工作,正是因为通过定义好的API来交互。在对外提供抽象API的同时,也可能需使用其他模块的API作为自身运行的基础。接口设计要职责单一,尽可能隐藏内部实现,避免通过继承导致行为扩散,命名及风格统一,增强可理解性,定义好版本等保障接口稳定性、兼容性,对接口进行验证的思路是保证接口的可测试性。

a)  封装原则

优秀的接口设计会隐藏实现的细节,仅把需要的接口呈现给关联方,而具体的实现则对外部透明。

b)  最小职责

一个类实现的功能应尽可能紧凑,只处理紧密相关的功能,一个方法更应该只做一件事情,需要设计人员发现类的不同职责并将其分离。譬如一个微服务应尽可能职责单一,提供的接口也尽可能单一。

c)  最小接口

暴露给用户使用的方法应尽可能的少,公布的方法可能被客户频繁使用,如设计上存在问题或是要进行改进,都会对现有方法造成影响,因此需要将这些影响减到最小。另外,一些较轻型的共有方法应组合为单个的方法,降低用户和系统的耦合程度,具体实现可以通过外观模式或委托模式。

d)  最小耦合

设计的类和其它类的交互应该尽可能的少,如果发现一个类和大量的类存在耦合,可以引入新的类来削弱这种耦合度。在设计模式中,中介模式和外观模式都是此类型的应用。

e)  分层原则

封装原则的提升。一个系统,往往有各种各样的职责,如有负责和DB打交道的代码,也有和用户打交道的代码,把这些代码根据功能划分为不同的层次,就可对软件架构的不同部分实现大的封装。

参考博客:【SOLID原则精解之接口隔离原则 ISP】

4) 最少知识原则

一种面向对象程序设计的指导原则,描述了一种保持代码松耦合的策略。每个单元对其他单元只拥有有限的知识,只了解与当前单元紧密联系的单元;为软件设计带来了两个主要的益处:更好的信息隐藏和更少的信息重载。打个比方,人可以命令一条狗行走,但是不应该直接指挥狗的腿行走,应该由狗去指挥它的腿行走。应用该原则有利于降低模块间的耦合,提升软件的可维护性和可重用性,但可能会导致不得不在类中设计出很多用于中转的包装方法,提升类设计的复杂度。

5) 门面控制模式

把职责赋予系统、设备或子系统的表示类(门面控制器),或者某个用例的表示类,让控制器接收事件并协调整个系统的运作。两个或多个对象间有交互的情况下,为避免直接耦合,提高重用性,创建中间类并赋予职责,对象的交互交由中间类协调。系统之间亦类同。

外观模式,可将调用者和复杂的业务类隔离,调用者无须知道业务类之间的复杂关系就能进行业务处理,从而大大降低了调用者和业务类之间的耦合度,适合用在内部关系较为复杂的组件中,也适合用在业务层向表示层发布接口的情况中。通过参数来实现数据的传递。

6) 信息专家模式

为子系统/模块分配职责的通用参考原则,把职责分配给拥有足够信息可以履行职责的专家,形象化来说就是将数据、事件、状态等传递给承担该数据处理的单一职责模块进行处理,可作为职责边界的划分依据。合理的职责分配能力,也就是每个类/组件/子系统应该承担什么职责,如何保证职责单一,它们之间如何协作,需要职责边界清晰。打个比方,当不确定哪个团队应该负责某个微服务时,一般原则也是谁拥有数据谁负责,基于有界上下文(一般是边界比较清晰的领域数据源)构建微服务。

7) 数据的一致性

分布式体系结构中,要保证数据的一致性,可用性和分区容忍性。但最多同时满足这三项中的两项,BASE理论思想是即使无法做到数据的强一致性,但可采用适当方式达到最终一致。

软状态是指允许系统存在中间状态,而该中间状态不会影响系统的整体可用性。分布式存储中一般单份数据至少存三副本,允许不同节点间副本同步的延迟就是软状态的体现。系统中的所有数据副本经过一段时间后最终能达成一致状态为最终一致性。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。

微服务体系架构中,状态管理也许会成为首要和中心的问题。提供连接性和集成意味着系统本质上要么是查询状态,要么是更改状态(或两者)。对于给定的实体或信息,查询和更改状态的方法通常并不唯一。为避免数据损坏或意外结果,显式声明状态并使用某种策略来处理更改和查询状态带来的副作用,通过这种策略,每个微服务组件可以实现更高物理级别上的自治,从而允许更快的更改速度。

8) 稳定、可靠原则

稳定、可靠性是系统在给定的时间间隔及给定环境条件下,按设计要求,成功地运行程序的概率。成功地运行不仅要保证系统能正确地运行,满足功能需求,还要求当系统出现意外故障时能够尽快恢复正常运行,数据不受破坏。架构设计及部署上要考虑N+1冗余度(中心/服务器/系统/中间件/服务/数据等),避免单点问题故障、宕机。面向服务化的系统架构可通过服务治理,譬如限流、降级等来保障系统的可靠、稳定运行, 要实现隔离故障设计,通过断路保护避免故障传播和交叉影响。

服务方面,网络拓扑中任意节点丢失都有可能导致服务不可用,如边缘系统能提前检测到具有高风险的节点那么就可避免。系统角度,节点之间能互通状态和诊断信息,使得在系统层面部署故障检测、节点替换、数据检测等十分方便。数据角度,指数据通信方面等的可靠性。

9) 可扩展、伸缩性

对扩展的渴求源于需求的易变,需要架构具有一定的扩展性以应对变化,适应未来可能的变化。但扩展性和稳定性,扩展性和简单性都存有矛盾面,因此需要权衡投入、产出比,以设计出具有适当延展性的架构,譬如系统能做到无缝升级、扩容、扩充的可行性和便利等。好的系统架构能获得低延迟和高吞吐量,扩展性的目标是用可接受的延迟获得最大的吞吐量。

扩展包含两个层面:一是功能的可扩展性,主要是针对平台框架是否设计并预留了足够的扩展点,后续可以方便的增加各种功能或有第三方实现各种插件。另一种是性能的可扩展性,系统的弹性扩容能力,即随着系统用户量、并发的增加是否可实现弹性扩容,通过增加硬件设备就能提供更强的处理能力,这种一般称为可伸缩性。可伸缩性是高性能、低成本和可维护性等诸多因素的综合考量和平衡,可伸缩性讲究平滑线性的性能提升,更侧重于系统的水平伸缩,通过廉价的服务器实现分布式计算。

10) 架构的重用原则

重用是为了避免重复劳动、降低成本。架构设计的过程中可将一些公共部分抽象提取形成公共类和接口,其它功能模块所需相关功能可以调用,以达到重用目的。现实中,已在架构重用上做了很多的工作,譬如框架。使用功能分解的规则有助于提高重用性,因为每个类和方法的精度都提高了。

微服务架构中,每个服务都必须实现许多跟基础设施相关的功能,包括可观测性和服务发现模式,还需实现外部化配置模式,以在运行时向服务提供数据存储凭据等配置参数。在开发新服务时,一种更好的方法是在处理这些问题时应用微服务基底模式,在现有成熟的基底框架之上构建服务。

11) 设计系统运行时

现在的系统趋于复杂化、大型化, 构架设计应使系统可以预测系统故障,防患于未然。因此通过合理的构架规划系统运行资源,便于控制系统运行、监视系统状态、进行有效的错误处理;为实现上述目标,模块间通信应尽可能简单,同时建立合理、详尽的系统运行日志,通过自动审计运行日志了解系统运行状态、进行有效的异常处理。

关注控制流、通讯机制、资源争用、锁/令牌环机制、同步异步、并发/串行,同时也要考虑质量属性。

基于系统运行时的质量需求考虑问题,关注于系统的非功能需求。譬如客户常常要求系统的功能画面的最长响应时间不超过4秒,能满足2000个用户同时在线使用,基于角色的系统资源的安全控制等等。

12) 监控、能观性

运维的一项重要工作就是搞明白应用在运行时的一些行为,同时能够根据错误的请求或者高延迟等故障进行诊断排错。设计具备可观测性的服务:

  • 健康检查API:可以返回服务健康状态的API;

  • 日志聚合: 将产生的日志写入集中式的日志服务器,提供日志搜索,也可据日志情况触发报警;

  • 分布式追踪:为每一个外部请求分配一个唯一ID,用于在各个服务之间追踪请求;

  • 异常跟踪:将程序异常发送到异常跟踪服务,给开发者发送告警并且跟踪每一个异常的解决;

  • 应用指标:供维护使用的指标,如计数器等,导出到指标服务器;

  • 审计日志:记录用户的行为。

13) 系统安全性设计

随着应用的不断深入和扩大,涉及的场景和信息也越来越多,大量涉密信息在网络上传输,因此对系统安全性的考虑已成为系统设计的关键,需从各个方面和角度加以考虑来保证数据资料的绝对安全。譬如在Web应用中,面对各种安全风险(SQL注入,XSS、CSR攻击等),各种漏洞是否能堵住,架构是否可以做到限流作用,防止DDOS攻击等。在微服务架构体系中,用户身份验证的工作通常由API网关/数据面来完成,服务调用方必须将有关用户的信息(如身份和角色)传递给它调用的服务,常见的解决方案是应用访问令牌模式,将访问令牌(如JWT)传递给服务,对令牌加以验证并获取有关用户的信息。

参考博客:【云原生零可信网络下-应用服务的安全】

4. 设计视图及架构

架构可从多视角来看,就像建筑架构,一般有结构、管线、电气等。4+1视图是描述应用程序架构的绝佳方式,四个不同的软件架构视图,每一视图描述架构的一个特定的重要侧面,且包括一些特定的元素及它们相互之间的关系。

  • 逻辑视图:在面向对象的语言中,这些元素是类和包。它们之间的关系是类和包之间的关系,包括继承、关联和依赖。

  • 实现视图:构建编译系统的输出,由表示打包代码的模块和组件组成。它们之间的关系包括模块之间的依赖关系以及组件和模块之间的组合关系。

  • 进程视图:运行时的组件,每个元素都是一个进程,进程之间的关系代表进程间通信。

  • 部署视图:进程如何映射到机器,视图中的元素由(物理或虚拟)计算机和容器进程组成,机器之间的关系代表网络。该视图还描述了进程和机器之间的关系。

视图中的+1是指场景,负责把把视图中的元素如何协作串联在一起。每个场景负责描述在一个视图中的多个架构元素如何协作以完成一个请求。例如,在逻辑视图中的场景展现了类是如何协作,在进程视图中的场景展现了进程的协作。

系统的物理架构

主要考虑硬件选择和拓扑结构,软件到硬件的映射,软硬件的相互影响。

5. 持续交付 & 部署

实现项目/系统的持续交付和持续部署是DevOps中的关键环节。持续交付能够以可持续的方式安全、快速地将所有类型的更改(功能、配置、错误修复和实验等)交付到生产环境或用户手中,其关键特征是软件总是随时可以交付,它依赖于高水平的自动化,包括自动化测试。持续部署把持续交付提升到了一个新的水准,实施持续部署的高绩效组织每天多次部署到生产环境中,生产中断的次数要少得多,且可以从发生的任何事情中快速恢复。

微服务架构天然支持持续交付和持续部署。

持续交付和部署的目标是快速可靠地交付软件,评估的四个有用指标如下:

  • 部署频率: 软件部署到生产环境中的频率;

  • 交付时间: 从开发人员提交变更,到变更被部署的时间;

  • 平均恢复时间:从生产环境问题中恢复的时间;

  • 变更失败率: 导致生产环境问题的变更提交百分比。

系统要小构建、小发布、快试错、不断迭代与交付。传统组织中部署频率低,交付时间很长,特别是开发和运维人员通常都会在维护窗口期间熬到最后一刻。相比之下,DevOps组织经常发布,通常每天多次发布,生产环境问题要少得多。如,亚马逊可做到秒级就将代码更改部署到生产环境,Netflix的一个软件组件的交付时间为分钟级。

持续交付和部署缩短了产品的上市时间,使企业能快速响应客户反馈,提供其所期望的可靠服务。开发人员可因此花费更多时间来提供有价值功能,而不是四处担任救火队员。

三. 软件架构的灵活设计

软件组件模块之间松耦合常见有两种方式:接口和消息,这两者如同人的骨关节一样,连接着松耦合的双方。消息比接口更加松耦合,因为接口方法有可能改变,导致接口的使用者跟着变化,而通过消息,消息生产者只要发送消息到消息系统就可以了,不再管是谁来接受消费这个消息,消息生产者由此和消费者完全解耦了。设计示例:

复杂性

假设原来是只有两个组件进行交互:

需求不断扩展变化,增加了第三个组件,那么这三个组件之间任意交互,复杂度就会提高:

复杂度以指数级的增长是惊人的,当我们增加到六个组件,复杂度将是惊人的。

自然界是如何应对这复杂呢?在物理中被称为构造定律。构造定律致力于描述能量和物质在物理网络(如河流)和生物网络(如血管)中的流动,理论提出,如果一个流体系统要继续存在(比如,生存),那它必须始终提供更容易的方式来获得这个系统中的流体。换句话说,系统应该致力于将能量消耗减少到最低限度,而同时将消耗单位能量产生的熵提高到最大限度。进化实质上是这么一个过程,即生物体不断的重组他们自身,以使能量和物质能够尽可能迅速高效的通过他们。更好的流体结构,不管它们是动物还是河流,将取代那些较差的结构。

如何设计出一种结构以促成流经这个结构的用户请求能更有效地获得响应呢?很显然,多个组件发生任意关联的方式肯定是不经济的,熵值副作用很大,如果变成以下这种结构,组件能够实现分组,三个元素是一组,另外一组是四个元素,组与组之间通过一个代表元素关联:

单一职责功能是关键,意味着只做一件事。它与让事情DRY原则是一致的:每一个知识都必须有一个单一的、明确的、在一个系统内的权威明确表示。不要重复,需要干脆。程序中的每一个重要的功能都应该在源代码中的一个地方实现,将业务规则、长表达式,if语句、数学公式和元数据等各自放在一个地方。单一职责也与委托原则有关,只有放弃一些才能获得一些,有舍有得,放弃的就是委托其他类实现:不要自己做所有的事情,可以委托给相应的类去完成。因为每个组件都秉持单一职责,组件之间才可能发生唯一的一个关联关系。

高凝聚意味着模块的复杂性降低。因为变得有条理,复杂性自然降低。组件之间的强关系用耦合表达,保留下好的强关系也就是高凝聚,去除不好的强关系也就是低耦合。高凝聚、低耦合是降低复杂性提高灵活性设计中重要的宗旨。

除了静态的结构关系以外,高凝聚、低耦合还表达为对象的方法行为上,这需要通过分配职责来实现。什么是职责?它包括三个方面:

1)   对象应该执行的动作;

2)   对象包含的知识如:算法 约束 规格 描述;

3)   当前对象影响其他对象的主要因素。

以报童向买报人收钱为例:报童应该向顾客收取两块钱的买报费,他是直接把顾客的钱包拿过来从中掏出两块钱,还是请求顾客自己从钱包里掏出两块钱呢?无疑是后者。

这两者的区别是什么?Tell, Don't Ask原则:

报童只要告诉顾客做什么,而不直接参与怎么做(你的钱够吗?够才能买)。报童只给顾客一个命令,而不必关注顾客是如何执行这个命令。Tell, Don't Ask原则能够让我们保证两个组件之间在动作上不会发生过于细节过多的耦合,而只是通过一个消息就能完成,通过发送消息告诉对方我需要什么,而不是直接把对方拎过来搜身。至此,完成了两个组件之间最大松耦合,实现了架构设计的最大可能的灵活性。

软件架构复杂性必然如自然界任何事物一样不断发展,作为软件架构师如何学习大自然的巧妙设计,如同庖丁解牛一样切分复杂性为单一职责,再从结构和行为的高凝聚关系方面进行组合或消息传递,从而真正实现高凝聚、低耦合的灵活性目标。

---------未完,请看(下)-----------

万字长文探讨可信构架之道(下)


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。