写点什么

极客时间架构 1 期:第 10 周 模块分解 - 学习总结

用户头像
Null
关注
发布于: 2020 年 11 月 30 日

巨无霸应用系统带来的问题

编译、部署困难

对于网站开发工程师而言,打包构建一个巨型应用是一件痛苦的事情。 也许只是修改了一行代码,输入 build命令后,抽完一支烟,回来一看,还在building;又去喝了一杯水,回来一看,还在building;又去了一次厕所,回来一看,还在 building; 好不容易 build 结束,一看编译失败,还得重来... 想砸了显示器有木有?



代码分支管理困难

复用的代码模块由多个团队共同维护修改,代码 merge 的时候总会发生冲突。代码 merge 一 般在网站发布的时候,和发布等问题互相纠结在一起,顾此失彼,每次发布都要半夜三更。



数据库连接耗尽

巨型的应用、大量的访问,必然需要将这个应用部署在一个大规模的服务器集群上,应用与数 据库的连接通常使用数据库连接池,以每个应用10个连接计,一个数百台服务器集群的应用将 需要在数据库上创建数千个连接,数据库服务器上,每个连接都会占用一些昂贵的系统资源, 以至于数据库缺乏足够的系统资源进行一般的数据操作。



新增业务困难:

想要在一个已经如乱麻般系统中增加新业务,维护旧功能,难度可想而知。一脚踩进去,发现全都是雷,什么都不敢碰。许多新工程师来公司半年了,还是不能接手业务,因为不知道水有多深。于是就出现这种怪现象:熟悉网站产品的“老人”忙得要死,加班加点干活;不熟悉网站产品的新人一帮忙就出乱,跟着加班加点;整个公司热火朝天,加班加点,却还是经常出故障,新产品迟迟不能上线。



如何解决

解决方案就是引入微服务结构,对业务进行拆分,将模块独立部署,降低系统耦合性

纵向拆分

将一个大应用拆分为多个小应用,如果新增业务较为独立,那么就直接将其设计部署为一个独立的Web应用系统。

横向拆分

将复用的业务拆分出来,独立部署为微服务,新增业务只需要调用这些微服务即可快速搭建一个应用系统。



微服务架构

微服务架构的本质是解决单体应用系统带来的问题,并能够支持业务需求快速迭代提高业务发展速度,并能够保证业务边界清晰,并能够保证业务的高内聚低耦合。

要发挥微服务架构所带来的好处的前提是,对业务有深刻的认知,能够准确把握对服务粒度的拆分,能够准确定义服务边界,能够准确的分析出服务的依赖关系(这就是微服务架构最核心的东西,这就是架构师的核心职责)

微服务架构的关注点在于,如何做好服务本身的设计、维护、治理



微服务框架需求

服务注册与发现



高效的远程通信

对于大型网站,核心服务每天的调用次数会达到数以亿计,如果没有高效的远程通信手段, 服务调用可能会成为整个系统性能的瓶颈。



负载均衡

对于集群部署的服务提供者,服务请求者可以使用加权轮询等手段访问,使服务提供者集群 实现负载均衡。



失效转移(Fail Over)

对于大型网站的微服务而言,即使是很少访问的简单服务,也需要集群部署,同时微服务框 架还需要支持服务提供者的失效转移机制,以实现服务高可用



对应用最少侵入

网站技术是为业务服务的,是否使用微服务需要根据业务发展规划,微服务也需要渐进式的 演化,甚至会出现反复,即使用了微服务后又退回到集中式部署,微服务框架需要支持这种 渐进式演化和反复。当然服务模块本身需要支持可集中式部署,也可分布式部署。



版本管理

为了应对快速变化的需求,服务版本升级不可避免,如果仅仅是服务实现升级,那么这种升级对服务请求者而言是透明的,无需关注。但是如果服务的访问接口发生变化,就需要服务请求者和服务提供者同时升级才不会导致服务调用失败。企业应用系统可以申请停机维护,同时升级接口,而网站服务不可能中断,需要服务提供者先升级接口,并同时提供历史版本的服务供请求者调用,当请求者访问接口升级后才可以关闭历史版本服务。



