分布式架构原理与实践+解构领域驱动设计

精华总结

既然技术的实现来源于业务,那么对业务的分析就需要放在第一位。我们可以利用DDD(Domain-Driven Design,领域驱动设计)的方法定义领域模型,确定业务和应用服务的边界,最终引导技术的实现。按照 DDD 方法设计出的应用服务符合“高内聚、低耦合”的标准。
DDD 并不是架构,而是一种架构设计的方法论,它通过边界划分将业务转化成领域模型,领域模型又形成应用服务的边界,协助架构落地。

领域模型是一个抽象的概念,其具体形态是一个大的领域,其中包裹着的各种不同的子领域也称为子域。这些子域通过限界上下文的方式进行分割,子域中间又包含领域对象,例如聚合、聚合根、实体、值对象;领域对象之间通过领域事件进行沟通。在抽取完领域模型之后,技术团队会根据这个模型搭建软件架构,并对架构分层,分别是用户接口层、应用层、领域层和基础层,再将每层用代码实现。

领域指的是业务边界。我们要做的事情就是对业务沿着业务边界进行划分,形成一个个领域模型,再用架构和代码实现这些领域模型,也就是解决从业务到技术的问题。

被包含在大业务边界(领域)中的小业务边界称为子领域,也叫作子域。如果把电商平台看成一个领域,那么其中会包含商品系统、订单系统、支付系统和库存系统,这些系统统称电商平台的子域。

限界上下文指的是通过限制边界的方法来确定业务的上下文,这是一种划分业务边界的方法。
领域是给领域专家和技术团队看的,因此一定要包含业务和技术两个层面的东西。如果对领域从横切面切一刀,就可以将其分为问题空间和解决方案空间。

问题空间是领域在业务层面的表现,从业务的角度会看到分割所得的子域,包括核心域、支撑域、通用域。
解决方案空间是领域在技术层面的表现,这里领域被限界上下文分割。

从理论上讲,业务和技术需要分别对应,在理想情况下子域和限界上下文应该是一一对应的,但也有互相融合的情况。

这也是领域驱动设计能让领域人员和技术团队合作的原因。领域模型针对不同的群体提供了不同的空间,又通过通用语言和限界上下文的方法,将不同空间对应起来。领域专家工作在问题空间,技术团队工作在解决方案空间,限界上下文是分割的工具,两个空间使用通用语言进行沟通,大家站在同一层面上交流。

分布式技术架构需要盯着限界上下文来拆分应用或者服务,限界上下文的边界就是应用服务的边界。另外,针对限界上下文还做进一步拆分,生成更加细致的限界上下文。因此,限界上下文是分布式架构和微服务架构拆分的依据,是业务从问题空间转换到解决方案空间的工具。

领域是指架构需要实现的业务范围,在这个范围中,借助通用语言和限界上下文工具,将领域分成一个个子域。子域是业务角度的称呼,从技术角度讲的话,领域就是被分成了一个个限界上下文。限界上下文中包含实体和值对象,它们都属于领域对象,而且实体可以引用值对象。

领域就是我们需要关注并且实施的业务范围,它还可以分为子域,子域是业务角度的理解。使用通用语言可以将领域分为限界上下文,它与子域相对应,是技术角度的理解,限界上下文中对业务有唯一的语义标识。我们可以根据限界上下文定义系统的物理边界,也就是应用或者服务。限界上下文中包含多个领域对象,领域对象包括实体和值对象,它们是对业务的真实反映,具有唯一性和可变性等特性。为了更好地协同和管理领域对象,用聚合的概念将它们组织起来。聚合是一个逻辑上的概念,它是一个领域对象的组织,聚合根是这个组织的管理者,或者说是对外接口。聚合根本身也是一个实体,它让聚合之间产生联系。最后,聚合之间的协作和通信需要通过领域事件完成,它让每个聚合不仅可以专注在自身的业务操作中,还可以和其他聚合共同完成工作。

1)领域划分:面对客户的业务需求,由领域专家与开发团队展开充分的交流,经过需求分析与知识提炼,获得清晰明确的问题空间,并从问题空间的业务需求中提炼出统一语言,然后利用子领域分解问题空间,根据价值高低确定核心子领域、通用子领域和支撑子领域。
(2)限界上下文和上下文映射:通过对问题空间开展战略层次的求解,获得限界上下文形成解空间的主要支撑元素。识别限界上下文的基础来自问题空间的业务需求,遵循“高内聚松耦合”的原则划分领域知识的边界,再通过上下文映射管理它们之间的关系。
(3)分层架构:每个限界上下文都是一个相对独立的“自治王国”,可以根据限界上下文是否属于核心子领域来选择内部的架构。通常,需要通过分层架构将限界上下文内部的领域隔离出来,进入战术设计阶段,进行面向领域的模型驱动设计。
(4)领域模型驱动设计:首先进行领域分析,提炼领域知识建立满足统一语言要求的领域分析模型,然后引入实体、值对象、领域服务、领域事件、聚合、资源库和工厂等设计要素开始程序设计,获得设计模型后在它的指导下进行编码实现,输出最终的领域模型。

