阿里早期为什么要上微服务?

单体应用的问题

  • 一个war包里包含所有业务模块
  • 打包的war包很大,编译,部署困难
  • 代码分支管理困难
  • 数据库连接耗尽
  • 新增模块困难

面向服务器的架构思想,最早应用的是webService,因为以下缺点导致没有在互联网项目中大规模应用:

  • 臃肿的注册与发现机制
  • 低效的XML序列号手段
  • 开销相对较高的Http远程通信
  • 复杂的部署与维护手段

image.png

微服务框架的需求

  • 服务注册与发现
  • 失效转移
  • 负载均衡
  • 高效的远程通信
  • 对应用最少侵入
    • 应用可以无感知地调用远程服务。
  • 接口的版本管理

微服务架构

Dubbo的架构
image.png

  • 服务器提供者
    • 在服务的容器中启动
    • 服务容器根据配置把服务列表向服务中心提供
    • 收到消费者的请求后,将结果打包通过远程模块返回给消费者
  • 服务注册中心
    • 维护服务器与服务列表的对应关系
  • 服务消费者
    • 通过服务接口调用服务,不依赖具体的实现
    • 服务接口通过接口代理访问服务框架客户端
    • 客户端先访问本地的服务提供列表,如果没有再访问远程的服务注册中心
    • 通过自己的负载均衡模块确定要连接的具体ip和端口
    • 通过远程通讯模块连接到远程服务的ip和端口,一般会使用tcp的长连接
    • 服务框架客户端打包请求,通过远程通讯模块发送给服务提供者
    • 接口访问代理将收到的结果包反序列化成class类,供服务接口调用

Service Mesh 服务网格

  • 是一个基础设施层,用于处理服务间的通信
  • 通常表现为一组轻量级的网络代理,与应用程序部署在一起,而对应用程序透明

image.png

service Mesh的边车模式(sideCare模式)
image.png
更多的service Mesh可以连成一个网络
image.png

微服务实践

微服务如何落地

  • 业务先行,业务是最重要的,如果没有理清楚业务边界和依赖,很容易导致失败
    • 业务耦合严重,逻辑复杂多变的系统进行微服务重构要谨慎
  • 技术是手段不是目的
  • 先有独立的模块,然后才有分布式的服务
  • 要搞清楚实施微服务的目的是什么,不能为了微服务而做微服务。确定了目的在业务拆分的时候才能更好的做出选择,一个业务到底应该怎么拆分。
    • 业务复用
    • 开发边界
    • 分布式集群提升性能

      微服务最佳实践

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

      在服务层面实现读写分离,可以分别针对读写进行优化,是使用缓存还是消息队列

事件溯源

完整记录处理过程中的每次状态编号,并按时间序列进行持久化存储。

  • 可以精确复现任何用户状态,进行复核审计
  • 可以有效监控用户状态编号,在此基础上实现分布式事务。
    • 可以方便的设置检查点,判断要做commit或rollback

断路器

当某个服务出现故障,响应延迟或者失败率增加,继续调用这个服务就会导致调用者请求阻塞,资源消耗增加,进而出现服务级联失效,这时应该使用断路器对故障服务进行调用。

三种状态:关闭、打开,半开
image.png

服务重试及调用超时

上游调用者超时时间要大于下游调用者超时时间之和,所以越往下的调用者超时时间应该越短

要关注的问题排序

需求是最重要的,用来解决什么问题
能否带来期望的价值
原则
最佳实践
选用工具
image.png

RPC协议实现原理

本质还是方法调用,但是是远程的
image.png

要解决的问题:

  1. 怎样进行远程通信
  2. 怎样找到要调用的那个方法

通讯协议

通信协议:tpc、ucp
编码传输协议:二进制、文本、json,xml
高效的通讯协议需要自定义,以充分利用协议中的每个字段,避免数据冗余

常见的私有协议模式

  • 定长协议
    • 协议内容长度是固定的,读写性能高,但灵活性差
  • 特殊结束符协议
    • 用户数据中不能包含结束符否则会解析错误,且需求读取所有数据包后才能进行处理
  • 变长协议
    • 使用更广泛
    • 协议头+协议体
    • 使用固定长度的协议头记录协议体特征

Dubbo协议
image.png

  • 使用requestId避免队头阻塞

    基于网关的微服务架构

  • 网关尽量不要涉及业务逻辑,只做简单的请求管理。比如各种校验和拦截,这些职责可以通过管道技术连接起来。责任链模式可以实现。image.png

  • 网关充当消费者

image.png

  • 大部分的网关都不是异步的,如果要实现异步可以利用servlet3实现image.png

image.png

image.png

开发平台网关

一般要考虑实现的功能包括:

  • API接口
  • 协议转换
  • 安全
    • 除了一般应用需要的身份识别、权限控制等安全手段
    • 还需要实现分级的访问带宽限制,保证平台服务不会受到外部服务调用的影响
  • 审计
    • 记录第三方应用的访问情况,在此基础上实现计费,监控等功能
  • 路由
    • 将各种访问路由映射到具体的内部服务
  • 流程
    • 将一组离散的服务组织成一个上下文相关的新服务,隐藏服务细节,提供统一接口给第三方调用
  • 开放授权协议OAuth2.0
    • image.png
    • 授权码授权示例
      • image.png

领域驱动设计DDD

DDD是为了实现领域模型的一种方法和手段

  • 为什么需要DDD
    • 需求零零散散,不断变更
    • 实现需求的代码有多种方式可以放在这个模块,也可以放到别的模块,没有一个统一的模型维持其内在的逻辑一致性
    • 领域模型要维持业务一致、产品一致、内部实现一致
    • 代码实现要做到优雅,逻辑自洽

领域是什么