微服务架构落地思路与实践

微服务架构落地思路

  • 业务先行,先理顺业务边界和依赖,技术是手段而不是目的。

  • 先有独立的模块,后有分布式的服务。

  • 业务耦合严重,逻辑复杂多变的系统进行微服务重构要谨慎。

  • 要搞清楚实施微服务的目的是什么,业务复用?开发边界清晰?分布式集群提升性能?



最佳实践策略

 命令与查询职责隔离(CQRS) 

 在服务接口层面将查询(读操作)与命令(写操作)隔离,实现服务层的读写分离。

  • 更清晰的领域模型

  • 针对读写分别优化,实现更好的性能

  • 查询服务不会修改数据,更好地保护数据



事件溯源

将用户请求处理过程中的每次状态变化都记录到事件日志中,并按时间序列进行持久化

存储。

  • 利用事件溯源,可以精确复现任何用户状态,进行复核审计。

  • 利用事件溯源,可以有效监控用户状态变化,并在此基础上实现分布式事务。



断路器

当某个服务出现故障,响应延迟或者失败率增加,继续调用这个服务会导致调用者请求

阻塞,资源消耗增加,进而出现服务级联失效,这种情况下使用断路器阻断对故障服务

的调用。

  • 断路器三种状态:关闭,打开,半开



服务重试及调用超时

上游调用者超时时间要大于下游调用者超时时间之和



最重要的是需求

所有技术都应该遵循一个实践或者落地的一个指导思想

Needs:应该先有需求,想要达到什么样的目的

Values:达到目的能带来什么样的价值

Principles:为了达到这样的价值,我们应该遵循什么样的原则

Practices:基于这些原则,有哪些最佳实践,可以去参考,实现这些最佳实践的工具、框架、技术手段有哪些

Tools:最后选择一个合适的工具或者框架去解决我们的问题



战略设计

领域

每个组织都有它自己的业务范围和做事方式,这个业务范围以及在其中所进行的活动便是领域。领域是一个组织所做的事情以及其中所包含的一切。领域的范围可能很大,试图想要创建一个全功能的领域模型是非常困难的,并且容易导致失败,所以需要把这个大的业务范围拆开。如果把这个范围看成一个空间的话,那它同时存在以下两个概念:

  • 问题空间

问题空间是从业务需求方面来看的,用子域来划分问题空间

  • 解决方案空间

解决方案空间是从软件方面来看的,用限界上下文来划分解决方案空间

子域

子域是对领域从业务需求方面进行逻辑上的拆分,子域主要分以下几种:

  • 核心子域

业务成功的核心竞争力,拥有最高的优先级及最优秀的团队

  • 通用子域

不是核心,但被整个业务系统所使用

  • 支撑子域

不是核心,不被整个系统使用,专注于业务的某个方面

统一语言

统一语言是提炼领域知识的产出物,获得统一语言就是需求分析的过程,也是团队中各个角色就系统目标、范围与具体功能达成一致的过程。使用统一语言可以帮助我们讲参与讨论的客户、领域专家与开发团队拉到同一个维度空间进行讨论,因此,在沟通需求时,团队中的每个人都应使用统一语言进行交流。

统一语言体现在两个方面:

  • 统一的领域术语

  • 领域行为描述

限界上下文

限界上下文是一个边界,领域模型便存在于这个边界之内。原则上,一个上下文对应一个子域,但并不是绝对,限界上下文的目的是为了更加明确领域模型的职责和范围,是从解决方案空间来考虑问题的,通常来说,一个限界上下文可以是一个微服务,或者一个模块。

统一语言也是以限界上下文为边界进行划分的。

识别限界上下文的方式如下:

  • 从业务边界识别

明确了系统问题域和业务期望后,确定业务流程,然后确定具体的业务场景,基于业务场景识别出具体的业务活动,通过业务活动的语义相关性和功能相关性,来初步识别出限界上下文

  • 从工作界限识别

基于团队合作划分工作边界可以帮助我们确定限界上下文合理的工作粒度

  • 从应用边界识别

