微服务架构:成为架构师的第一步,就是先要搞清楚什么是架构设计
Posted jinggege795
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微服务架构:成为架构师的第一步,就是先要搞清楚什么是架构设计相关的知识,希望对你有一定的参考价值。
微服务架构设计
◎ 微服务架构的难点
◎ 架构设计
◎ 微服务的核心组件
微服务架构有两个难点:一是微服务架构本身的核心组件的落地设计,即技术实现;二是微服务在物理上的层次结构和拆分设计,这也是微服务架构设计是否成功的关键因素。
微服务架构的难点
讲到微服务的核心架构,大家不妨回忆一下图1.3,关于服务的注册与发现,微服务架构中也采用了类似的设计思路,大多数技术框架都是依托于这种方式实现了微服务架构的核心组件:远程调用和服务治理。当然,仅仅解决了服务的相互调用问题是远远不够的,在使用微服务架构的过程中,还会遇到各式各样的难题,这些难题都是必须要解决的问题。
例如,如何管理大量的服务配置?微服务之间都是独立的,它们往往拥有自己的技术体系、独立的数据存储,自然每个微服务都会拥有自己的配置文件,其中还包括相同服务在不同环境下的配置,最常见的就是数据库地址、服务域名端口号等。随着微服务数量的增加,有关配置管理的工作量则呈几何倍数的增长。因此,实现便捷的配置管理似乎成了刻不容缓需要解决的问题。
有过微服务项目经验的人不难发现,如果要画一个微服务的依赖关系图,那么它一定像一张网一样,杂乱无章、纵横交错,虽然看似各司其职,但是环环相扣,这样的网状式结构带来了一个严峻的问题,就是一旦其中任何一个节点不可用或运行缓慢,它带来的阻碍或影响就可能是灾难性的,因为任何一个服务都可能出现在业务功能的关键路径上,所以完善的监控和容错机制也是迫切需要解决的问题。
最后,还有一个最大的难题,就是关于日志的跟踪和收集,日志是在开发和调试程序时的关键利器,我们无法通过DUBUG等工具进行程序执行过程的追踪,那么一个没有日志的应用程序和一个拥有完善日志体系的应用程序,在解决各种问题时的效率是完全不同的。而微服务分散的服务设计理念,导致了日志在物理上的分散,可能在调试单个服务时不会有问题,但是当一个完整的业务功能需要各个服务组合而成时,日志则很难被收集起来,更不用说对问题的追踪和分析了。
因此,对日志整体的追踪也是微服务架构的一大技术难题。说了这么多,是不是感觉微服务架构太难了?确实很难,不过问题都由前人解决了,我们现在是站在巨人的肩膀上来学习微服务架构的,这些难题的解决方案和技术实现也会重点在本章中探究和学习。
当然还是那句话:理论先行。虽然可能有些枯燥,但其理论就是对过往做事情的经验和思路的总结,花时间看看别人的经验总是好的,一方面可以少走些弯路,另一方面可以提高理解问题和归纳事物本质的能力。下面先来了解一下架构设计的本质。
架构设计
相信大家应该看过不少关于软件架构和设计的讨论,讲得最多的可能就是什么是架构师,如何成为一个好的架构师之类的话题了。笔者认为成为架构师的第一步,就是先要搞清楚什么是架构设计,架构设计的目的是什么。
在很多时候,架构设计可能会受到各种场景的限制,其中就包括技术手段、组件和框架等,尤其是在特定的假设和约束条件下,设计出最合适的架构显得尤为重要。
那么,到底什么才是架构设计呢?架构设计的关注点是什么呢?
下面就来依次了解这些问题。
了解什么才是架构
软件架构是有关软件整体结构与组件的抽象描述。换句话说,就是采用一些通用、具有代表性和概括性的语言将软件的整体结构描述出来,并提供其行为方式的说明。总体来说,软件架构告诉了我们系统能做什么,怎么做的,如图2.1所示。
架构还给程序提供了一个基础,人们可以依托它在上面构建软件。在同一个软件中,人们常常使用各种不同的设计模式或高级框架,但大多都会使用一系列相同的架构模式或设计原则,这种相同的架构模式被称为软件的架构风格。微服务架构就是一种架构风格。
架构一般采用一些描述性的视图工具来完成,在建筑学中,称之为蓝图。软件架构中包括了很多视图,如逻辑视图、结构视图、线程视图、部署视图等,当然,架构不是文档本身,而是系统元素的一组原则、属性或依赖性,它是抽象的东西,是我们思考得出的创建系统的方式。图像、图表或描述仅是记录架构的规划。
图2.2所示为一个简单的软件架构,可以简单地表述系统的大致运行结构。
那么,什么是软件设计呢?
软件设计的3个阶段
软件设计是程序员按照特定顺序撰写计算机数据和指令的集合,它可以是撰写基础上的二进制0和1比特,也可以是创建在比特之上的各类软件语言、算法、架构、程序、图像化代码。换句话说,软件设计是将软件需求转化为软件实现的过程,即将软件能做什么转变成怎么做的过程。
软件设计的过程大体上可以划分为3个阶段,即软件需求规范阶段、高级设计阶段和详细设计阶段。当然,这个划分不是绝对的,细分还有总体设计、概要设计、基础设计等,这里为大家介绍比较常见的3个设计阶段,如图2.3所示。
软件需求规范阶段的目的很明显,就是把客户的原始需求转化为可以理解、可以衡量的软件的功能需求,也可能包含非功能需求,如安全和性能需求等。
高级设计是将系统粗略地划分为若干模块,以抽象的形式记录下来,并描绘它们之间的相互作用。这种设计视角侧重于系统及其所有组件如何以模块的形式实现,不同的模块之间是如何相互作用的,而不关注具体模块内部的实现细节。
详细设计则是在搞清楚系统大致架构和模块间的关系后,对模块及其实现进行更加详细的设计,它定义了每个模块的逻辑结构以及其与其他模块通信的接口等。
从图2.3中可以看出,软件的设计流程并不是一个单向的链状结构,而是更像一个可以无限循环的环状结构。其实任何事情都有渐进明细的特点,软件也是如此,无论是客户需求还是产品设计,都是随着我们不断地深入,在不断地变化,并不断地去近似于我们想要的样子。
因此,一个好的软件架构设计一定是要能够不断进化的,任何阶段都有可能产生新的变化,这些变化可能会影响最初的设计,这时就需要设计者去判断、去权衡利弊,新的设计是更合理,还是对之前的影响更大,这些决策才是设计中最难的地方。
软件架构的目的与方法
架构设计其实就是通过一定的设计手段,得到软件的架构的过程。即使掌握了完善的视图工具,分清了步骤来执行软件设计的过程,软件架构设计仍然不是一个轻松的工作,它可能还需要结合业务的场景、软件的约束和假设条件来考虑各种要素,如软件的扩展性、健壮性、可用性、安全性、模块化等。当然,鱼和熊掌不可兼得,在设计过程中,不可能将每种要素都考虑到极致、设计到极致。软件架构设计的精髓就是在遵循一定的设计原则下,对各种设计要素进行合理的分析和取舍,从而设计出最合适的架构方案。
那么,为了能设计出最合适的架构,需要遵循哪些比较科学的设计原则或考虑哪些关键要素呢?可能大部分程序员听到最多的一句话就是“高内聚低耦合”,如图2.4所示,但笔者遇到的很多程序员都觉得没有新意。这确实是一个非常经典的设计原则,但是又有多少人能真正在软件开发过程中坚持做好这一点呢?
下面先来看什么是耦合。在不同的领域,耦合可能有着不同的含义:在软件中,人们通常会把耦合作为一种度量词来使用,用来表示程序中模块或者代码逻辑之间的依赖程度,例如,某模块之间的耦合性很低,某代码之间的耦合度较高,等等。因此,无论是大到系统或服务,还是小到某一个方法,人们希望它的逻辑是内聚的,调用者无须关心其他逻辑,只要使用它就好了,也不需要关心其他的依赖,无论是修改还是替换这个服务或方法,对其相关的依赖影响都是很小的,这就是“高内聚低耦合”。
那么,如何才能设计出这样的程序?当然也有方法,而且在真正的架构设计实战中,除了耦合性,可能还需要关注扩展性、健壮性、可用性、安全性、模块化等,下面来了解一下都有哪些方法?
1. 使用增量和迭代
一个软件的开发往往是渐进明细的,架构设计在一开始就要明白一件事,那就是不要想着一开始就让你的架构很完美,应用程序可能需要随时间的变化以满足新的需求和挑战,你的架构应该保证具有足够的灵活性,往往一个灵活可变的粗糙架构设计要比一个一开始就考虑很多条件,可能连细节都设计得很完善、很全面的稳定架构要实用得多。
因为越完善、越全面的架构带来的问题就是只要有变化,改造架构的成本也就越高。在软件工程中,很多因素甚至业务需求都不能在一开始就考虑得很全面,因此过度的设计往往是错误的选择。
所以,架构师都很推崇从基础架构开始设计,然后通过不断地迭代,向系统增量地添加细节设计,从而逐渐完善架构。采用增量和迭代的设计方法还能在一开始就告诫设计者,这个架构后面肯定会不断地修改和完善,促使设计者在一开始就要注意考虑架构的灵活性、扩展性等。
2. 善于使用图形来表达设计
可以借助一些设计工具(如可视化的视图工具)或一些通用的建模系统(如UML等)来表达架构的设计。图形化的好处是直观,直观的好处是可以快速地、有效地给所有利益相关者传递架构的设计,包括技术人员和非技术人员,同样,也可以快速传达设计的变更,快速地收集反馈,提高决策效率,设计的有效沟通、决策以及对设计的持续变更对于良好的架构至关重要。图2.5所示为一个简单的UML类图。
3.合理的分离
组件化是每个系统架构的追求,而组件化的前提是分离,将系统组件划分为特定的功能,以便组件功能之间不会重叠,这也将提供高内聚力和低耦合度,避免了系统组件之间的相互依赖性,有助于系统维护。
例如,在领域驱动设计中,我们通过事件风暴等方法将系统功能划分不同的领域模型,又将不同的领域模型分组为不同的限界上下文,这将有助于用户在较高层次上理解系统的结构,避免在同一层中混合不同类型的组件。有关领域驱动设计的具体概念和使用方法将在后面的章节中进行详细介绍。
4. 多用组合而不是继承
这可能是比较有争议的一点,继承是面向对象中的概念,可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。子类也可以重新定义某些属性或重写父类原有的方法,使其获得与父类不同的功能。另外,子类还可以追加新的属性和方法。
继承一般为了重用或者约束相同的行为,可以规范同一类型对象的操作,也可以有效地减少重复代码,是部分面向对象开发中最常用的方式。但继承会在子类和父类之间创建依赖关系,因此会阻止子类的自由使用。这在实际应用中有时会带来很多困扰,例如,有一个父类的存储模型的主键ID的类型是整数类型,那么所有继承它的子类的ID都要是整数类型。因此,在现代的面向对象程序设计模式中提到了“多用组合,少用继承”的原则。也就是说,在程序设计时多用组合的方式替代继承。首先,将功能组件化之后,如果有需要重复或扩展的地方,可以多采用将组件进行组合的方式,该组合提供了很大的自由度并减少了继承层次结构。
5. 减少重复的功能
原则上不应该重复组件的功能,因此一段代码应该仅在一个组件中实现。在单应用模式的应用程序中,功能的重复可能难以实现更改,这会降低架构的清晰度并引入一些潜在的不一致的风险性。
设想一下,当一个修改商品的功能出现在两个组件中时,一旦后续需求有所变更,开发者很可能已经不是当时的开发者,他可能只找到了一处进行修改,那么系统中就会出现一种功能两种实现的情况,如果不注意重构,随着系统中重复的组件越来越多,系统很快会出现无人能看懂、无法维护的状况。系统的每个模块都应该有一个特定的职责,这有助于用户、开发者、测试人员、运维人员等了解系统。此外,它还应该有助于将组件与其他组件集成。例如,与安全性、通信或系统服务(如日志记录、性能分析和配置)相关的代码应在单独的组件中进行抽象,不要将这些代码与业务逻辑混合,因为它很容易扩展设计和维护。
6. 合理异常处理和日志管理
事先定义异常处理机制,有助于组件以优雅的方式管理系统错误。合理有效的日志管理,包括设计日志的记录、分类、收集、展示等方面,将大大提升系统的运维效率。其中,最常见的就是当系统出现问题时,有效的日志输出可以帮助运维人员更快速地定位问题的原因。
7. 命名约定统一
强调统一而非命名,注重约定而非规范文档。这一点是笔者最近才学习到的,并且觉得是十分重要的。关于命名,各个公司应该都有很多详细的规范,如词性规范、缩写规范、字数限制、大小写规范等,但更重要的是对相同事物描述的统一,名称实际上就是一种事或物的代表。也就是说,在一个系统中,对某个模块的命名可以是A,也可以是B,那么在一个项目团队中,只要提到这个模块,所有人都知道它在系统中是A,比它可能在意思上更适合命名为B更重要。
笔者曾帮助某大型软件公司开发一个集成它现有的所有内部系统和管理流程的项目管理平台,其中就包括需要集成Jira和该公司内部的组织结构管理系统。由于该公司的规模比较庞大,Jira现有的管理层级设计不能很好地满足他们的业务需求,因此把Jira的项目当作自己的部门,把Jira中的史诗问题当作项目,这样的方式在集成时会产生很多沟通的成本,当我们说项目时,不知道是表示Jira中的项目还是真正意义上的项目。
在做系统设计时,和自己人沟通起来也很费劲,更别提和客户沟通,后来在不断地沟通中去约定统一的命名,如VP项目就是真正意义上的项目,也就是Jira的史诗问题(Epic/ssue),说Jira项目或部门,才是部门。后来无论是沟通还是模型设计都变得顺畅起来。当然,如果命名既能统一又能够更贴切、更合理肯定更好,这里并不是说名称本身不重要,只是强调统一更重要,而且命名约定应事先定义,约定应该有文档记录,但约定本身比文档更重要。命名约定能够提供一致的模型,可以帮助用户轻松地了解系统,帮助团队成员更容易地验证其他人编写的代码,从而提高系统的可维护性。
觉得文章不错的朋友可以点赞+转发此文关注小编,有需要的小伙伴可以扫下方二维码获取~
感谢大家的支持!
以上是关于微服务架构:成为架构师的第一步,就是先要搞清楚什么是架构设计的主要内容,如果未能解决你的问题,请参考以下文章
干货!成为架构师的第一步,2.3w长文带你彻底读懂常见的服务器架构!从单体架构EAI到SOA再到微服务和ServiceMesh