架构师训练营 - 学习笔记 - 第十周
思考与感悟
找到问题的关键、本质
我记得戴尔卡耐基在《人性的弱点》一书中说过:“我不是告诉你新的道理,而是不断地重复已有的,让你不再忘记。”所以这节课智慧老师又重复了这个原则:一定要先找到问题;不要拿着锤子找钉子,先找到钉子,找到问题。
找到问题的关键,因为书本、博客上的知识都是“大路货”,你能学会的,别人都能学会。—— 关键是你能不能快速、高效的解决当下公司遇到的问题。
靠学习知识只能维持你不被淘汰,你要去思考问题背后的东西,而不要干巴巴地去学这些知识。
DDD
智慧老师在讲 DDD 的时候,讲到要如何分析问题领域,拆分子域(微服务),分析业务边界,至于你用不用 DDD 去实现领域模型,或者你只用 DDD 做了战略设计,而没有战术设计,这些都可以(并不是说你只用了战略设计,没用战术设计就不是 DDD 了)。
多面试——了解行业现状以及你的身价
周五晚上参加了左耳朵耗子—陈皓的面试直播,我主要收获是:你要不断地去面试,好处如下:
了解行业现状
通过不断地看 JD(Job Description)、去面试, 你就能知道市场需要什么样的人才、什么样的主流技术。
了解你在行业中的身价
不能把自己卖“贱”了 。
锻炼你的软技能——表达能力、沟通能力、谈判能力
微服务——关键是要搞清楚业务边界
微服务的关键点不在技术,而是要搞清楚业务及模块的边界。先把逻辑关系、耦合关系理顺了,先大后小,关注服务分离。
微服务框架:Spring Cloud, Dubbo
智慧语录:不要用战术上的勤奋,去掩盖你战略上的懒惰
996, 007 等加班,有意思吗?真的需要吗?还是真的只是像智慧老师说的某宝的例子一样:就改了一行代码,自测没问题,但是却不能走,要等软件发布,然后留在公司打游戏,打到凌晨三点。。。
TIPS
不要把 DDD 当作一门技术去学习(可以是战略层面的)。
微服务 - 2020/8/6 - 周四
阿里早期微服务架构重构
巨无霸应用系统带来的问题
编译、部署困难
很不 DevOps
代码分支管理困难
merge 冲突
数据库连接耗尽
巨型的应用、大量的访问,必然需要讲这个应用部署在一个大规模的服务器集群上,应用与数据库的连接通常使用数据连接池,以每个应用10个连接计,一个数百台服务器集群的应用将需要在数据库服务器集群上创建数千个连接,数据库服务器上,每个连接都会占用一些昂贵的系统资源,以至于数据库缺乏足够的系统资源进行一般的数据操作。
新增业务困难
乱,烂代码,一脚踩进去,发现全都是雷,什么都不敢碰。
解决方案就是拆分,将模块独立部署,降低系统耦合性:
纵向拆分:将一个大应用拆分为多个小应用,如果新增业务较为独立,那么就直接将其设计部署为一个独立的 Web 应用系统。
横向拆分:将复杂的业务拆分出来,独立部署为微服务,新增业务只需要调用这些微服务即可快速搭建一个应用系统。
微服务框架
Web Service 与企业级分布式服务
服务提供者通过 WSDL (Web Services Description Language) 向注册中心(Service Broker) 描述自身提供的服务接口属性,注册中心使用 UDDI(Universal Description, Discovery, and Integration, 统一描述、发现和集成)发布服务提供者提供的服务,服务请求者从注册中心检索到服务信息后,通过 SOAP(“肥皂协议”,Simple Object Access Protocol, 简单对象访问协议) 和服务提供者通信,使用相关服务。
Web Service 虽然有着成熟的技术规范和产品实现,以及在企业应用领域有许多成功的案例,但是也具有一些固有的缺点:
臃肿的注册与发现机制
低效的 XML 序列化手段
开销相对较高的 HTTP 远程通信
复杂的部署与维护手段
这些问题导致 Web Service 难以满足大型网站对系统高性能、高可用、易部署、易维护的要求。
微服务框架需求
大型互联网系统,需要微服务框架能够支持:
失效转移(Fail Over):
对于大型网站的微服务而言,即使是很少访问的简单服务,也需要集群部署,同时微服务框架还需要支持服务提供者的失效转移机制,以实现服务高可用。
负载均衡:
对于集群部署的服务提供者,服务请求着可以使用加权轮询等手段访问,使服务提供者集群实现负载均衡。
高效的远程通信:
对于大型网站,核心服务每天的调用次数会达到数以亿计,如果没有高效的远程通信手段,服务调用可能会成为整个系统性能的瓶颈。
对应用最少侵入:
网站技术是为业务服务的,是否使用微服务需要根据业务发展规划,微服务也需要渐进式地演化,甚至会出现反复,即使用了微服务后又退回到集中式部署,微服务框架需要支持这种渐进式演化和反复。当然服务模块本身需要支持可集中式部署,也可分布式部署。
版本管理:
为了应对快速变化的需求,服务版本升级不可避免,如果仅仅是服务实现升级,那么这种升级对服务请求者而言是透明的,无需关注。但是如果服务的访问接口发生变化,就需要服务请求者和服务提供者同时升级才不会导致服务调用失败。企业应用系统可以申请停机维护,同时升级接口,而网站服务不可能中断,需要服务提供者先升级接口,并同时提供历史版本的服务供请求者调用,当请求者访问接口升级后才可以关闭历史版本服务。
微服务框架(Dubbo)架构
Service Mesh 服务网格
Service Mesh 是一个基础设施层,用于处理服务间的通信,通常表现为一组轻量级网络代理,他们与应用程序部署在一起,而对应用程序透明。
Service Mesh 的 Sidecar 模式
微服务架构实践
微服务架构落地
业务先行,先理顺业务边界和依赖,技术是手段而不是目的。
先有独立的模块,后又分布式的服务。
业务耦合严重,逻辑复杂多变的系统进行微服务重构要谨慎。
要搞清楚实施微服务的目的是什么,业务复用?开发边界清晰?分布式集群提升性能?
命令与查询职责隔离(CQRS)
在服务接口层面将查询(读操作)与命令(写操作)隔离,实现服务层的读写分离。
更清晰的领域模型
针对读写分别优化,实现更好的性能
查询服务不会修改数据,更好的保护数据
事件溯源
将用户请求处理过程中的每次状态变化都记录到事件日志中,并按实践序列进行持久化存储。
利用事件溯源,可以精确复现任何用户状态,进行复核审计。
利用事件溯源,可以有效监控用户状态变化,并在此基础上实现分布式事务。
断路器
当某个服务出现故障,响应延迟或者失败率增加,继续调用这个服务会导致调用者请求阻塞,资源消耗增加,进而出现服务级联失效,这种情况下使用断路器阻断对故障服务的调用。
断路器三种状态:关闭,打开,半开
服务重试及调用超时
上游调用者超时时间要大于下游调用者超时时间之和。
最重要的是需求
RPC 协议实现原理
远程过程调用(RPC)
通讯协议(Communications Protocol)
通讯协议在电信领域中是指在任何物理介质中允许两个或多个在传输系统中的终端之间传播信息的系统标准,也是指计算机通信或者网路设备的共同语言。
一个完整的应用层通信协议通常包含两个部分:
网络通信协议:TCP、UDP
编码传输协议:二进制、文本、协议头格式
为什么设计私有通讯协议?
充分并有效利用通讯协议里的每个字段,减少冗余数据传输
灵活满足自定义通讯需求,例如 CRC 校验、Server Fail-fast、自定义序列化器
最大程度满足性能需求:IO 模型和线程模型的充分利用
常见的私有协议模式
Dubbo 通讯协议
Dubbo 通讯协议-解码 Response 对象
Dubbo 通讯协议-解码 Request 对象
Dubbo 利用 requestId 避免队头阻塞
SOFA-RPC 通讯协议(Bolt 协议)
请求命令:
响应命令:
序列化协议
微服务网关
基于网关的微服务架构
网关作用
网关:微服务的请求者与消费者
微服务网关
网关管道技术
Flower 异步网关与异步微服务框架
开放平台网关
开放授权协议 OAuth2.0
授权码授权
OAuth 2.0 一共有四种授权方式:授权码、隐式授权、资源所有者密码凭据和客户端凭据。
目前互联网上使用最多也是最安全的一种方式是授权码方式。
领域驱动设计 DDD - 2020/8/8 - 周六
为什么需要 DDD
用户或者产品经理的需求零零散散,不断变更。
工程师在各处代码中寻找可以实现这些需求变更的代码,修修补补。
软件只有需求分析,并没有真正的设计,系统没有一个统一的领域模型维持其内在的逻辑一致性。
功能特性并不是按照领域模型内在的逻辑设计,而是按照各色人等自己的主观想象设计。
项目时间一长,各种困难重重,需求不断延期,线上 bug 不断,管理者考虑是不是要推到重来,而程序员则考虑是不是要跑路。
事务脚本
领域模型
让 类,对象自己去处理业务逻辑,而不是交给 Service
贫血模型 VS 充血模型
事务脚本中,Service、Dao 这些对象只有方法,没有数值成员变量,而方法调用时传递的数值对象,比如 Contract, 没有方法(或者只有一些 getter、setter 方法 ),因此事务脚本又被称作贫血模型。
领域模型的对象则包含了对象的数据和计算逻辑,比如合同对象,既包含合同数据,也包含合同相关的计算。因此从面向对象的角度看,领域模型才是真正的面向对象。收入确认是和合同强相关的,是合同对象的一个职责,那么合同对象就应该提供一个 calculateRecognition 方法计算收入。
领域模型是合并了行为和数据的领域的对象模型。通过领域模型对象的交互完成业务逻辑的实现,也就是说,设计好了领域模型对象,也就设计好了业务逻辑实现。和事务脚本的贫血模型相对应的,领域模型也被称为充血模型。
领域是一个组织所做的事情以及其包含的一切,通俗地说,就是组织的业务范围和做事方式,也是软件开发的目标范围。
领域驱动设计就是从领域出发,分析领域内模型及其关系,进而设计软件系统的方法。
子域
领域是一个组织所做的事情以及其包含的一切。这个范围就太大了,不知道该如何下手。
所以通常的做法是把整个领域拆分成多个子域,比如用户、商品、订单、库存、物流、发票等。
如何划分子域?
限界上下文
在一个子域中,会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该边界内部的确切含义。这样边界便称为限界上下文。限界上下文和子域具有一对一的关系,用来控制子域的边界。
通常限界上下文对应一个组件或者一个模块,或者一个微服务。
上下文映射图
不同的限界上下文,也就是不同的子系统或者模块之间会有各种的交互合作。DDD 使用上下文映射图来设计这种交互。
实体
领域模型对象也被称为实体,每个实体都是唯一的,具有一个唯一标识,一个订单对象是一个实体,一个产品对象也是一个实体,订单 ID 或者产品 ID 是它们的唯一标识。实体可能会发生变化,比如订单的状态会变化,但是它们的唯一标识不会变化。
实体设计是 DDD 的核心所在,首先通过业务分析,识别出实体对象,然后通过相关的业务逻辑设计实体的属性和方法。这里最重要的,是要把握住实体的特征是什么,实体应该承担什么职责,不应该承担什么职责,分析的时候要放在业务场景和界限上下文中,而不是想当然地认为这样的实体就应该承担这样的角色。
值对象
并不是领域内的对象都应该被设计为实体,DDD 推荐尽可能将对象设计为值对象。比如像住址这样的对象就是典型的值对象,也许建在住址上的房子可以被当作一个实体,但是住址仅仅是对房子的一个描述,像这样仅仅用来做度量或描述的对象应该被设计为值对象。
值对象的一个特点是不变性,一个值对象创建以后就不能再改变了。如果地址改变了,那就是一个新地址,而一个订单实体则可能会经历创建、待支付、已支付、代发货、已发货、待签收、待评价等各种变化。
聚合
聚合是一个关联对象的集合,我们将其作为一个单元来处理数据更改。每个集合都有一个根和一个边界。边界定义了聚合内部的内容。根是聚合中包含的单个特定实体。
聚合根:将多个实体和值对象聚合在一起的实体。
DDD 分层架构
用户接口层 -> 应用层 -> 领域层
DDD 六边形架构
领域模型通过应用程序封装成一个相对比较独立的模块,而不同的外部系统则通过不同的适配器和领域模型交互,比如可以通过 HTTP 接口访问领域模型,也可以通过 Web Service 或者消息队列访问领域模型,只需要为这些不同的访问接口提供不同的适配器就可以了。
DDD 战略设计与战术设计
领域、子域、界限上下文、上下文映射图,这些是 DDD 的战略设计。
实体、值对象、聚合、CQRS、事件溯源,这些事 DDD 战术设计。
通过战略设计,划分模块和服务的边界及依赖关系,对微服务架构的设计至关重要。
智慧老师经历的一个 DDD 重构实践过程
当前系统设计与问题汇总讨论
架构与代码混乱,需求迭代困难,部署麻烦,bug 率逐渐升高
正对问题分析具体原因
子系统 A 太庞大,模块 B 和 C 职责不清,业务理解不一致
重新梳理业务规则和边界,明确业务术语
DDD 战略设计,领域建模
技术框架选型与落地方案验证
DDD 战术设计,样例代码
任务分解与持续重构
在不影响业务开发的前提下,按照战略与战术设计,将重构开发和业务迭代有机融合
如果一个工作多年的程序员,还是仅仅写一些跟他工作第一年差不多的 CRUD 代码,那么他迟早会遇到自己的职业危机。公司必然愿意用更年轻、更努力,当然也更低薪水的程序员来代替他。至于学习新技术的能力,其实多年工作经验也并没有太多帮助,有时候也许还是劣势。
资深程序员真正有优势的是他在一个业务领域的多年积淀,对业务领域有更深刻的理解和认知。那么如何将这些业务沉淀和理解反映到工作中,体现在代码中呢?实践 DDD 是一个不错的方式。
如果一个人有多年的领域经验,那么必然对领域模型设计有更深刻的认识,把握好领域模型在不断的需求变更中的演进,使系统维持更好的活力,并因此体现自己真正的价值。
参考链接
https://xie.infoq.cn/article/a4553af28bc43fc3db6c4a64c
https://xie.infoq.cn/article/109a5712dbec6007702e04089
评论