1.1 从传统单体架构到服务化架构
1.1.1 JEE 架构
JEE 将企业级软件架构分为三个层级:
- Web 层:负责与用户交互或者对外提供接口。
- 业务逻辑层:为实现业务逻辑而设计的流程处理和计算处理模块。
- 数据存取层:将业务逻辑层处理的结果持久化以待后续查询,并维护领域模型中对象的生命周期。
JEE 平台将不同的模块化组件聚合后运行在通用的应用服务器上,例如:WebLogic、WebSphere、JBoss、Tomcat
图1-1 JEE 时代的典型架构
1.1.2 SSH 架构
- Struts、Spring、Hibernate
- 视图、模型、控制器(Struts MVC)
图1-3 MVC 模型
- Spring IoC --> 【Spring思维导图,让Spring不再难懂(ioc篇)】
- Spring AOP --> 【Spring思维导图,让Spring不再难懂(AOP 篇)】
图1-4 SSH 时代的架构
1.1.3 服务化架构
从 JEE 到 SSH ,服务的特点仍然是单体化、服务的粒度抽象为模块化组件,所有组件耦合在一个项目中,并配置和运行在一个 JVM 进程中。
为解决上述问题,SOA 出现了:
- SOA 代表面向服务的架构,俗称服务化。
- SOA 将应用程序的模块化组件通过【定义明确的接口和契约】联系起来,接口采用中立的方式进行定义,独立于某种语言、硬件和操作系统。
- SOA 通常通过网络通信来完成,但不局限于某种网络协议,可以是底层的 TCP/IP,也可以是应用层的 HTTP,也可以是消息队列协议,甚至可以是约定的某种数据库存储形式。
SOA 服务化的发展:
一、Web Service
SOA 服务化的一种实现方式,使得运行在不同机器及操作系统上的服务互相发现和调用成为可能,并可以通过某种协议交换数据。
图1-5 Web Service 的工作原理图
- 服务提供者 Web Service 2 和 Web Service 3 通过 UDDI 协议将服务注册到 Web Service 目录服务中。
- 服务消费者 Web Service 1 通过 UDDI 协议从 Web Service 目录中查询服务,并获得服务的 WSDL 服务描述文件。
- 服务消费者 Web Service 1 通过 WSDL 语言远程调用和消费 Web Service 2 和 Web Service 3 提供的服务。
二、ESB
ESB 是企业服务总线的简称,用户设计和实现网络化服务交互和通信的软件模型,是 SOA 的另外一种实现方式,主要用于企业信息化系统的集成服务场景中。
ESB 也适用于事件处理、数据转换、映射、消息和事件异步队列顺序处理、安全和异常处理、协议转换、保证通信服务的质量等场景。
图1-6 ESB 架构图
ESB 服务密友中心化的服务节点,每个服务提供者都是通过总线的模式插入系统,总线根据流程的编排负责将服务的输出进行转换并发送给流程要求的下一个服务进行处理。ESB 的核心在于企业服务总线的功能和职责:
- 监控和控制服务之间的消息路由。
- 控制可拔插的服务化的功能和版本。
- 解析服务之间交互和通信的内容和格式。
- 通过组合服务、资源和消息处理器来统一编排业务需要的信息处理流程。
- 使用冗余来提供服务的备份能力。
1.2 从服务化到微服务
图1-7 微服务架构
1.3 微服务架构的核心要点和实现原理
1.3.1 微服务架构中职能团队划分
【康威定律】
1.3.2 微服务的去中心化治理
1.3.3 微服务的交互模式
1、读者容错模式(Tolerant Reader)
微服务中服务提供者和消费者之间如何对接口的改变进行容错。在服务消费者处理服务提供者返回的消息过程中,需要对服务返回的消息进行过滤,提取自己需要的内容,对多余或位置内容抛弃,不是硬生生的抛错。
推荐宽松的校验策略,只有无法识别信息,继续处理流程时,才能抛错。
2、消费者驱动契约模式
消费者驱动契约模式用来定义服务化中服务之间交互接口改变的最佳规则。
服务契约分为:提供者契约、消费者契约、消费者驱动契约,它从期望与约束的角度描述了服务提供者与消费者之间的联动关系。
- 提供者契约:以提供者为中心,消费者无条件遵守
- 消费者契约:对某个消费者的需求进行更为精确的描述,可以用来标识现有的提供者契约,也可以用来发现一个尚未明确的提供者契约。
- 消费者驱动的契约:代表服务提供者向其所有当前消费者承诺遵守的约束。一旦各消费者把具体的期望告知提供者,则提供者无论在什么时间和场景下,都不应该打破契约。
图1-10 服务之间的交互需要使用的三种服务契约
3、去数据共享模式
1.3.4 微服务的分解和组合模式
1、服务代理模式
根据业务的需求选择调用后端的某个服务,在返回给使用端前,可以对后端服务的输出进行加工,也可以直接把后端服务的返回结果返回给使用端。
图1-12 服务代理模式
![]()
【典型案例:平滑的系统迁移】
- 在新老系统上双写。
- 迁移双写之前的历史遗留数据。
- 将读请求切换到新系统『服务代理模式』。
- 下调双写逻辑,只写新系统。
第 3 步,一般只会对读请求切换设计一个开关,开关打开时查询新系统,开关关闭时查询老系统。
图1-13 迁移案例中开关的逻辑
2、聚合服务模式模式
最常用的服务组合模式,根据业务流程处理的需要,以一定的顺序调用依赖的多个微服务,对依赖的微服务返回的数据进行组合、加工和转换,返回给使用方。
图1-14 服务聚合模式的架构
![]()
3、服务串联模式
类似工作流,最前面的服务1负责接收请求和相应使用方,串联服务后再与服务1交互,随后服务 1与服务2交互,最后,从服务2产生的结果经过服务1和串联服务逐个处理后返回给使用方。
图1-17 服务串联模式的架构
● 使用 RESTful 风格的远程调用实现;
● 采用同步调用模式,在串联服务没有完成返回之前,所有服务都会阻塞和等待;
● 一个请求会占用一个线程来处理;
● 不建议层级太多,如果能用服务聚合模式,优先使用服务聚合模式;
● 串联链路上增加节点,只要不是在正后方,串联服务无感知
图1-18 在串联服务中调用链的最后增加无感知的架构
图1-19 服务串联模式案例的架构图
4、服务分支模式
● 服务分支模式是服务代理模式、服务聚合模式和服务串联模式相结合的产物。
● 分支服务可以拥有自己的存储,调用多个后端的服务或者服务串联链,然后将结果进行组合处理再返回给客户端。
● 分支服务也可以使用代理模式,简单地调用后端的某个服务或者服务链,然后将返回的数值直接返回给使用方。
图1-20 服务分支模式的架构图
调用链上有多个层次重复调用某基础服务,导致基础服务挂掉时影响的流量有累加效果:
假设基础服务资源池中的机器个数为 i,一次挂掉的机器个数为 j,一个调用链中调用 x 次基础服务,那么正确处理的流量的计算公式为:成功率 = ((i-j) / i) x方
分支模式放大了服务的依赖关系,在现实设计中尽量保持服务调用级别的简单,在使用服务组合和服务代理模式时,不要使用服务串联模式和服务分支模式,以保持服务依赖关系的清晰明了。
5、服务异步消息模式
核心的系统服务使用同步调用,核心链路以外的服务可以使用异步消息队列进行异步化。
图1-20 服务异步消息模式的架构
5、服务共享数据模式
其实是反模式
图1-25 服务共享数据模式
【在下面两种场景中,仍然需要数据共享模式】:
一、单元化架构
对性能要求高。
图1-26 单元化架构的示意图
![]()
二、遗留的整体服务
在重构微服务的过程中,发现单体服务依赖的数据库表耦合在一起,对其拆分需要进行反规范化的处理,可能造成数据一致性问题。
1.3.4 微服务的容错模式
1、舱壁隔离模式
1)微服务容器分组
将微服务的每个节点服务池分为三组:
- 准生产环境;
- 灰度环境;
- 生产环境。
图1-27 服务分组
2)线程池隔离
图1-28 线程池隔离
2、熔断模式
- 用电路保险开关来比如熔断模式。
- 对微服务系统,当服务的输入负载迅速增加,如果没有有效的措施对负载进行熔断,则会使服务迅速压垮。
图1-29 熔断模式
3、限流模式
【有如下几种主流的方法实现限流】:
1)计数器
2)令牌桶
图1-32 令牌桶结构
3)信号量
类似于生活中的漏洞
示例:
public class SemaphoreExample {
private ExecutorService exec = Executors.newCachedThreadPool();
public static void main(String[] args) {
final Semaphore sem = new Semaphore(5);
for (int index = 0; index < 20; index++) {
Runnable run = new Runnable() {
public void run() {
try {
// 获得许可
sem.acquire();
// 同时只有 5 个请求可以到达这里
Thread.sleep((long) (Math.random()));
// 释放许可
sem.release()
System.out.println("剩余许可:" + sem.availablePermits());
} catch(InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(run);
}
exec.shutdown();
}
}
4、实现转移模式
【如果微服务架构发生了熔断和限流,则该如何处理被拒绝的请求】
- 采用【快速失败】,直接返回使用方错误
- 是否有【备份】,如果有,则迅速切换到备份服务
- 【failover策略】,采用重试的方法来解决,这种方法要求服务提供者的服务实现幂等性
1.4 Java 平台微服务架构的项目组织形式
1.4.1 微服务项目的依赖关系
在微服务领域,jar 包被分为:
- 一方库:本服务在 JVM 进程内依赖的 Jar 包。
- 二房库:在服务外通过网络通信或 RPC 调用的服务的 Jar 包。
- 三方库:所依赖的其他公司或者组织提
1.4.2 微服务项目的层级结构
Java 微服务项目一般分为:服务导出层、接口层、逻辑实现层
- 服务导出层 : 最后会打包成一个 War 包,包含服务的实现 Jar 包、接口 Jar 包,以及 Web 项目导出 RPC 服务所需的配置文件等。
- 服务接口层 : 包含业务接口、依赖的 DTO 及需要的枚举类等,最后打包成 Jar 包,发布到 Maven 服务器,也包含在服务导出层的 War 包中。
- 服务实现层 : 包含业务逻辑实现类、依赖的第三方服务的包装类,以及下层数据库访问的 DAO 类,最后打包成 Jar。
【服务实现层的架构图】
【服务实现层的反模式架构图】
1.4.3 微服务项目的持续发布
微服务项目需要实现自动化的CI/CD,包括:
- 代码管理
- 自动编译
- 发布 OA
- 自动化测试
- 性能测试
- 准生产部署和测试
- 生成环境发布
1.5 服务化管理和治理框架的技术选型
1.5.1 RPC
1、JDK RMI
Java 到 Java 的分布式调用框架,一个 Java 进程内的服务可以调用其他 Java 进程内的服务,使用 JDK 内置的序列化和反序列化协议。
- 序列化协议:JDK自带的专用序列化协议,不能跨语言
- 网络传输协议:底层网络协议
2、Hessian & Burlap
- 序列化协议:Hessian 序列化为二进制协议;Burlap 序列化为 XML 数据
- 网络传输协议:HTTP 协议
3、Spring HTTP Invoker
- 序列化协议:Hessian 序列化为二进制协议;Burlap 序列化为 XML 数据
- 网络传输协议:HTTP 协议
1.5.2
1、Dubbo
- 提供高性能、透明化的 RPC 远程服务调用,还提供了基本的服务监控、服务质量和服务调度
- 支持多种序列化协议和通信编码协议,默认使用 Dubbo 协议传输 Hessian 序列化的数据
- 使用 ZooKeeper 作为注册中心来注册和发现服务
- 通过客户端负载均衡来路由请求,负载均衡算法包括:随机、轮询、最少活跃调用数、一致性哈希等。
2、HSF
High Speed Framework
3、Thrift
- 采用中间的接口描述语言定义并创建服务
- 支持跨语言服务开发和调用,并包含中间的接口描述语言与代码生成和转换工具
- 采用二进制序列化传输数据
4、AXIS
源于 IBM "SOAP4J",使用 SOAP 协议
5、Mule ESB
基于 Java 语言的企业服务总线产品,可以把多个异构系统通过总线模式集成在一起
1.5.3 微服务
1、Spring Boot
【图1-37 JEE时代,应用包含在容器内的架构图】
Spring Boot 相反,它将容器嵌入自启动的 Jar 包中,在 Spring Boot 应用启动时,内部启动嵌入的容器
【Spring Boot 的容器包含在应用内的架构图】Spring Boot 这种设计在微服务架构下有如下明显有点:
- 可以创建独立、自启动的应用程序
- 无需构建 War 包并发布到容器中,构建和维护 War 包、容器的配置和管理也需要成本
- 通过 Maven 的定制化标签,可以快速构建 Spring Boot 的应用程序
- 可以最大化的自动化配置 Spring,而无需人工配置各项参数
- 提供了产品话特点,如:性能分析、健康检查和外部化配置
- 无 XML 配置
- 是 Spring Cloud 构建微服务架构的重要基础
2、Netflix
提供服务发现、断路器和监控、智能路由、客户端负载均衡、易用的 REST 客户端等服务化必须的功能
3、Spring cloud Netflix
- 服务发现组件 Eureka
- 容错性组件 Hystrix
- 智能路由组件 Zuul
- 客户端负载均衡组件 Ribbon
【图 1-39 Spring Cloud Netfix 架构图】
【Netflix 交互流程】
- 服务在 Eureka 服务器实例注册
- Zuul 作为一个特殊的服务在 Eureka 上注册并发现服务
- Zuul 作为网关,将发现的服务导出给客户端
- RestTemplate 和 FeignClient 使用简单的服务调用方法调用服务1、服务2
【Netflix 特点】
- 服务在 Eureka 注册,由 Spring 管理的 Bean 来发现和调用
- 通过配置的方式可以启动嵌入式的 Eureka 服务器
- Feign 客户端通过声明的方式即可导入服务代理
- Zuul 使用 Ribbon 服务实现客户端负载均衡
- 通过声明的方式即可插入 Hystrix 的客户端
- 通过配置的方式即可启动 Hystrix 面板服务器
- 在 Spring 环境中可以直接配置 Netflix 组件
- Zuul 可以自动注册过滤器和路由器,形成一个反向代理服务器
- Hystrix 面板可以对服务的状态进行监控,并提供容错机制