业务层面
(1)价值需求分析:从价值需求开始,识别目标系统的利益相关者,明确系统愿景,确定系统范围。
(2)领域划分:将那些对准系统愿景的业务需求放到核心子领域,将提供支撑作用的业务需求放到支撑子领域,将提供公共功能的业务需求放到通用子领域,由此就可以从价值角度完成对问题空间的分解。
对应的技术层面
(1)通过系统上下文呈现利益相关者、目标系统与伴生系统之间的关系。系统上下文实际上确定了解空间的边界,除了系统边界与外部环境之间必要的集成,整个开发团队都工作在系统上下文的边界之内。
(2)根据语义相关性和功能相关性对业务服务表达出来的业务知识进行归类与归纳,即可识别出边界相对合理的限界上下文(限界上下文和子领域之前有一定的联系,但不是一一对应)。限界上下文之间则通过不同的上下文映射模式表达上游和下游之间的协作方式,规范服务契约。
(3)分层架构:将属于核心子领域的限界上下文映射为业务价值层,将支撑子领域和通用子领域的限界上下文映射为基础层,并从前端用户体验的角度考虑引入边缘层,为前端提供一个统一的网关入口,并通过聚合服务的方式响应前端发来的客户端请求。
(4)领域建模,即建立领域模型:包括两方面
a.对象——实体、值对象、聚合、聚合根
b.流程——流程图、序列图等

2.如何拆分得到限界上下文(也即子领域)?
通过对业务主体(实体)、业务服务(动作、事件)进行归纳,可以得到业务服务图来代表业务主体的限界上下文。这里主要靠业务专家和产品分析师从业务角度进行拆分。

3.分层架构-限界上下文之间
我们需要改变分层架构的技术视角,从价值的角度将所有的限界上下文分为两个层次。
业务价值层(value-added layer):映射核心子领域。
基础层(foundation layer):映射通用子领域和支撑子领域。

4.棱形对称架构-限界上下文内部
北向网关:远程服务层+本地服务层
领域层:聚合+领域服务
南向网关:客户端+客户端适配器

5.一共四层边界,由外向内,
-业务和技术关联
领域-系统上下文
子领域-限界上下文
上下文映射-分层结构,包含接入层、业务服务层、基础服务层
-技术
菱形对称架构-限界上下文内部分为 网关层、领域层
领域模型-聚合,即领域层进一步划分为不同的聚合

6.架构设计
(1)业务架构
结合业务愿景与业务范围,描绘出业务组件——核心子领域、支撑子领域与通用子领域之间的关系。
a、业务组件——子领域
从业务相关性识别限界上下文,并将其作为组成业务架构的业务组件。
b、业务架构视图
确定业务组件与子领域之间的关系,从业务角度绘制整个目标系统的业务架构。可以展现前端、业务组件(限界上下文)与伴生系统之间的调用关系。
c.网上说法,业务架构包含
①业务策略:定义组织的方向和目标,即愿景?
②业务流程:描述组织完成其目标所需的活动和任务的顺序,即各个业务组件的调用关系——上下文映射?
③业务功能:描述组织为了完成其目标所需的各种能力和服务,即每个业务组件要包含的能力——业务服务?
④业务数据:描述组织在运营过程中使用的数据和信息,即——领域模型,聚合?
(2)应用架构
a、应用组件——限界上下文、微服务
将业务架构的业务组件映射为应用架构的应用组件。应用组件的粒度对应于限界上下文,但需要从团队维度和技术维度进一步梳理限界上下文的边界,
同时根据质量属性的要求确定进程边界。应用组件以库或服务的形式呈现。
b、应用架构视图——限界上下文的分层架构,也就是说业务架构的子领域不应该分层,应用架构的限界上下文应该分层
在业务架构视图的指导下,通过系统分层架构体现应用架构视图。其中,系统分层架构的业务价值层与基础层由具有限界上下文特征的应用组件组成。

分布式架构原理与实践

1.分布式架构设计的特征与问题

为了应对请求的高并发和业务的复杂性,需要对应用服务进行合理拆分,将其从原来的大而集中变成小而分散;要想让这些分散的服务共同完成计算任务,就需要解决它们之间的通信与协同问题;和服务一样,负责存储的数据库也会有分散的情况,因此需要考虑分散存储;如果说所有的服务、数据库都需要硬件资源作为支撑,那么对资源的管理和调度也是必不可少的;此外,软件系统上线以后,还需要对关键指标进行监控。

1.1架构设计的演进过程

1.1.1应用与数据一体模式
1.1.2应用与数据分离模式
1.1.3缓存与性能的提升

这里提到的缓存技术分为客户端浏览器缓存、应用服务器本地缓存和缓存服务器缓存。
缓存技术的加入

1.1.4服务器集群处理并发