领域是一个组织所做的事情以及其所包含的一切。即组织的业务范围和做事方式,也是软件开发的目标范围。
领域驱动设计就是从领域出发,分析领域内模型及其关系,进而设计软件系统的方法。

传统的做法

  • 事务脚本,随着业务的增加,会导致service层增大,业务逻辑复杂
  • 也叫贫血模式,Service、dao中只有方法,没有数值成员变量;而方法调用时传递的数值,只有数据,没有方法;

image.png

领域模型

  • 每个类复杂所有和自己相关的操作,比如Contract的计算是没有参数的,内部实现都是通过自己的成员变量来计算所得,具体成员变量的值则从外部获取,而不是将数据和操作分离开。
  • 类似于面向对象的设计方式
  • 又称为充血模式,相比贫血模式,领域模型的对象既包含对象的数据,又包含了计算逻辑。
  • 设计好了领域模型,也就等于设计好了业务逻辑。

image.png

DDD的战略设计

通过战略设计,划分模块和服务的边界以及依赖关系

拆分子域

领域包含的范围过大,通常的做法是首先把领域拆分成子域,比如用户、商品、订单、库存、物流、发票等子域。

子域实际上就对应微服务。

限界上下文

在一个子域中,会创建一个概念上的领域边界。在这个边界内,任何领域对象都只表示特定于该边界内部的确切含义。这样的边界便称为限界上下文。

限界上下文和子域具有一对一的关系,用来控制子域的边界,保证子域内的概念统一。

比如商品在不同的子域(比如财务,订单,物流等)内代表不同的模型和业务流程,要在所有子域内实现统一并不现实,所以要限定上下文,保证在一个子域内商品的定义是一致的。

通常限界上下文对应一个组件或一个模块,或者一个微服务,一个子系统。

上下文映射图

不同的限界上下文会有各种交互,DDD中使用『上下文映射图』来设计这种关联和交互。
image.png

DDD的战术设计

实体

  • 领域模型的对象也被称为实体
  • 每个实体都是唯一的,具有一个唯一标识
  • 实体可能发生变化,但唯一标识是不会变的
  • 实体设计是DDD的核心所在
    • 通过业务分析,识别出实体对象
    • 通过业务逻辑设计实体的属性和方法
    • 要把握实体的特征是什么
      • 应该承担什么职责
      • 不应该承担什么职责
      • 分析的时候要把实体放到业务场景和限界上下文中,而不是考虑通常意义上的实体,想当然的觉得应该承担什么职责

值对象

  • 并不是领域内的所有对象都应该被设计成实体,DDD推荐尽可能地将对象设计为值对象。
  • 什么样的对象应该是值对象
    • 有不变性
    • 只是对一个实体的描述或者度量
    • 比如住址
      • 是对房子这个实体的描述
      • 有不变性,如果住址变了,那就需要创建一个新的住址对象
      • 与之相对的比如订单,订单的状态会经历创建,待支付,已支付,已发货等变化,当仍然是同一个订单

聚合

  • 聚合是一些关联对象的集合,我们将其作为一个单元来处理数据更改
  • 每个集合都有一个根和一个边界。
    • 边界定义聚合内部的内容
    • 根是聚合包含的单个特定实体
    • 聚合根,将多个实体和值对象聚合在一起的实体,负责对外提供服务。外部如果要调用方法应该通过聚合根来实现,而不是调用其内部的对象。(比如车对象是聚合根,启动应该调用车的启动实现,而不是调用车引擎的启动来实现)。image.png

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

在服务层面实现读写分离,可以分别针对读写进行优化,是使用缓存还是消息队列

事件溯源

完整记录处理过程中的每次状态编号,并按时间序列进行持久化存储。

  • 可以精确复现任何用户状态,进行复核审计
  • 可以有效监控用户状态编号,在此基础上实现分布式事务。
    • 可以方便的设置检查点,判断要做commit或rollback

DDD的分层设计

image.png

领域层中包含各种实体以及实现
领域实体的组合和调用,应该控制在应用层
用户接口层通过应用层调用领域层实现业务逻辑

DDD六边形架构

领域模型通过应用程序被封装成相对比较独立的模块,而不同的外部系统有不同的业务需求。
比如外部系统有的需要通过Http接口访问领域模型,有的需要通过Web Service接口访问模型,这时要做的就是为领域模型提供不同的适配器
image.png

总结

一个DDD重构的实践过程
image.png

战略设计可以独立于战术设计进行,战略设计有利于重构业务模型,梳理明确业务才能保证开发的顺利进行,即使不落地战术设计,对系统开发也有着重要的指导意义。

扩展

现实中业务域要和系统中的模型要做到一致,系统要能虚拟出现实中的各种关系,这样才能做到系统优雅自然地随着外部业务需求的变化而变化。所以,架构一个信息化的系统,首先要了解这个系统应用的外部组织结构是如何工作的

但现实中的业务模型可能也是混乱的,这就需要对现实中的业务模型再进行抽象和优化。

子域的拆分要根据业务需求以及可能发展的方向来,这个可能只有产品经理是最了解的,工程师如果能通过领域模型和产品经理确认需求,可能会更好一点。

子域设计好以后,所有的需求都是在子域内实现,通过调整子域内以及子域间的交互来实现需求,这样的思路下,一个需求的实现逻辑其实就是固定的,不会再出现有多种实现方式。

一个系统的发展,相当于维护领域的内在一致性,当出现业务发展和领域模型不一致时,就需要及时调整领域,这有可能是前期对领域的设计有问题,也有可能是当前的领域已经不能适应业务的发展了。

资深程序员真正的优势是他在一个业务领域的多年沉淀,对业务领域有更深刻的理解和认知,通过实践DDD可以将这些沉淀反映到工作中,反映到代码中。这也是一种架构。