分布式架构原理与实现---第一篇
(一)架构特征:
分布式架构将资源、服务、任务、计算分布到不同的容器、服务器、网络节点中,它们需要协同完成一个或者多个任务。其特征如下:
● 分布性:将分布两字分开来看,“分”指的是拆分,可以理解为服务的拆分、存储数据的拆分、硬件资源的拆分。有时候我们虽然在软件架构上进行了服务的水平扩展,但是这些分布式的服务却在一个宿主机上,并没有分散到多个机器节点,没有做到严格意义上的硬件资源的拆分。布”指的是部署,也指资源的部署。既有计算资源,也有存储资源的部署。
● 自治性:简单来说,自治性就是每个应用服务都有管理和支配自身任务和资源的能力。从分布性的特征来看,资源分散了,以前一份资源做的事情,现在由多份资源同时完成,提高了系统的性能和可用性。
● 并行性:自治性导致每个应用服务都是一个独立的个体,拥有独立的技术和业务,占用独立的物理资源。这种独立能够减小服务之间的耦合度,增强架构的可伸缩性,为并行性打下基础。将应用服务进行扩展后,他们完成的功能相同,处理的业务相同,占用的资源也相同,它们并行处理大量请求,相当于将一个大任务拆解成了若干个小任务,分配到不同的服务器上完成,因此并行性也会被称为并发性。其目的还是提高性能和可用性。
● 全局性。分布性使得服务和资源都是分开部署的,自治性说明单个服务拥有单独的业务和资源,多个服务通过并行的方式完成大型任务。多个分布在不同网络节点的服务应用在共同完成一个任务时,需要有全局性的考虑。例如,商品服务在调用支付服务时,需要通过服务注册中心感知支付服务的存在;多个库存服务对商品库存进行扣减时,需要考虑临界资源的问题;订单服务在调用支付服务和库存服务的时候需要考虑分布式事务问题;当主数据库服务器挂掉的时候,需要及时切换到从数据库服务器,这些都是全局性的问题。说白了,就是分散的资源要想共同完成一件大事,需要沟通和协作,也就是拥有大局观。
待解决问题:
任何一个系统都是为业务服务的,所以首先根据业务特点对应用服务进行拆分,拆分之后会形成一个个服务或者应用。这些服务具有自治性,可以完成自己对应的业务功能,以及拥有单独的资源。当一个服务需要调用其他服务时,需要考虑服务之间通信的问题。同理,多个服务要完成同一件事时,需要考虑协同问题。当遇到大量任务需要进行大量计算工作的时候,需要多个同样的服务共同完成。任何应用或者计算架构都需要考虑存储的问题。实现了对应用与资源的管理和调度,才能实现系统的高性能和可用性。此外,加入指标与监控能够保证系统正常运行。分布式架构需要解决的问题按照顺序列举为如下几步:
(1) 分布式是用分散的服务和资源代替几种服务和资源,所以先根据业务进行应用服务拆分。
(2) 由于服务分布在不同的服务器和网络节点上,所以要解决分布式调用的问题。
(3) 服务能够互相感知和调用以后,需要共同完成一些任务,这些任务或者共同进行,或者依次进行,因此需要解决分布式协同问题。
(4) 在协同工作时,会遇到大规模计算的情况,需要考虑使用多种分布式计算的算法来应对。
(5) 任何服务的成果都需要保存下来,这就要考虑存储问题。和服务一样,存储的分布式也可以提高存储的性能和可用性,因此需要考虑分布式存储的问题。
(6) 所有的服务与存储都可以看作资源,因此需要考虑分布式资源管理和调度。
(7) 设计分布式架构的目的是实现高性能和可用性。为了达到这个目的,一起来看看高性能与可用性的最佳实践,例如缓存的应用、请求限流、服务降级等。
(8) 最后,系统上线以后需要对性能指标进行有效的监控才能保证系统稳定运行,此时指标与监控就是我们需要关注的问题。
(二)服务拆分方法
通过领域驱动设计的定义可以知道,应用服务的拆分源头是需求。企业开发某款软件时,会有一个目的,针对目的会生成需要解决的问题,这些问题就是业务需求。领域驱动设计就是将业务需求转化为架构设计,最后落地到代码。明确了目的,后面的参与者需要对业务比较精通,对系统需要解决的问题和需求有着透彻理解的领域专家,以及在类、接口、方法、设计模式、架构等很熟悉,能够用面向对象的思想来思考问题的技术团队一起参与。
之后双方从企业目标、需解决的问题、业务需求出发,通过通用的语言进行沟通与分析,得到领域知识。这些领域知识是对业务的一般性描述,例如用户通过浏览网站,选择商品下单,付款以后收到确认付款和准备发货的通知,其中参与者(实体)是用户,业务流程是浏览、下单、付款、收到付款通知,命令有下单、付款,事件有已经下单、已经付款、已经发送通知。得到这些领域知识后,通过抽取的方式形成领域模型。
领域模型是一个抽象的概念,其具体形态是一个大的领域,其中包裹着的各种不同的子领域也称为子域。这些子域通过限界上下文的方式进行分割,子域中间又包含领域对象,例如聚合、聚合根、实体、值对象;领域对象之间通过领域事件进行沟通。在抽取完领域模型之后,技术团队会根据这个模型搭建软件架构,并对架构分层,分别是用户接口层、应用层、领域层和基础层,再将每层用代码实现。将上面这些思想整合到一张图如下:
从上图中可以看出,领域驱动设计对应用服务进行拆分的流程大致分为:分析、抽取、构建。
领域、子域和限界上下文:
领域从字面上理解就是从事某种专项活动或事情的范围。这个“范围”可以是业务的范围、系统的范围、服务的范围,甚至是物理资源的范围。只有确定好这些范围,我们才能更好地对分布式架构进行拆分,做到“高内聚,低耦合”。回到业务分析上来,领域指的是业务边界。我们要做的事情就是对业务沿着业务边界进行划分,形成一个个领域模型,再用架构和代码实现这些领域模型,也就是解决从业务到技术的问题。
按照业务边界将业务领域分割以后,形成了一个个子域,这些子域对内都有通用语言作为支撑。如果说领域与子域的概念是从业务角度出发告诉我们如何对业务定义边界,那么该如何划分这个边界,又如何将业务边界定义到技术上呢?答案是限界上下文。
领域中包含的子域就是用限界上下文的方法划分出来的。限界上下文就像一把刀,将业务领域分割成不同的子域。这里需要注意,领域是给领域专家和技术团队看的,因此一定要包含业务和技术两个层面的东西。如果对领域从横切面切一刀,就可以将其分为问题空间和解决方案空间。
● 问题空间是领域在业务层面的表现,从业务的角度会看到分割所得的子域,包括核心域、支撑域、通用域。
● 解决方案空间是领域在技术层面的表现,这里领域被限界上下文分割。
限界上下文是分布式架构和微服务架构拆分的依据,是业务从问题空间转换到解决方案空间的工具。
(三) 领域驱动设计分层
领域驱动设计分层能够帮助我们把领域对象转化为软件架构。在分解复杂的软件系统时,分层是最常用的一种手段。在领域驱动设计的思想中,分层代表软件框架,是整个分布式架构的“骨架”;领域对象是业务在软件中的映射,好比“血肉”。如何将血肉填充到骨架中去,就是领域驱动设计中的分层架构的意义。
分层的原则:
● 高内聚:定义每层需要关注的重点,使复杂问题简单化,让整个架构清晰化。例如商店只负责卖好商品就行了,不需要考虑商品都是如何制作的。同样,基础设施层做好提供日志、通知服务的工作就好了,不用关注具体的业务流程是怎样的。
● 低耦合:各层分工明确,层与层之间通过标准接口进行通信。一个层次不需要关心其他层次的具体实现过程,即便其他层次的内部结构或者流程发生改变了,只要接口不变化就不会影响自己的工作。
● 可扩展:由于每层都各司其职,层与层之间的沟通都通过接口完成,因此无论在哪层扩展功能,都是很方便的,只需要对其他层的功能进行组合即可。例如商店之前只卖面包,现在想扩展业务——卖蛋糕,于是就去联系蛋糕厂。商店只需要知道如何经营和组合好这些商品就可以了。
● 可复用:每层都可以向一层或者多层提供服务,特别是基础、通用功能会被多处使用。这样的复用提高了应用服务的使用率,避免了重复造轮子的现象。
领域驱动设计将架构分成四层,从上往下分别是用户接口层、应用层、领域层和基础层。箭头表示层和层之间的依赖与被依赖关系。例如,箭头从用户接口层指向应用层,表示用户接口层依赖于应用层。从图中可以看到,基础层被其他所有层依赖,位于最核心的位置。
但这种分法和业务领导技术的理念是相冲突的,搭建分布式架构时是先理解业务,然后对业务进行拆解,最后将业务映射到软件架构。这么看来,领域层才是架构的核心,上图的依赖关系是有问题的。于是出现了 DIP(Dependency Inversion Principle,依赖倒置原则),DIP 的思想指出:高层模块不应该依赖于底层模块,这两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。【简单来说就是高层不直接实现业务细节,而是调用抽象接口完成业务目标,至于抽象接口如何实现,则是由底层去实现这个接口】因此,作为底层的基础层应该依赖于用户接口层、应用层和领域层提供的接口。高层是根据业务展开的,通过对业务抽象产生了接口,底层依赖这些接口为高层提供服务。
分层代码结构图:
最右边的其他服务通过基础层中的 API 网关,将信息传入用户接口层。传入的信息先通过 Assembler 转换成 DTO 对象,再传给 Facade。Facade 负责把信息传递给应用层,信息以命令的形式被传递给 Application Service。Application Service 会组合领域层中的 Aggregate 和 Service。领域层中的Entity 和值对象,配合 Aggregate 和 Service 完成业务逻辑,并且通过Repository 将 Entity 和值对象存储到数据库中。领域层中的 Event 会根据业务的发生,获取事件信息,通过应用层中 Event 里的订阅和发布,与其他服务进行通信。