服务器集群说白了,就是多台服务器扎堆的意思,用更多服务器来分担单台服务器的负载压力,提高性能和可用性。再说白一点,就是提高单位时间内服务处理请求的数量。原来是一台服务器处理多个用户的请求,现在是一堆服务器处理,就好像银行柜台一样,通过增加柜员的人数来服务更多的客户.
此时需要注意负载均衡器采用的均衡算法(例如轮询和加权轮询)要能保证用户请求均匀地分布到多台服务器上、属于同一个会话的所有请求在同一个服务器上处理,以及针对不同服务器资源的优劣能够动态调整流量。
服务器集群的加入

1.1.5数据库读写分离

加入缓存可以解决部分热点数据的读取问题,但缓存的容量毕竟有限,那些非热点的数据依然要从数据库中读取。
数据库对于写入和读取操作的性能是不一样的。在写入数据时,会造成锁行或者锁表,此时如果有其他写入操作并发执行,就会出现排队现象。而读取操作不仅比写入操作更加快捷,并且可以通过索引、数据库缓存等方式实现。

数据库读写分离
数据库读写分离的方式将数据库的读、写职责分离开来,利用读数据效率较高的优势,扩展更多的从库,从而服务于请求读取操作的用户。毕竟在现实场景中,大多数操作是读取操作。

1.1.6反向代理和 CDN

之前用户都是通过客户端直接访问应用服务器获取服务,这使得应用服务器暴露在互联网中,容易遭到攻击。如果在应用服务器与互联网之间加上一个反向代理服务器,由此服务器来接收用户的请求,然后再将请求转发到内网的应用服务器,相当于充当外网与内网之间的缓冲,就可以解决之前的问题。反向代理服务器只对请求进行转发,自身不会运行任何应用,因此当有人攻击它的时候,是不会影响到内网的应用服务器的,这在无形中保护了应用服务器,提高了安全性。同时,反向代理服务器也在互联网与内网之间起适配和网速转换的作用。例如,应用服务器需要服务于公网和教育网,但是这两个网络的网速不同,那么就可以在应用服务器与互联网之间放两台反向代理服务器,一台连接公网,另一台连接教育网,用于屏蔽网络差异,服务于更多的用户群体。

加入反向代理服务器

CDN,它的全称是 Content Delivery Network,也就是内容分发网络。如果把互联网想象成一张大网,那么每台服务器或者每个客户端就是分布在这张大网中的节点。节点之间的距离有远有近,用户请求会从一个节点跳转到另外一个节点,最终跳转到应用服务器获取信息。跳转的次数越少,越能够快速地获取信息,因此可以在离客户端近的节点中存放信息。这样用户通过客户端,只需要跳转较少的次数就能够触达信息。由于这部分信息更新频率不高,因此推荐存放一些静态数据,例如 JavaScript 文件、静态的 HTML、图片文件等。这样客户端就可以从离自己最近的网络节点获取资源,大大提升了用户体验和传输效率。

加入 CDN
从请求资源的角度来看,这种方式也有局限性,即它只对静态资源起作用,而且需要定时对 CDN 服务器进行资源更新。反向代理和 CDN 的加入解决了安全性、可用性和高性能的问题。

1.1.7分布式数据库与分表分库

可是随着系统运行时间的增加,数据库中累积的数据越来越多,同时系统还会记录一些过程数据,例如操作数据和日志数据,这些数据也会加重数据库的负担。即便数据库设置了索引和缓存,但在进行海量数据查询时还是会表现得捉襟见肘。如果说读写分离是对数据库资源从读写层面进行分配,那么分布式数据库就需要从业务和数据层面对数据库进行分配。

  • 对于数据表来说,当表中包含的记录过多时,可将其分成多张表来存储。除了可以按照行来分割,也可以按照业务对表中的列进行分割,把表中的某些列放到其他表中存储,然后通过外键关联到主表。注意被分割出去的列通常是不经常访问的数据。
  • 对于数据库来说,每个数据库能够承受的最大连接数和连接池是有上限的。为了提高数据访问效率,会根据业务需求对数据库进行分割,让不同的业务访问不同的数据库。当然,也可以将相同业务的不同数据放到不同的数据库中存储。

分布式数据库与分表分库

因为数据的分散部署,所以从业务应用获取数据时需要依靠数据库中间件的帮忙。例如 MyCat 和Sharding JDBC。

1.1.8业务拆分

如果说前面的服务器集群模式是将同一个应用复制到不同的服务器上,那么业务拆分就是将一个应用拆成多个部署到不同的服务器中。
应用虽然做了拆分,但应用之间仍旧有关联,存在相互之间的调用、通信和协调问题。由此引入了队列、服务注册发现、消息中心等中间件,这些中间件可以协助系统管理分布到不同服务器、网络节点上的应用。
业务拆分
业务拆分以后会形成一个个应用服务,既有基于业务的服务,例如商品服务、订单服务,也有基础服务,例如消息推送和权限验证。这些应用服务连同数据库服务器分布在不同的容器、服务器、网络节点中,它们之间的通信、协调、管理和监控都是我们需要解决的问题。

1.1.9分布式与微服务