可以基于质量属性、扩展性、遗留系统等来对上下文进行划分

上下文映射

两个限界上下文之间的关系是有方向的,DDD 使用两个专门的术语表述他们,  上游(Upstream)下游(Downstream) ,在上下文映射图中,以 U 代表上游, D 代表下游。



根据团队协作模式,分为以下几种:

  • 合作关系

限界上下文的合作关系是一种反模式,这种关系存在强耦合,甚至糟糕的双向依赖,在设计中应该杜绝使用

  • 共享内核

共享内核往往被用来解决合作关系引入的问题,是解除不必要依赖并实现重用的重要手段

  • 客户方-供应方开发

这是团队合作中最为常见的合作模式,是上游团队满足下游团队提出的领域需求

  • 遵奉者

当由上游团队来决定是响应还是拒绝下游团队提出的请求时,就产生了所谓的遵奉者了。遵奉者还有一层意思是下游限界上下文对上游限界上下文模型的追随,比如对接口定义的重用。

  • 分离方式

指两个限界上下文之间没有丝毫关系,这是一种最好的方式,两者互相不影响。



根据通信集成模式,分为以下几种:

  • 防腐层

在架构层面,通过引入一个间接的层,就可以有效隔离限界上下文之间的耦合,这个防腐层类似于适配器模式。防腐层往往属于下游限界上下文,用以隔绝上游限界上下文可能发生的变化。

  • 开放主机服务

防腐层是下游限界上下文对抗上游变化的利器,开放主机服务就是上游服务用来吸引更多下游调用者的诱饵。设计开放主机服务,就是定义公开服务的协议,包括通信的方式、传递消息的格式,即传统意义上的接口。

  • 发布/订阅事件

即使使用开放主机服务,任然会导致两个上下文之间存在耦合关系,采用发布/订阅事件的方式可以在解耦合方面走的更远,通过消息中间件,发布方与订阅方完全隔离了,这种基于发布/订阅事件的协作关系,已经做到了力所能及的松耦合极致了。这种模式往往用于事件驱动架构或者 CQRS 架构模式中。



识别上下文映射,可以通过以下思考角度:

  • 协作产生的依赖

  • 领域行为产生的依赖

  • 领域模型产生的依赖

  • 数据产生的依赖

代码架构

DDD 的代码架构与传统的贫血模型使用的代码架构不同,代码架构有经典分层架构(四层架构)、六边形架构(整洁架构)、微服务架构,每种都有各自的特点,下图是基于以上的架构演进出来的一种架构:



image.png



这种架构符合整洁架构的思想,将业务与技术完全分离,适配层(基础设施层)依赖应用层和领域层,突出了系统的核心。

战术设计

战术设计是通过一系列的设计原则和手段,识别出限界上下文中具体的聚合、实体、值对象等关键对象及其之间的关系。

实体

实体是指要描述事物的主体,应该具有三要素:

  • 身份标识

身份标识是实体对象的必要标志,没有身份标识的领域对象就不是实体

  • 属性

属性用来说明其主体的静态特征,并通过属性持有数据与状态。

  • 行为

行为用来说明其主体的动态特征。一般分为变更状态的领域行为、自给自足的领域行为、互为协作的领域行为。

值对象

值对象与实体的根本区别在于是否拥有唯一的身份标识。值对象具有不变性,所以是线程安全的。值对象的领域行为与实体的领域行为并无区别。

聚合

聚合具有以下基本特征:

  • 聚合是包含了实体和值对象的一个边界

  • 聚合内包含的实体和值对象形成一棵树,只有实体才能作为这棵树的根,这个根称为聚合根

  • 外部对象只允许持有聚合根的引用

  • 聚合作为一个完整的领域概念整体,在其内部会维护这个领域概念的完成性,体现业务上的不变量约束

  • 有聚合根统一对外提供履行该领域概念职责的行为方法,实现内部各个对象之间的行为协作



用户头像

Null

关注

还未添加个人签名 2017.12.29 加入

还未添加个人简介

评论

发布
暂无评论
极客时间架构 1 期:第 10 周 模块分解 - 学习总结