熔断器Hystrix
1.雪崩效应
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不 可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者,红色代表不可用。随着时间的推移,A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。
雪崩产生的过程 :
- 正常情况 , 一个请求进入C , C会从线程池中申请一个线程处理 , 然后请求B, 同时线程等待 ; B服务收到请求同样申请线程然后请求A , A处理完成返程结果并归还释放A自身的线程 , 然后BC依次完成响应并归/释放还线程
- A故障后 , B的线程请求A后 , 迟迟未得响应 , 线程阻塞等待
- C继续接收大量请求并传给B, 导致B大量阻塞线程等待 , 直到线程资源耗尽 , 无法接收新的请求 .
- B服务故障,故障继续蔓延 , C的线程资源耗尽后 , 一个请求再也不能完成 . 整个微服务宕机
导致雪崩产生的原因 :
-
服务提供者不可用
-
硬件故障 ; 服务器主机死机 , 或网络硬件故障导致服务提供者无法及时处理和相应
-
程序故障 ; 缓存击穿 , 缓存应用重启或故障导致所有缓存被清空 , 或者大量缓存同时过期 , 导致大量请求直击后端(文件或数据库) , 造成服务提供者超负荷运行导致瘫痪 ; 高并发请求 , 在某些场景下 , 例如秒杀和大促销之前 , 如果没有做好应对措施 , 用户的大量请求也会导致服务故障
-
-
不合理的流量激增
-
用户重试 ; 用户忍不了界面上的一直加载或等待 , 频繁刷新页面或提交表单 , 这是在秒杀场景下的常规操作
-
代码逻辑重试 , 在消费者服务中存在大量不合理的重试逻辑 , 比如各种异常的重试机制等
-
-
服务消费者不可用
- 大量的等待线程占用系统资源 , 一旦资源被耗尽 , 消费者这边也会发生连锁反应 , 然后会导致故障向下蔓延
雪崩的应对策略
-
硬件上升级
-
流量控制
-
网关限流 , 例如nginx等 , 防止大量请求进入系统
-
用户交流限流 , 改进用户等待页面的效果 , 提高用户等待时长 , 以及对提交按钮限制点击频率
-
-
改进缓存模式
-
缓存预加载 ; 对集中添加并且过期时间一致的缓存 , 适当的随机分配一些过期时长 , 防止集中过期
-
服务扩容
-
通过软件对服务监控 , 到达上限自动扩容
-
在特定场景下提前增加服务器
-
-
服务降级
-
对调用服务提供者的线程进行隔离 , 单独开辟出一个线程池 . 即使这个线程池全部占用了 , 对其他请求的服务有限 ; 例如 , 一个商品页面 , 要展示商品信息 , 和评论信息 , 及购买记录等 , 那对每个服务的线程池都单独开辟 , 这样即使获取不到评论信息 , 那不耽误用户正常浏览商品的一些内容
-
对依赖服务进行分类优化 ; 强依赖服务(必须请求上级并获得结果) , 强制中断该业务 , 引导重试或返回错误 , 弱依赖服务(可以不获取上级结果 , 不影响整体业务) , 跳过故障服务 , 或做个标识后续执行补救措
-
对不可用的服务快速调用失败 . 即断路器降级方法 , 释放线程资源 , 确保服务稳定
-
2.熔断器(CircuitBreaker)
熔断器的原理很简单,如同电力过载保护器。它可以实现快速失败,如果它在一段时间内侦测到许多类 似的错误,会强迫其以后的多个调用快速失败,不再访问远程服务器,从而防止应用程序不断地尝试执 行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的 超时产生。熔断器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试 调用操作。
熔断器模式就像是那些容易导致错误的操作的一种代理。这种代理能够记录最近调用发生错误的次数, 然后决定使用允许操作继续,或者立即返回错误。
熔断器开关相互转换的逻辑如下图:
熔断器就是保护服务高可用的最后一道防线。
3.Hystrix特性
3.1.断路器机制
断路器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.
3.2.Fallback
Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.
3.3.资源隔离
在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池. 例如调用产品服务的Command放入A线程池, 调用账户服务的Command放入B线程池. 这样做的主要优点是运行环境被隔离开了. 这样就算调用服务的代码存在bug或者由于其他原因导致自己所在线程池被耗尽时, 不会对系统的其他服务造成影响. 但是带来的代价就是维护多个线程池会对系统带来额外的性能开销. 如果是对性能有严格要求而且确信自己调用服务的客户端代码不会出问题的话, 可以使用Hystrix的信号模式(Semaphores)来隔离资源.
4.Feign Hystrix
因为熔断只是作用在服务调用这一端,因此我们只需要改动消费端工程可以。因为Feign中已经依赖了 Hystrix所以在maven配置上不用做任何改动。
4.1 开启Hystrix
在 customer-consumer
模块的 application.yml
中增加以下内容,来开启Hystrix:
#开启hystrix
feign:
hystrix:
enabled: true
4.2 创建回调类
创建一个 CustomerClient
接口的实现类,作为熔断实现类。为了区分是否成功返回结果,我们将 getCustomerById
方法的返回值改为 CrmResult
对象,CrmResut
类中包含了三个属性: code
、message
和data
。使用code
的不同来表示是否成功调用服务,成功返回200,如果不是 200代表处理失败。
@Service
public class MyServiceImpl implements MyService {
@Override
public CrmResult getCrm(Integer id) {
return new CrmResult(500,"当前服务不可用,请稍后再试......",null);
}
}
4.3 在接口类添加fallback属性
@FeignClient(name = "CUSTOMER-SERVICE",fallback = MyServiceImpl.class)
public interface MyService {
//接口要与服务提供方一致
@RequestMapping("custInfo/{id}")
CrmResult getCrm(@PathVariable("id") Integer id);
}
4.4 测试
正常启动 eureka-server
、customer-provider
和 customer-consumer
工程,在浏览器中输入访问接口,可以正常访问,增加熔断器后不影响正常访问。 我们将服务端 customer-provider
关闭,再次访问看到如下结果:
说明熔断器已经生效了。
集中配置组件SpringCloudConfig
随着线上项目变的日益庞大,每个项目都散落着各种配置文件,如果采用分布式的开发模式,需要的配置文件随着服务增加而不断增多。某一个基础服务信息变更,都会引起一系列的更新和重启,运维苦不堪言也容易出错。配置中心便是解决此类问题的。
在我们了解spring cloud config
之前,我可以想想一个配置中心提供的核心功能应该有什么?
- 提供服务端和客户端支持
- 集中管理各环境的配置文件
- 配置文件修改之后,可以快速的生效
- 可以进行版本管理
- 支持大的并发查询
- 支持各种语言
Spring Cloud Config
可以完美的支持以上所有的需求。
Spring Cloud Config
项目是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分, server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client通过接口获取数据、并 依据此数据初始化自己的应用。Spring cloud使用git或svn存放配置文件,默认情况下使用git。
1.将配置文件放到git
我们需要一个git仓库来保存配置文件,可以将配置文件放到github或者gitee(码云),在国内访问码云的 速度会更快所有我们以码云为例来演示。
我们在码云上创建一个仓库,为了模拟生成环境,我们将 provider
和 consumer
配置文件重新命名上传到gitee上去:
// provider开发环境
provider-dev.yml
// consumer开发环境
consumer-dev.yml
可以直接在网页操作,上传成功后会有下面两个文件:
2.配置服务端
我们需要创建一个 config-center
模块,用来管理配置文件,此工程可以实现从git仓库中查询配置文件的功能。
1.添加依赖
<!-- 服务端配置中心起步依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
2.创建启动类
启动类添加 @EnableConfigServer
,激活对配置中心的支持:
@SpringBootApplication
@EnableConfigServer //启动配置中心
public class ConfigCenterRunner {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterRunner.class,args);
}
}
3.application.yml
#端口
server:
port: 9900
#应用名
spring:
application:
name: config-center
#配置中心地址
cloud:
config:
server:
git:
# 配置git仓库的地址
uri: https://gitee.com/lbb_luckly/cloud-config.git
# git仓库地址下的相对地址 放在哪个文件夹下了,对应值是数组,如果是多个文件夹,则用逗号隔开 [路径1,路径2]
search-paths: [cloud-conter,config]
#如果仓库是私有的,则需要提供用户名和密码
# git仓库的账号
username: lbb_luckly@163.com
# git仓库的密码
password: l68709110712135
4.测试
访问 http://localhost:9900/provider/dev
来测试是否生效,如果看到浏览器返回文件信息,则成功:
访问: http://localhost:9900/provider-dev.yml
返回结果:
Spring Cloud Config
也提供本地存储配置的方式。我们只需要设置属性 spring.profiles.active=native
,Config Server
会默认从应用的 src/main/resource
目录 下检索配置文件。也可以通过spring.cloud.config.server.native.searchLocations=file:E:/properties/
属性来指定配置文件的位置。虽然Spring Cloud Config
提供了这样的功能,但是为了支持更好的管理内容和版本控制的功能,还是推荐使用git的方式。
仓库中的配置文件会被转换成web接口,访问可以参照以下的规则:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
以config-dev.yml
为例,它的 application
是 config
, profile
是 dev
, label
对应远程仓库的分支名称,例如 master
, client
会根据填写的参数来选择读取对应的配置。
3.配置客户端
通过上面的学习我们可以了解到使用配置中心可以读取到保存到git仓库中的配置文件内容,所以我们可以使用git来管理工程中的配置文件。
1.我们把 customer-provider
和 customer-consumer
的配置文件都放到git仓库中,并且命名 为 {application}-{profile}.yml
形式。
customer-provider-dev.yml
customer-consumer-dev.yml
2.在 customer-provider
和 customer-provider
工程的pom文件中增加如下内容:
<!-- 客户端配置中心起步依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
3.删除两个工程中 application.yml
文件,创建 bootstrap.yml
文件
#配置中心地址
spring:
cloud:
config:
#配置文件名称
name: provider
#配置文件环境
profile: dev
#指定分支
label: master
#配置中心地址
uri: http://localhost:9900/
启动工程测试即可
4.配置中心服务化
在前的介绍中,客户端都是直接调用配置中心的server端来获取配置文件信息。这样就存在了一个问题,客户端和服务端的耦合性太高,如果server端要做集群,客户端只能通过原始的方式来路由, server端改变IP地址的时候,客户端也需要修改配置,不符合springcloud服务治理的理念。 springcloud提供了这样的解决方案,我们只需要将server端当做一个服务注册到eureka中,client端去 eureka中去获取配置中心server端的服务既可。
配置中心服务改造
1.添加Eureka的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.配置文件添加
#eureka
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka/
3.启动类 启动类添加 @EnableDiscoveryClient
激活对注册中心的支持
@SpringBootApplication
@EnableConfigServer //启动配置中心
@EnableDiscoveryClient //激活对注册中心的支持
public class ConfigCenterRunner {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterRunner.class,args);
}
}
客户端改造
客户端需要改造 bootstrap.yml
文件:
#配置中心地址
spring:
cloud:
config:
#配置文件名称
name: provider
#配置文件环境
profile: dev
#指定分支
label: master
#配置中心地址
#uri: http://localhost:9900/
#通过注册中心获取配置中心信息
#启动服务发现
discovery:
enabled: true
#指定服务id
service-id: config-center
#指定eureka信息
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
主要是去掉了 spring.cloud.config.uri
直接指向server端地址的配置,增加了最后的三个配置:
spring.cloud.config.discovery.enabled :开启Config服务发现支持
spring.cloud.config.discovery.service-id :指定server端的name,也就是server端 spring.application.name 的值
eureka.client.service-url.defaultZone :指向注册中心的地址
这三个配置都需要放到 bootstrap.yml
的配置中,这样服务会先去eureka
找配置中心,再从配置信息找配置文件。
5.配置中心高可用
为了模拟生产集群环境,我们在配置中心工程中创建一个新的配置文件 application-002.yml
改动 server端的端口为9911,再启动一个server端来做服务的负载,提供高可用的server端支持。
链接:SpringCloud之消息总线组件及微服务网关