近几年,微服务是一种比较火的架构方式,它对业务应用进行了更加精细化的切割,使之成为更小的业务模块,能够做到模块间的高内聚低耦合,每个模块都可以独立存在,并由独立的团队维护。每个模块内部可以采取特有的技术,而不用关心其他模块的技术实现。模块通过容器的部署运行,各模块之间通过接口和协议实现调用。可以将任何一个模块设为公开,以供其他模块调用,也可以热点模块进行水平扩展,增强系统的整体性能,这样当其中某一个模块出现问题时,就能由其他相同的模块代替其工作,增强了可用性。大致总结下来,微服务拥有以下特点:业务精细化拆分、自治性、技术异构性、高性能、高可用。

微服务架构和分布式架构的区别:

  • 拆分目的不同:提出分布式设计是为了解决单体应用资源有限的问题,一台服务器无法支撑更多的用户访问,因此将一个应用拆解成不同的部分,然后分别部署到不同服务器上,从而分担高并发的压力。微服务是对服务组件进行精细化,目的是更好地解耦,让服务之间通过组合实现高性能、高可用、可伸缩、可扩展。
  • 拆分方式不同:分布式服务架构将系统按照业务和技术分类进行拆分,目的是让拆分后的服务负载原来单一服务的业务。微服务则是在分布式的基础上进行更细的拆分,它将服务拆成更小的模块,不仅更专业化,分工也更为精细,并且每个小模块都能独立运行。
  • 部署方式不同:分布式架构将服务拆分以后,通常会把拆分后的各部分部署到不同服务器上。而微服务既可以将不同的服务模块部署到不同服务器上,也可以在一台服务器上部署多个微服务或者同一个微服务的多个备份,并且多使用容器的方式部署。
    在这里插入图片描述
    可以说微服务是分布式的进化版本,也是分布式的子集,因此它同样会遇到服务拆分、服务通信、协同、管理调度等问题。

1.2一个简单的例子:分布式架构的组成

软件架构是沿着高性能、高可用、可扩展、可伸缩、够安全的方向发展的,其中最重要的是高性能和高可用。
订单业务架构图

1.2.1架构概述与分层

为了完成上面的订单业务流程,将分布式系统分为了四层。

  • 客户端:这是用户与系统之间的接口。了提升用户体验,会利用 HTTP 缓存手段将部分静态资源缓存下来,同时也可以将这部分静态资源缓存到 CDN 中,因为 CDN 服务器通常会让用户从比较近的网络节点获取静态数据。
  • 负载均衡器(以下称接入层):负载均衡器可以通过用户 IP 将用户的请求路由到不同的服务器集群。另外,在负载均衡这一层,还可以进行流量控制和身份验证等操作。
  • 应用服务器(以下称应用层):当负载均衡器将请求路由到应用服务器或者内网以后,会去找对应的服务,例如商品服务。当请求从外网进入内网后,需要通过 API 网关进行再次路由,特别是微服务架构中服务拆分得比较细,就更需要 API 网关了。API 网关还可以起到内网的负载均衡、协议转换、链式处理、异步请求等作用。由于应用服务(一个或者多个)部署在多个服务器上,这些服务器要想互相调用,就要考虑各服务间的通信、协调等问题,因此加入了服务注册中心、消息队列消息中心等组件。同时由于商品服务会经常被用户调用,因此加入了缓存机制。
  • 数据服务器(以下称存储层):这里使用了数据分片,并加入了主、备数据库的设计,这两个数据库服务器会进行同步,当主数据库服务器挂掉的时候,备数据库服务器就会接管它的一切。
1.2.2接入层

用户浏览商品的请求一般是一个 URL,这个 URL 被 DNS 服务器解析成服务器的IP 地址,然后用户的请求通过这个 IP 地址访问应用服务器上的服务,这是早期的做法。让应用服务器暴露在互联网上是非常危险的,为了解决这个问题,在应用服务器和客户端之间加入了反向代理服务器,它负责对外暴露服务的入口地址,保护内网的服务。
负载均衡
客户端请求服务器的过程可以分为如下四个步骤:
(1) 客户端向 DNS 服务器发出 URL 请求;
(2) DNS 服务器向客户端返回应用服务器入口的 IP 地址;
(3) 客户端向服务器发送请求;
(4) 负载均衡器接收请求以后,根据负载均衡算法找到对应的服务器,并将请求发送给此服务器。

1.2.3应用层
01.api网关

如果把请求看成水流,API 网关就像一个控制水流的水阀。它控制水的流向、大小,调整不同蓄水池存储水流的方式,记录水流的信息。API 网关和负载均衡器在原理上是相同的,区别在于前者更多是在服务器内部服务之间实现,而后者是在互联网与服务器之间实现。
在这里插入图片描述

02.服务协同与通信

订单服务和支付服务事先会在服务注册中心注册自己。订单服务在调用支付服务之前,会先去注册中心获取所有可用服务的调用列表,然后根据列表上的地址对支付服务进行调用。此时订单服务会对支付服务发起一个 RPC 调用,把需要传递的订单信息以序列化的方式打包,并经过网络协议传递给支付服务,支付服务在接收到信息以后,通过反序列化工具解析传递的内容,然后执行接下来的付款操作。
在这里插入图片描述
在这里插入图片描述

