前页
随着 DevOps 和以 Docker 为主的容器技术的发展,云原生应用架构和微服 务变得流行起来。 云原生包含的内容很多,如 DevOps、持续交付、微服务、敏捷等
架构的演进从单体到 SOA,再到微服务架构。 微服务架构是 SOA 思想的一种具体实践。
微服务架构将早期的单体应用从数据存储开始垂直拆分成多个不同的服务,每个服务都能独立部署,独立维护,独立扩展.
服务与服务间通过诸如 RESTful API的方式互相调用。只有业务复杂到一定程度的时候才适合采用微服务架构。
单体应用架构,即传统的Web应用程序中分层架构,按照调用顺序,从上到下为表示层、业务层、数据 访问( DAO)层、 DB 层。
表示层负责用户体验;业务层负责业务逻辑;数据访问层负责 DB 层的数据存取, 实现增删改查的功能。 业务层定义了应用的业务逻辑,是整个应用的核心。
在单体应用中,所有这些模块都集成在一起,或称为巨石型应用架构。
面向服务的架构(SOA)是 Gartner 于 20 世纪 90 年代中期提出的。
SOA 的核心主体是服务,其目标是通过服务的流程化来实现业务的灵活性。 服务就像 一堆“元器件”,这些元器件通过封装形成标准服务,它们有相同的接口和语义表达规则。
SOA 治理是将 SOA 的一堆元 器件进行有效组装
完整的 SOA 架构由五大部分组成:
微服务的定义可以概括如下:
组成
优点
挑战:
常见的微服务架构方案有四种,分别是 ZeroC IceGrid、 基于消息队列 、 Docker Swarm 和 Spring Cloud。 下面分别介绍这四种方案。
ZeroC IceGrid 是基于 RPC 框架 Ice 发展而来的一种微服务架构, Ice 不仅仅是一个 RPC 框架,它还为网络应用程序提供了一些补充服务。 Ice 是一个全面的 RPC 框架, 支持 C++、 C#、 Java、 JavaScript、 Python 等语言。 IceGrid 具有定位、部署和管理 Ice 服务器的功能, 具有良好的性能与分布式能力。
Ice 的 DNS。 DNS 用于将域名信息映射到具体的 IP 地址 ,通过域名得到该域名对应的 IP 地址的过程叫做域名解析。
在微服务架构的定义中讲到,各个微服务之间使用“轻量级”的通信机制。 所谓轻量级,是指通信协议与语言无关、 与平台无关。 微服务之间的通信方式有两种:同步和异步。同步方式有 RPC , REST 等;除了标准的基于同步通信方式的微服务架构外,
还有基于消息 队列异步方式通信的微服务架构。
在基于消息队列的微服务架构方式中,微服务之间采用发布消息与监听消息的方式来实现服务之间的交互
Swarm 项目是 Docker 公司发布的三剑客中的一员,用来提供容器集群服务 ,目的是更好地帮助用户管理多个 Docker Engine,方便用户使用。 通过把多个 Docker Engine 聚集 在一起,形成一个大的 Docker Engine,对外提供容器的集群服务。 同时这个集群对外提供 Swarm API,用户可以像使用 Docker Engine 一样使用 Docker 集群。
Swarm 集群中有两种角色的节点:
Manager: 负责集群的管理、集群状态的维持及将任务(Task)调度到工作节点上等。
Worker : 承载运行在 Swarm 集群中的容器实例,每个节点主动汇报其上运行的任 务并维持同步状态。
Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具,是一系列框架的集合,当添加这些工具库到应用后会增强应用的行为。 Spring Boot 秉持约定优于配置的思想,因此可以利用这些组件基本的默认行为来快速入门 ,并在需要的时候可以配置或扩展,以创建自定义解决方案。
Spring Cloud 利用 Spring Boot 的开发便利性,巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以基 于 Spring Boot 组件进行开发,做到一键启动和部署。
以下为 Spring Cloud 的核心功能 :
分布式/版本化配置
服务注册和发现
服务路由
服务和服务之间的调用
负载均衡
断路器
分布式消息传递
CNCF(云原生计算基金会)
CNCF 宪章中给出了云原生应用的三大特征,概括如下:
可以理解为云原生应用即面向云环境的软件架构,
基准代码。显式声明依赖关系。在环境中存储配置。把后端服务当作附加资源。 严格分离构建、发布和运行。无状态进程。 通过端口绑定提供服务。 通过进程模型进行扩展。快速启动和优雅终止。开发环境与线上环境等价。 日志作为事件流。管理进程。&&&& API 声明管理、认证和授权、 监控与告警。
Docker 是一个开源引擎,可以轻松地为任何应用创建一个轻量级的、 可移植的、自给自足的容器。Docker 让开发工程师可以将他们的应用和依赖封装到一个可移植的容器中。 Docker 根本的想法是创建软件程序可移植的轻量容器,让其可以在任何安装了 Docker 的机器上运 行, 而不用关心底层操作系统。
优势:
DevOps 是软件开发人员( Dev)和 IT 运维技术人员( Ops)之间的合作,目标是自动执行软件交付和基础架构更改流程,使得构建 、测试、发布软件能够更加地快捷和可靠。 它创造了一种文化和环境,可在其中快速、频繁且更可靠地构建、 测试和发布软件。
微服务将单体业务系统分解为多个可独立部署的服务。 这个服务通常只关注某项业务, 或者最小可提供业务价值的“原子”服务单元。
优势:
Spring Cloud 是一系列框架的有机集合,基于 Spring Boot 实现的云应用开发工具,为云原生应用 开发中的服务发现与注册、熔断机制 、 服务路由 、分布式配置中心 、消息总线、负载均衡 和链路监控等功能的实现提供了一种简单的开发方式。
Springboot Cloud特点:
Spring Cloud 提供了多种方式来促进云原生开发风格。 Spring Cloud 提供了一系列组件,可 以在分布式系统中直接使用,这些组件大大降低了分布式系统的搭建和开发难度。
组件大多数由 Spring Boot 提供, Spring Cloud 在此基础上添加了分布式系统的相关特性。 Spring Cloud 依赖于 Spring Cloud Context 和 Spring Cloud Commons 两个公共库,
Bootstrap 上下文 :
除了应用上下文配置( application.yml 或者 application.properties)之外, Spring Cloud 应用程序提供与 Bootstrap 上下文配置相关的应用属性。 Bootstrap 上下文对于主程序来说是一个父级上下文,它支持从外部资源中加载配置文件,和解密本地外部配置文件中 的属性。 Bootstrap 上下文和应用上下文将共享一个环境( Environment),这是所有 Spring 应用程序的外部属性来源。 一般来讲, Bootstrap 上下文中的属性优先级较高,所以它们不能被本地配置所覆盖。
Bootstrap 上下文使用与主程序不同的规则来加载外部配置。 bootstrap.yml 用于为 Bootstrap 上下文加载外部配置,区别于应用上下文的 application.yml 或者 application. properties
spring:
application:
name: my-application
cloud:
config:
uri: ${CONFIG_SERVER :http://localhost:8080}
2. 应用上下文层级
Spring 的上下文有一个特性 :子级上下文将从父级中继承属性源和配置文件。
如果通过 bootstrap.yml 来配置 Bootstrap 上下文,且在设定好 父级上下文的情况下, bootstrap.yml 中的属性会添加到子级的上下文。 它们的优先级低于 application.yml 和其他添加到子级中作为创建 Spring Boot 应用的属性源。
基于属性源的排序规则, Bootstrap 上下文中的属性优先,但是需要注意这些属性并不 包含任何来自 bootstrap.yml 的数据。 bootstrap.yml 中的属性具备非常低的优先级,因此可 以作为默认值。
将父级上下文设置为应用上下文来扩展上下文的层次结构。 Bootstrap 上下 文将会是最高级别上下文的父级。 每一个在层次结构中的上下文都有它自 己的 Bootstrap 属 性源(可能为空),来避免无意中将父级上下文中的属性传递到它的后代中。
3. 修改 Bootstrap 配置文件的位置
bootstrap.yml 的位置可以通过在配置属性中设置 spring.cloud.bootstrap.name (默认是 bootstrap)或者 spring.cloud.bootstrap. location 来修改。
4. 重载远程属性
通过 Bootstrap 上下文添加到应用程序的属性源通常是远程的,例如来自配置中心,通常本地的配置文件不能覆盖这些远程属性源。一般来说过,启动命令行参数的优先级高于远程配置,可以通过设定启动命令行参数的方式覆盖远程配置
应用程序的系统属性或者配置文件覆盖远程属性,那么远程属性源必须设 置为 spring.cloud.config.allowOverride=true (这个配置在本地设置不会生效)。 在远程属性 源中设定上述配置后,就可以通过更为细粒度的设置来控制远程属性是否能被重载。
5. 自定义 Bootstrap 配置
通常来说服务注册与发现包括两部分一个是服务器端,另一个是客户端。
Server 是 一个公共服务, 为 Client 提供服务注册和发现的功能,维护注册到自身的 Client 的相关信 息,同时提供接口给 Client 获取注册表中其他服务的信息,使得动态变化的 Client 能够进 行服务间的相互调用。
Client 将自己的服务信息通过一定的方式登记到 Server 上,并在正 常范围内维护自己信息一致性,方便其他服务发现自己,同时可以通过 Server 获取到自己 依赖的其他服务信息,完成服务调用。
Spring Cloud Netflix Eureka 是 Spring Cloud 提供用于服务发现和注册的基础组件,是 搭建 Spring Cloud 微服务架构的前提之一。 Eureka 作为一个开箱即用的基础组件,屏蔽了 底层 Server 和 Client 交互的细节,使得开发者能够将精力更多地放在业务逻辑上,加快微 服务架构的实施和项目的开发。
在 Netflix 中 , Eureka 是一个 RESTful 风格的服务注册与发现的基础服务组件。 Eureka 由两部分组成,
Eureka Client 会定 时将自己的信息注册到 Eureka Server 中,并从 Server 中发现其他服务。 Eureka Client 中内 置一个负载均衡器,用来进行基本的负载均衡。
通常来讲,一个 Eureka Server 也是一个 Eureka Client,它会尝试注册自己,所以需要至少一个注册中心 的 URL 来定位对等点 peer。 如果不提供这样一个注册端点,注册中心也能工作,但是会 在日志中打印无法向 peer 注册自己的信息。 在独立( Standalone) Eureka Server 的模式下, Eureka Server 一般会关闭作为客户端注册自己的行为。
Eureka Server 与 Eureka Client 之间的联系主要通过心跳的方式实现。 心跳( Heartbeat) 即 Eureka Client 定时向 Eureka Server 汇报本服务实例当前的状态,维护本服务实例在注册 表中租约的有效性。
启动 Eureka Server 后,应用会有一个主页面用来展示当前注册表中的服务实例信息并 同时暴露一些基于 HTTP 协议的端点在/eureka 路径下,这些端点将由 Eureka Client 用于注 册自 身、获取注册表信息以及发送心跳等。
首先对 AWS 上的区域(Regin )和可用区(Availability Zone )进行简单的介绍。
组件与行为 :
DiscoveryClient 是 Spring Cloud 中用来进行服务发现的顶级接 口,在 Netflix Eureka 或者 Consul 中都有相应的具体实现类 , 该接口提供的方法如下:
//DiscoveryClient. java
public interface DiscoveryClient {
//获取实现类的描述
String description() ;
//通过服务工d获取服务实例的信息
List getInstatces(String serviceid) ; //获取所有的服务实例Id List getServices() ;
EurekaClient 来 自于 com.netflix.discovery 包中,其默认实现为 com.netflix.discovery. DiscoveryClient,属于 eureka-client 的源代码, 它提供了 Eureka Client 注册到 Server 上、续 租、 下线以及获取 Server 中注册表信息等诸多 关键功能。 Spring Cloud 通过组合方式调用了Eureka 中的服务发现方法。
为了对 Eureka Client 的执行原理进行讲解, 首先需要对服务发现客户端 com.netflix. discover.DiscoveryClient 职能以及相关类进行讲解,它负责了与 Eureka Seever 交互的关键逻辑。
DiscoveryClient 职责
DiscoveryClient 是 Eureka Client 的核心类,包括与 Eureka Server 交互的关键逻辑,具 备了以下职能:
2. DiscoveryClient 类结构:
DiscoveryClient 继承了 LookupService 接口, LookupService 作用是发现活跃的服务实例,
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.netflix.discovery.shared;
import com.netflix.appinfo.InstanceInfo;
import java.util.List;
public interface LookupService {
Application getApplication(String var1);
Applications getApplications();
List getInstancesById(String var1);
InstanceInfo getNextServerFromEureka(String var1, boolean var2);
}
Application 持有服务实例信息列表 ,它可以理解成同一个服务的集群信息,这些服务 实例都挂在同一个服务名 appName 下。 InstanceInfo 代表一个服务实例信息。 Application 部分代码如下:
public class Applications {
private static final String STATUS_DELIMITER = "_";
private String appsHashCode;
private Long versionDelta;
@XStreamImplicit
private final AbstractQueue applications;
private final Map appNameApplicationMap;
private final Map virtualHostNameAppMap;
private final Map secureVirtualHostNameAppMap;
为了保证原子性操作, Application 中对 Instancelnfo 的操作都是同步操作。
Applications 是注册表中所有服务实例信息的集合, 里面的操作大多也是同步操作。 EurekaC!ient 继承了 LookupService 接口,为 DiscoveryClient 提供了一个上层接 口,
EurekaCient 在 LookupService 的基础上扩充了更多的接 口 ,提供了更丰富的获取服务 实例的方式 , 主要有:
一般来讲,在 Eureka 客户端,除了第一次拉取注册表信息,之后的信息拉取都会尝试 只进行增量拉取(第一次拉取注册表信息为全量拉取), 下面将分别介绍拉取注册表信息的 两种实现,全量拉取注册表信息 DiscoveryCli en t#getAn d S toreFu I !Regis try 和增量式拉取注 册表信息 DiscoveryC!ient#getAndUpdateDelta。
在拉取完 Eureka Server 中的 注册表信息并将其缓存在本地后 , Eureka Client 将向 Eureka Server 注册自身服务实例元数据,主要逻辑位于 Discovery#register 方法中。
Eureka Client 会将自身服务实例元数据(封装在 Instancelnfo 中)发送到 Eureka Server 中请求服务注册,当 Eureka Server 返回 204 状态码时,说明服务注册成功。
Eureka Client 通过定时发送心跳的方式与 Eureka Server 进行通信,维持自己在 Server 注册表上的租约。 同时 Eureka Server 注册表中 的服务实例信息是动态变化的,
为了保持 Eureka Client 与 Eureka Server 的注册表信息的一致性, Eureka Client 需要定时向 Eureka Server 拉取注册表信息并更新本地缓存。
为了监控 Eureka Client 应用信息和状态的变化, Eureka Client设置了一个按需注册定时器,定时检查应用信息或者状态的变化, 并在发生变化时向 Eureka Server 重新注册,避免注册表中的 本服务实例信息不可用。
在 DiscoveryClient#initScheduledTasks 方法中初始化了三个定时器任务,
1. 缓存刷新定时任务与发送心跳定时任务。
2. 按需注册定时任务:按需注册定时任务的作用是当 Eureka Client 中的 Instancelnfo 或者 status 发生变化时, 重新向 Eureka Server 发起注册请求,更新注册表中的服务实例信息,保证 Eureka Server 注 册表中服务实例信息有效和可用。
应用服务在关闭的时候, Eureka Client 会主动向 Eureka Server 注销自身在注册表中的信息。
Hystrix 是 Netflix 的一个开源项目,它能够在依赖服务失效的情况下,通过隔离系统依 赖服务的方式,防止服务级联失败;同时 Hystrix 提供失败回滚机制,使系统能够更快地从 异常中恢复。
spring-cloud-netflix-hystrix 对 Hystrix 进行封装和适配,使 Hystrix 能够更好地运行于 Spring Cloud 环境中,为微服务间的调用提供强有力的容错机制。
Hystrix 具有如下的功能:
服务雪崩效应是一种因服务提供者的不 可用导致服务调用者的不可用,并将不可用 逐渐放大的过程
其中, A 作为基础的服务提供者,为 B 和 C 提供服务, D、 E、 F 是 B 和 C 服务的调用 者,当 A 不可用时,将引起 B 和 C 的不可用,并将这种不可用放大到 D、 E、 F,从而可能 导致整个系统的不可用,服务雪崩的产生可能导致分布式系统的瘫痪。
服务雪崩效应的产生一般有三个流程;
原因:
服务调用者因为服务提供者的不可用导致了自身的崩溃。 当服务调用者使用同 步调用的时候,大量的等待线程将会耗尽线程池中的资源, 最终导致服务调用者的若机, 无法响应用户的请求,服务雪崩效应就此发生了。
在分布式系统中,不同服务之间的调用非常常见,当服务提供者不可用时就很有可能 发生服务雪崩效应,导致整个系统的不可用。 所以为了预防这种情况的发生,可以使用断路器模式进行预防(类比电路中的断路器,在电路过大的时候自动断开,防止电线过热损害 整条电路)。
断路器将远程方法调用包装到一个断路器对象中,用于监控方法调用过程的失败。 一 旦该方法调用发生的失败次数在一段时间内达到一定的阀值,那么这个断路器将会跳闸,
在接下来时间里再次调用该方法将会被断路器直接返回异常,而不再发生该方法的真实调 用。 这样就避免了服务调用者在服务提供者不可用时发送请求,从而减少线程池中资源的 消耗,保护了服务调用者。
虽然断路器在打开的时候避免了被保护方法的无效调用,但是当情况 恢复正常时,需要外部干预来重置断路器,使得方法调用可以重新发生。 所以合理的断路器应该具备一定的开关转化逻辑,它需要一个机制来控制它的重新闭合
过重置时间来决定断路器的重新闭合
断路器的打开能保证服务调用者在调用异常服务时,快速返回结果,避免大量的同步等待,减少服务调用者的资源消耗。 并且断路器能在打开一段时间后继续侦测请求执行结 果,判断断路器是否能关闭,恢复服务的正常调用。
在 Hystrix 中,也采用了舱壁模式,将系统中的服务中供者隔离起来, 一个服务提供者延迟升高或者失败,并不会导致整个系统的失败,同时也 能够控制调用这些服务的并发度。(在货船中,为了防止漏水和火灾的扩散,一般会将货仓进行分割)
在 Hystrix 中,当服务间调用发生问题时,它将采用备用的 Fallback 方法代替主方法 执行并返回结果,对失败服务进行了服务降级。 当调用服务失败次数在一段时间内超过了 断路器的阀值时,断路器将打开,不再进行真正的方法调用,而是快速失败,直接执行 Fall back 逻辑,服务降级,减少服务调用者的资源消耗,保护服务调用者中的线程资源
1,线程与线程池 :Hystrix 通过将调用服务线程与服务访问的执行线程分隔开来,调用线程能够空出来去 做其他的工作而不至于因为服务调用的执行阻塞过长时间。 在 Hystrix 中,将使用独立的线程池对应每一个服务提供者,用于隔离和限制这些服务。
信号量 : Hystrix 还可以通过信号量(计数器)来限制单个服务提供者的并发量。 如果通过信号量来控制系统负载,将不再允许设置超时控制和异步化调用,这就表示在服 务提供者出现高延迟时,其调用线程将会被阻塞,直至服务提供者的网络请求超时。 如果 对服务提供者的稳定性有足够的信心,可以通过信号量来控制系统的负载。
简单的流程如下 :
@HystrixCommand 注解
在基础应用中我们使用@HystrixCommand 注解来包装需要保护的远程调用方法。 首先 查看该注解的相关属性
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {
//默认为被注解方法的运行时类名
String groupKey() default "";
// hystrix的命令键,用于区分不同的注解方法 ,默认为注解方法的名称
String commandKey() default "";
String threadPoolKey() default "";
//指定Fallback方法名, Fallback方法也可以被HystrixCommand注解
String fallbackMethod() default "";
//自定义命令的相关配置
HystrixProperty[] commandProperties() default {};
//自定义线程池的相关配置
HystrixProperty[] threadPoolProperties() default {};
//定义忽略哪些异常
Class extends Throwable>[] ignoreExceptions() default {};
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
//默认的 fallback
HystrixException[] raiseHystrixExceptions() default {};
String defaultFallback() default "";
@HystrixCommand 的配置,仅需要关注 fallbackMethod 方法,当然如果 对命令和线程池有特定需要,可以进行额外的配置。
@Hystrix Collapser 注解用于请求合并操作,但是需 要与@HystrixCommand 结合使用, 批量操作的方法必须被@HystrixCommand 注解
异步与异步回调执行命令:Hystrix 除了同步执行命令 ,还可以异步以及异步回调执行命令。 异步执行命令需要定 义函数的返回方式为 Future。
继承 HystrixCommand:除了通过注解的方式声 明 Hystrix 包装函数,还可 以通过继承 HystrixCommand 以及 HystrixObservableCommand 抽象类接口来包装需要保护的远程调用函数。
继承 HystrixObservableCommand :以继承 HystrixObservableCommand 来构建以异步回调执 行命令的 Commando
package com.liruilong.hystrix;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixObservableCommand;
import org.apache.logging.log4j.CloseableThreadContext;
import rx.Observable;
import sun.security.jca.GetInstance;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/3/21 22:58
*/
public class CustorrHystrixObservableCommand extends HystrixObservableCommand {
protected CustorrHystrixObservableCommand(HystrixCommandGroupKey group) {
super(group);
}
protected CustorrHystrixObservableCommand(Setter setter) {
super(setter);
}
@Override
protected Observable construct() {
return null;
}
@Override
protected String getFallbackMethodName() {
return super.getFallbackMethodName();
}
}
请求合并:
Hystrix 多个请求被合并为一个请求进行一次性处理,可以有效减少网络通信和线程池资源。 请求合并之后 一个请求原本可能在 6 毫秒之内能够结 束, 现在必须等待请求合并周期后( I 0 毫秒)才能发送请求,增加了请求的时间 ( 16 毫 秒)。 但是请求合并在处理高并发和高延迟命令上效果极佳。
它提供两种方式进行请求合并 :
Hystrix 为 Spring Cloud 中微服务间的相互调用提供了强大的容错保护。 它通过将服务 调用者和服务提供者隔离的方式,在服务提供者失效的情况下,保护服务调用者的线程资 源,保证系统的整体稳定性,防止服务雪崩效应的发生。