模块分解总结
微服务:服务设计、维护与治理
微服务产生背景:网站系统越来越复杂,随着系统复杂性提高,维护也越困难。
解决方案:将模块独立部署,降低系统耦合。
纵向拆分:将大应用拆成小应用,如果新应用比较独立,将其设计部署为一个独立的web应用。
水平拆分:将负用的业务拆分,独立部署为微服务,新增业务只需要调用这些微服务即可快速搭建一个应用系统。
Web service与企业级分布式服务
缺点:
l 臃肿的注册与发现机制。
l 低效的xml序列化手段。
l 开销相对较高的HTTP远程通信。
l 复杂的部署与维护手段。
以上缺点导致web service难以满足大型网站系统对高性能、高可用、易部署、易维护要求。
微服务框架需求
服务注册与发现。
负载均衡:对于集群部署提供者,服务请求者可以使用加权轮询等手段访问,使服务提供者集群实现负载均衡。
失效转移:对于大型互联网的微服务而言,即使是很少访问的简单访问,也需要集群部署。同时微服务框架还需要支持服务提供者的失效转移机制,以实现服务高可用。
高效的远程通信:
对于大型网站,核心服务每天的调用次数会达到数以亿计,如果没有高效的远程通信手段,服务调用可能会成为整个系统性能瓶颈。
对应用最少侵入:
网站技术是为业务服务的,是否使用微服务需要根据业务发展规划,微服务也需要渐进式的演化,甚至会出现反复,使用微服务后又退回集中式部署,微服务框架需要支持这种渐进式演化和反复。当然服务模块本身需要支持可集中式部署,也可分布式部署。
版本管理:
为应对快速变化需求,服务版本升级不可避免,如果仅仅是服务实现升级,那这种升级对服务请求者而言是透明的,无需关注。
微服务框架(dubbo)框架
Service mesh服务网格
Service mesh是基础设施层,用于处理服务间通信,通常表现为一组轻量级网络代理,他们与应用程序部署一起,而对应用程序透明。
Service mesh的sidecar模式
微服务架构落地
l 业务先行,先理顺业务边界和依赖,技术是手段而不是目的。
l 现有独立模块,再有分布式服务。
l 业务耦合严重,逻辑复杂多变的系统进行微服务重构时要谨慎。
l 要搞清实施微服务的目的是什么?业务复用?开发边界清晰?分布式集群提升性能?
命令与查询职责隔离(CQRS)
在服务接口层面将查询(读操作)与命令(写操作)隔离,实现服务层的读写分离。
l 更清晰的领域模型。
l 针对读写分别优化,实现更好性能。
l 查询服务不会修改数据,更好的保护数据。
事件溯源
将用户请求处理过程中的每次状态变化都记录在事件日志中,并按时间顺序进行持久化存储。
l 利用事件溯源,可以精确复现用户任何状态,进行复核审计。
l 利用事件溯源,可以有效监控用户状态变化,并在此基础上实现分布式事务。
断路器
当服务出现故障,响应延迟或者失败率增加,继续调用这个服务会导致调用请求阻塞,资源消耗增加,进而出现服务级联失效,这种情况下使用断路器阻断对故障服务的调用。
断路器三种状态:关闭、打开、半开。
微服务网关的技术架构
基于网关的微服务架构
网关作用
网关管道技术
网关本身没有什么业务,主要职责就是做各种校验与拦截,这些职责可以通过管道技术连接起来。
Request –> 参数校验 -> 限流控制 -> 接口调用 -> request
使用责任链模式实现管道技术
开放平台网关
API接口:是开放平台暴露给合作者的使用的一组API,其形式可以是Restful、webservice、RPC等形式。
协议转换:将各种API输入转换成内部服务可以识别的形式,并将内部服务的返回封装成API的格式。
安全:除了一般应用需要的身份识别、权限控制等安全手段,开放平台还需要分级的访问带宽控制,保证平台资源被第三方应用公平合理使用,也保护网站内部服务不会被外部应用拖垮。
审计:记录第三方应用的访问情况,并进行监控、计费等。
路由:将开放平台的各种访问路由映射到具体的内部服务。
流程:将一组离散的服务组织成一个上下文相关的新服务,隐藏服务细节,提供统一接口供开发者调用。
开放授权协议OAuth2.0
授权码授权
OAuth2.0一共有四种授权方式:授权码、隐式授权、资源所有者密码、凭据和客户端凭据。
目前互联网使用最多也是最安全的一种方式是授权码授权。
领域驱动设计DDD
多数项目现状:
l 用户和产品经理需求零零散散,不断变更。
l 工程师在各处代码中寻找可以实现这些需求变更的代码,修修补补。
l 软件只有需求分析,并没有真正的设计,系统没有一个统一的领域模型维持其内在的逻辑一致性。
l 功能特性并不是按照领域模型的内在的逻辑设计,而是按照各色人等自己的主观想象设计。
事务脚本
领域模型
贫血模型VS充血模型
由于事务脚本中,Service、Dao这些对象中只有方法,没有数值成员变量,而方法调用时传递的数值对象,比如contact,没有方法(只有一些setter getter方法),因此事务脚本被称为贫血模型。
领域模型的对象则包含了对象的数据和计算逻辑,比如合同对象,既包含合同数据,也包含合同相关的计算。因此从面向对象的角度看,领域模型是真正的面向对象。收入确认是和合同强相关,是合同对象的一个职责,那么合同对象就应该提供一个calculateRecognition方法计算收入。
领域模型是合并了行为和数据的领域的对象模型。通过领域模型的对象的交互实现业务逻辑。也就是说,设计好了领域模型对象,也就设计好了业务逻辑实现,因此领域模型被称为充血模型。
DDD战略设计
领域是一个组织要做的事情以及包含的一切,通俗的说,就是组织的业务范围和做事方式,也就是软件开发的目标范围。
领域驱动设计就是从领域出发,分析领域内模型及其关系,进而设计软件系统的方法。
子域
通常将整个领域拆分成多个子域。比如:用户、商品、订单、库存、物流、发票等。
限界上下文
在一个子域中,会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该边界内部的确定含义。这个边界称为限界上下文。限界上下文和子域一对一,用来控制子域边界,保证子域内的概念统一性。
通常限界上下文对应一个组件或者一个模块,或者一个服务、一个子系统。
上下文映射图
不同的限界上下文,也就是不同的子系统或者模块之间会有各种的交互合作。DDD使用上下文映射图来设计这种关联和交互。
实体
领域模型对象也被称为实体,每个实体都是唯一的,具有唯一标识,一个订单对象是一个实体,一个产品对象也是一个实体,订单ID或者产品ID是它们的唯一标识。实体可能会发生变化,比如订单的状态变化,但是它们的唯一标识不会变化。
DDD核心所在,通过业务逻辑分析,识别出实体对象,然后通过相关的业务逻辑设计实体的属性和方法。这里是最重要的,是要把握住实体的特征是什么,实体应该承担什么职责,不应该承担什么职责,分析的时候要放在业务场景和界限上下文中,而不是想当然的认为这样的实体应该承担这样的角色。
值对象
领域内的对象兵不应该都设计为实体,DDD推荐尽可能将对象设计为值对象。比如像住址这样的对象就是典型的值对象,建在住址上的房子可以被当做一个实体,但住址仅仅是房子一个描述,像这样仅仅用来做度量或描述的对象应该被设计为值对象。
值对象的一个特点是不变性,一个值对象创建以后就不能再改变了。如果地址改变了,那就是一个新地址,而一个订单实体则可能会经历创建、待支付、已支付、待发货、已发货等各种变化。
聚合
聚合是关联对象的集合,我们将其作为一个单元来处理数据更改。每个集合都有一个根和一个边界。边界定义了聚合内部的内容。根是聚合中包含的单个特定实体。
聚合根:将多个实体和值对象聚合在一起的对象。
DDD分层架构
领域实体的组合调用和事务控制在应用层。
DDD六边形架构
领域模型通过应用程序封装成一个相对独立的模块,而不同的外部系统通过不同的适配器和领域模型交互,比如可以通过HTTP接口访问领域模型,也可以通过webservice或者消息队列访问领域模型,只需要为这些不同的访问接口提供不同适配器。
DDD战略设计:领域、子域、界限上下文、上下文映射图。
DDD战术设计:实体、值对象、聚合、CQRS、事件溯源。
通过战略设计,划分模块和服务的边界及依赖关系,对微服务架构的设计至关重要。
软件组件设计原则
组件内聚原则
l 复用发布等同原则
版本号约定建议:
主版本号升级,表示组件发生了不向前兼容的重大变更。
次版本号升级,表示组件进行重要功能修订或bug修复,但组件向前兼容。
修订号升级,表示组件进行不重要功能修订或bug修复。
l 共同封闭原则
通常将那些会同时修改,为相同目的而修改的类放在同一个组件中,将不会为相同目的而修改的类放在不同组件中。
l 共同复用原则
不会强迫一个组件的用户依赖它们不需要的东西。
组件耦合原则
l 无循环依赖原则
l 稳定依赖原则
l 稳定抽象原则
案例:用领域驱动设计驱动系统重构
随着业务发展如何做到高内聚、低耦合
版权声明: 本文为 InfoQ 作者【Mars】的原创文章。
原文链接:【http://xie.infoq.cn/article/0513bac26e84c2394b7e7c797】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论