03.分布式互斥

多线程访问临界资源
通过zookeeper进行分布式资源访问
(1) 当库存服务 A 访问库存的时候,需要先申请锁,于是在 ZooKeeper 的 Locker 节点下面新建一个 DataNode1 节点,表明它可以扣减库存。
(2) 库存服务 B 在库存服务 A 后面申请库存的访问权限,由于其申请锁操作排在库存服务 A 后面,因此其按照次序建立的节点会排在 DataNode1 下面,名字为DataNode2。
(3) 库存服务 A 在申请锁成功以后访问库存资源,并进行库存扣减。在此期间库存服务 B 一直处于等待状态,直到库存服务 A 完成扣减操作以后,ZooKeeper 中Locker 下面的 DataNode1 节点被删除,库存资源被释放。
(4) DataNode1 被删除以后,DataNode2 成为序号最靠前的节点,库存服务 B 因此得到了对库存的访问权限,并且可以完成库存扣减操作
多个库存服务访问库存资源

04.分布式事务

2PC、ACID、CAP、TCC 等处理分布式事务的方法

1.2.4存储层
01.读写分离与主从同步

读写分离与主从复制
配置主节点 writeHost 来负责写入操作,配置从节点readHost 来负责读取操作。图中处于上方的 MyCat 数据库中间件会通过ZooKeeper 定期向两个节点服务器发起心跳检测(虚线部分)。图 1-23 中实线部分描述的信息有:MyCat 开启读写分离模式之后,中间件接收到商品读/写的请求时,会通过 SQL 解析,将写入请求的 DML(Data Manipulation Language)SQL 发送到 writeHost 服务器上,将读取请求的 Select SQL 发送到 readHost 服务器上。writeHost 在完成写入信息以后,会和 readHost 进行数据同步,也就是主从复制。由于存在心跳检测机制,当 writeHost 挂掉时,如果在默认N次心跳检测后(N可以配置)仍旧没有恢复,MyCat 就会发起选举,选举出一台服务器成为新的 writeHost。它会接替之前的 writeHost,负责处理写入数据的数据同步。当之前的 writeHost 恢复以后,会成为从节点 readHost 并且接收来自新 writeHost 的数据同步。

1.3分布式架构的特征

  • 分布性。将分布两字分开来看,“分”指的是拆分,可以理解为服务的拆分、存储数据的拆分、硬件资源的拆分。“布”指的是部署,也指资源的部署。简单来说,分布性就是拆开了部署。
  • 自治性(独立性)。以前一份资源做的事情,现在由多份资源同时完成。这样提高了系统的性能和可用性,这也是设计分布式架构的目的。分布性导致了自治性。简单来说,自治性就是每个应用服务都有管理和支配自身任务和资源的能力。对内,它可以采用自己的技术来实现,并不受其他服务的影响,业务上专注于处理订单业务;对外的沟通则使用服务注册中心和消息队列,与其他服务是平等关系。
  • 并行性。这种独立能够减小服务之间的耦合度,增强架构的可伸缩性,为并行性打下基础。通过水平扩展,扩展后的这些服务完成的功能相同,处理的业务相同,占用的资源也相同,它们并行处理大量请求,相当于将一个大任务拆解成了若干个小任务,分配到不同的服务器上完成,因此并行性也会被称为并发性。其目的还是提高性能和可用性
  • 全局性。就是分散的资源要想共同完成一件大事,需要沟通和协作。

1.4分布式架构的问题

1.4.1 分布式架构的逻辑结构图

分布式架构需要解决的问题按照顺序列举为如下几步。
(1) 分布式是用分散的服务和资源代替几种服务和资源,所以先根据业务进行应用服务拆分。
(2) 由于服务分布在不同的服务器和网络节点上,所以要解决分布式调用的问题。
(3) 服务能够互相感知和调用以后,需要共同完成一些任务,这些任务或者共同进行,或者依次进行,因此需要解决分布式协同问题。
(4) 在协同工作时,会遇到大规模计算的情况,需要考虑使用多种分布式计算的算法来应对。
(5) 任何服务的成果都需要保存下来,这就要考虑存储问题。和服务一样,存储的分布式也可以提高存储的性能和可用性,因此需要考虑分布式存储的问题。
(6) 所有的服务与存储都可以看作资源,因此需要考虑分布式资源管理和调度。
(7) 设计分布式架构的目的是实现高性能和可用性。为了达到这个目的,一起来看看高性能与可用性的最佳实践,例如缓存的应用、请求限流、服务降级等。
(8) 最后,系统上线以后需要对性能指标进行有效的监控才能保证系统稳定运行,此时指标与监控就是我们需要关注的问题。

分布式架构需解决问题的结构图

1.4.2应用服务拆分

在分布式架构的实践过程中,产生了一些争论和疑惑,例如应用服务如何划分?划分以后如何设计?业务的边界如何定义?

应用服务如果拆分得过于细致,就会导致系统架构的复杂度增加,项目难以推进,程序员学习曲线变得陡峭;如果拆分得过于粗旷,又无法达到利用分布式资源完成海量请求以及大规模任务的目的。换言之,定义业务的边界是划分应用服务的关键。说白了,就是先划分业务,再针对划分后的业务进行技术实现。

既然技术的实现来源于业务,那么对业务的分析就需要放在第一位。我们可以利用DDD(Domain-Driven Design,领域驱动设计)的方法定义领域模型,确定业务和应用服务的边界,最终引导技术的实现。按照 DDD 方法设计出的应用服务符合“高内聚、低耦合”的标准。
DDD 并不是架构,而是一种架构设计的方法论,它通过边界划分将业务转化成领域模型,领域模型又形成应用服务的边界,协助架构落地。

1.4.3分布式调用

将分布式调用总结为两部分,第一部分是感知对方,包括负载均衡、API 网关、服务注册与发现、消息队列;第二部分是信息传递,包括 RPC、RMI、NIO 通信。

1.4.4 分布式协同

我们将这些问题归纳为以下几点。

  • 分布式系统的特性与互斥问题:集中互斥算法、基于许可的互斥算法、令牌环互斥算法。
  • 分布式锁:分布式锁的由来和定义、缓存实现分布式锁、ZooKeeper 实现分布式锁、分段加锁。
  • 分布式事务:介绍分布式事务的原理和解决方案。包括 CAP、BASE、ACID 等的原理;DTP 模型;2PC、TCC 方案。
  • 分布式选举:介绍分布式选举的几种算法,包括 Bully 算法、Raft 算法、ZAB 算法。
  • 分布式系统的实践:介绍 ZooKeeper 的基本原理和组件。
1.4.5 分布式计算

计算模式分为两种:针对批量静态数据计算的MapReduce 模式,以及针对动态数据流进行计算的 Stream 模式。

  • MapReduce 模式:介绍其特点、工作流程和示例。
  • Stream 模式:通过 Storm 的最佳实践介绍其要素和流程。
1.4.6 分布式存储

从数据类型上来看,数据又分为结构化数据、半结构化数据、非结构化数据。在分布式架构中,会对数据按照规则分片,对于主从数据库还需要完成数据同步操作。如果要建立一个好的数据存储方案,需要关注数据均匀性、数据稳定性、节点异构性以及故障隔离几个方面。因此,我们组织以下内容来讲解分布式存储。

  • 数据存储面临的问题和解决思路:RAID 磁盘阵列。
  • 分布式存储概念:分布式存储的要素和数据类型分类。
  • 分布式关系数据库:分表分库、主从复制、数据扩容。
  • 分布式缓存:缓存分片算法、Redis 集群方案、缓存节点之间的通信、请求分布式缓存的路由、缓存节点的扩展和收缩、缓存故障的发现和恢复。
1.4.7 分布式资源管理与调度

如果把每个用户请求都看成系统需要完成的任务,那么分布式架构要做的就是对任务与资源进行匹配。

  • 中心化调度的特点是由一个网络节点参与资源的管理和调度。
  • 两级调度在单体调度的基础上将资源的管理和调度从一层分成了两层,分别是资源管理层和任务分配层。
  • 共享状态调度,通过共享集群状态、共享资源状态和共享任务状态完成调度工作。
  • 资源调度的实践:介绍 Kubernetes 的架构及其各组件的运行原理。
1.4.8 高性能与可用性

高性能和可用性本身就是分布式架构要达成的目的。分布式架构拆分和分而治之的思想也是围绕着这个目的展开的。这部分主要从缓存、可用性两个方面展开。
对于可用性来说,为了保证系统的正常运行会通过限流、降级、熔断等手段进行干涉。

  • 缓存的应用:HTTP 缓存、CDN 缓存、负载均衡缓存、进程内缓存、分布式缓存。
  • 可用性的策略:请求限流、服务降级、服务熔断。
1.4.9 指标与监控

判断一个架构是好是坏时,有两个参考标准,即性能指标和可用性指标,分布式架构也是如此。性能指标又分为吞吐量、响应时间和完成时间。

  • 性能指标:延迟、流量、错误、饱和度。
  • 分布式监控系统:创建监控系统的步骤、监控系统的分类、监控系统的分层。
  • 流行监控系统的最佳实践:包含 Zabbix、Prometheus。

在这里插入图片描述

2.分布式应用服务的拆分

2.1 领域模型

领域模型是一个抽象的概念,其具体形态是一个大的领域,其中包裹着的各种不同的子领域也称为子域。这些子域通过限界上下文的方式进行分割,子域中间又包含领域对象,例如聚合、聚合根、实体、值对象;领域对象之间通过领域事件进行沟通。在抽取完领域模型之后,技术团队会根据这个模型搭建软件架构,并对架构分层,分别是用户接口层、应用层、领域层和基础层,再将每层用代码实现。
将上面这些思想整合到一张图中,就是图
领域驱动设计的拆分思想和过程

2.2 模型结构

2.2.1 通用语言

通用语言是一种沟通工具,用来描述需要实现的业务场景和领域模型,是领域专家与技术团队沟通的桥梁,它包含自然语言、文档和图表。
它的特点:

  • 通用语言的使用是有范围限制的。这个范围就是限界上下文。在范围一定的情况下,团队所有人对同一事物的称呼都是统一的,这就是通用语言。在这个范围内,领域专家无须使用业务术语,技术团队也无须使用技术术语,大家通过通用语言进行交流,保证了对同一事物的认知具有统一性。
  • 通用语言可以定义实体、命令和事件。由于通用语言描述了业务流程和应用场景,因此可以直接反映在代码中。
2.2.2 领域、子域和限界上下文

领域指的是业务边界。我们要做的事情就是对业务沿着业务边界进行划分,形成一个个领域模型,再用架构和代码实现这些领域模型,也就是解决从业务到技术的问题。

被包含在大业务边界(领域)中的小业务边界称为子领域,也叫作子域。如果把电商平台看成一个领域,那么其中会包含商品系统、订单系统、支付系统和库存系统,这些系统统称电商平台的子域。在每个子域中,描绘同一个事物时使用的通用语言都是唯一的,不存在二义性
在这里插入图片描述
如果说领域与子域的概念是从业务角度出发告诉我们如何对业务定义边界,那么该如何划分这个边界,又如何将业务边界定义到技术上呢?答案是限界上下文。
限界上下文指的是通过限制边界的方法来确定业务的上下文,这是一种划分业务边界的方法。
领域是给领域专家和技术团队看的,因此一定要包含业务和技术两个层面的东西。如果对领域从横切面切一刀,就可以将其分为问题空间和解决方案空间。

  • 问题空间是领域在业务层面的表现,从业务的角度会看到分割所得的子域,包括核心域、支撑域、通用域。
  • 解决方案空间是领域在技术层面的表现,这里领域被限界上下文分割。

从理论上讲,业务和技术需要分别对应,在理想情况下子域和限界上下文应该是一一对应的,但也有互相融合的情况。

在这里插入图片描述
这也是领域驱动设计能让领域人员和技术团队合作的原因。领域模型针对不同的群体提供了不同的空间,又通过通用语言和限界上下文的方法,将不同空间对应起来。领域专家工作在问题空间,技术团队工作在解决方案空间,限界上下文是分割的工具,两个空间使用通用语言进行沟通,大家站在同一层面上交流。

分布式技术架构需要盯着限界上下文来拆分应用或者服务,限界上下文的边界就是应用服务的边界。另外,针对限界上下文还做进一步拆分,生成更加细致的限界上下文。因此,限界上下文是分布式架构和微服务架构拆分的依据,是业务从问题空间转换到解决方案空间的工具。

2.2.3 实体和值类型

限界上下文中包含领域对象,实体就是一种领域对象。

01.实体

实体是实际存在的物体。

  • 唯一性。每个实体在限界上下文中都是唯一存在的,并且可以被唯一标识。
  • 可变性指的是实体状态和行为的可变。在产生行为的同时会产生事件,事件起到了让实体之间相关联的作用。
02.值对象

值对象就是,用对象的方式来描述值。

03.实体和值对象的关系

领域模型中,实体对值对象的引用
领域是指架构需要实现的业务范围,在这个范围中,借助通用语言和限界上下文工具,将领域分成一个个子域。子域是业务角度的称呼,从技术角度讲的话,领域就是被分成了一个个限界上下文。限界上下文中包含实体和值对象,它们都属于领域对象,而且实体可以引用值对象。
领域、子域(限界上下文)、实体和值对象的关系

2.2.4聚合和聚合根

将实体和值对象组合成整体,并进行统一的协调和管理就是聚合要做的事情。聚合针对领域对象(实体、值对象)进行组合以及业务封装,并且保证聚合后内部数据的一致性。如果说限界上下文对应一个服务或者应用,是系统的物理边界,那么聚合就是领域对象处在限界上下文内部的逻辑边界。
聚合是实体与值对象的组合,在限界上下文中形成了逻辑边界
聚合内部的领域对象协同实现业务逻辑,因此需保证数据的一致性。同时聚合也是数据存储的基本单元,一个聚合对应一个仓库,对数据进行存储。如果把聚合比作一个组织,那么这个组织肯定需要一个领导者,领导者负责本组织和其他组织之间的沟通。其中提到的领导者便是聚合根,它本质也是一个实体,具有实体的业务属性、状态和行为。作为聚合的管理者,聚合根负责协调实体和值对象,让它们协同完成工作,保证这个行为在聚合内的事务性和数据一致性

2.2.5 领域事件

当多个聚合需要共同完成一个业务的时候,如何让它们协同工作呢,这就需要用到领域事件。
聚合之间通过领域事件相互沟通、协同工作
聚合在执行命令和操作之后便会产生事件,这个事件会引出下一步的命令和操作,其中提到的事件就被称为领域事件一次事务只能更改一个聚合的状态,如果一次业务操作涉及多个聚合状态的更改,就好像上面的例子中,付款、修改订单、发货三个步骤需要放在同一个事务中处理,就需要通过领域事件保持数据的最终一致性。有了领域事件的加入,每个聚合都能专注于自己的工作,并依赖领域事件和其他聚合进行沟通和协同,保证了聚合内部的事务独立性和数据一致性。领域事件是解耦的工具,在分布式应用拆分和微服务的场景下,会被频繁用到。特别是当服务部署在不同的网络节点时,尤为重要。

领域就是我们需要关注并且实施的业务范围,它还可以分为子域,子域是业务角度的理解。使用通用语言可以将领域分为限界上下文,它与子域相对应,是技术角度的理解,限界上下文中对业务有唯一的语义标识。我们可以根据限界上下文定义系统的物理边界,也就是应用或者服务。限界上下文中包含多个领域对象,领域对象包括实体和值对象,它们是对业务的真实反映,具有唯一性和可变性等特性。为了更好地协同和管理领域对象,用聚合的概念将它们组织起来。聚合是一个逻辑上的概念,它是一个领域对象的组织,聚合根是这个组织的管理者,或者说是对外接口。聚合根本身也是一个实体,它让聚合之间产生联系。最后,聚合之间的协作和通信需要通过领域事件完成,它让每个聚合不仅可以专注在自身的业务操作中,还可以和其他聚合共同完成工作。
域、子域(限界上下文)、聚合、实体、值对象、领域事件结构

2.3 边界的划分(生成限界上下文)

划分限界上下文的思路:
(1)根据业务场景创建业务流程,在流程节点上标注参与者、命令和事件信息。
(2)生成领域对象,包括实体、值对象、聚合、领域事件。通过通用语言,对相关的领域对象进行进一步划分,形成聚合并找到聚合根。
(3)通过聚合划定限界上下文。限界上下文就是服务的边界,根据它来创建服务或者应用。

2.3.1 分析业务流程

通过业务流程图,并且标注参与者、命令和事件信息。
在创建完这个业务流程的基础上,查找每个流程节点对应的命令和事件。
申请选修课场景

2.3.2 抽取领域对象和生成聚合

将领域对象分别抽取出来,然后重点关注实体、领域事件、命令。抽取的目的是观察领域对象之间的关联和共性,最终对它们进行聚合和限界上下文划分。

用不同的形状表示三个场景中的领域对象:圆形表示实体,长方形表示命令,五边形表示事件。
从业务流程中抽取领域对象
根据逻辑上的相关性,对领域对象进行组合,生成聚合。聚合是逻辑上的边界,为限界上下文的划分提供依据。要想生成聚合,首先需要考虑聚合的逻辑独立性,即能否在聚合内部完成一个完整的业务逻辑。

例如,虽然选修课实体在三个场景中都存在,但申请选修课场景和审批选修课场景中的选修课描述的是课程本身,包括课程内容、学分;而选修课签到场景中的选修课更多的是关心上下课的时间、上课的位置等信息。这正是上下文不一致导致的,同一事物在不同上下文中的含义出现了偏差。
从抽取领域对象到生成聚合

2.3.3 划定限界上下文

如果说聚合是服务的逻辑边界,那么限界上下文就是服务的物理边界。选修课申请聚合和签到聚合的含义不太相同。因此这两个聚合需要分开。至于人员组织聚合,可以单独将其划分为一个限界上下文,或者转化为一个通用组件。将三个聚合划分为选修课申请、签到、人员组织三个限界上下文。人员组织可以作为通用域,协助另外两个子域。选修课申请作为单独的限界上下文,承载大部分实体、命令和事件,可以考虑将其称为核心域。签到则可以作为支撑域,用来支撑核心域。

基于聚合划分限界上下文

2.4 领域驱动设计分层

2.4.1 分层的概述与原则
  • 高内聚:定义每层需要关注的重点,使复杂问题简单化,让整个架构清晰化。例如商店只负责卖好商品就行了,不需要考虑商品都是如何制作的。同样,基础设施层做好提供日志、通知服务的工作就好了,不用关注具体的业务流程是怎样的。
  • 低耦合:各层分工明确,层与层之间通过标准接口进行通信。一个层次不需要关心其他层次的具体实现过程,即便其他层次的内部结构或者流程发生改变了,只要接口不变化就不会影响自己的工作。接口能够降低层与层之间的耦合度。
  • 可扩展:由于每层都各司其职,层与层之间的沟通都通过接口完成,因此无论在哪层扩展功能,都是很方便的,只需要对其他层的功能进行组合即可
  • 可复用:每层都可以向一层或者多层提供服务,特别是基础、通用功能会被多处使用

领域驱动设计将架构分成四层,从上往下分别是用户接口层、应用层、领域层和基础层。
领域驱动设计的传统四层架构
但这种分法和业务领导技术的理念是相冲突的,搭建分布式架构时是先理解业务,然后对业务进行拆解,最后将业务映射到软件架构。这么看来ÿ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值