通过前面的讲述,我们已经基本解决了服务注册与消费的问题,理想情况下,如果服务永远不会发生错误,那自然皆大欢喜,但在复杂的实际环境中,显然是不可能的,更何况在微服务的环境之下,存在数十个甚至数百个服务,服务之间还存在着更复杂的相互调用关系,一个请求需要调用多个服务是非常常见的,比如存在一条A->B->C的服务调用链,由于网络等原因影响,如果B 服务或者 C 服务不能及时响应,A 服务将处于阻塞状态,直到 B 服务 C 服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的**“雪崩”**效应。
Hystrix正是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性、容错性与局部应用的弹性,是一个实现了超时机制和断路器模式的工具类库。
1.使用 Hystrix 预防服务雪崩
服务降级(Fallback)
对于查询操作,我们可以实现一个 fallback 方法,当请求后端服务出现异常的时候,可以使用 fallback 方法返回的值。fallback 方法的返回值一般是设置的默认值或者来自缓存。
资源隔离
在 Hystrix 中,主要通过线程池来实现资源隔离。通常在使用的时候我们会根据调用的远程服务划分出多个线程池。例如调用产品服务的 Command 放入 A 线程池,调用账户服务的 Command 放入 B 线程池。这样做的主要优点是运行环境被隔离开了。这样就算调用服务的代码存在 bug 或者由于其他原因导致自己所在线程池被耗尽时,不会对系统的其他服务造成影响。
Hystrix 中除了使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销要远比线程池的开销小得多,但是它不能设置超时和实现异步访问。
断路器模式
当 Hystrix Command 请求后端服务失败数量超过一定阈值,断路器会切换到开路状态 (Open)。这时所有请求会直接失败而不会发送到后端服务。
断路器保持在开路状态一段时间后 (默认 5 秒),自动切换到半开路状态 (HALF-OPEN)。这时会判断下一次请求的返回情况,如果请求成功,断路器切回闭路状态 (CLOSED),否则重新切换到开路状态 (OPEN)。
在某一个服务单元发生故障时,通过断路器的故障监控,向调用方返回一个服务预期的,可处理的备选响应,而不是长时间等待或排除无法处理的异常,保证了调用方的线程不会被长时间,不必要的占用
服务熔断
服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
服务降级
服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。
2.Feign中使用Hystrix
Feign在整合到Spring Cloud时已经自带了hystrix模块,所以pom.xml中不需要额外引入feign依赖。只需要在配置文件中开启即可。
配置文件
在原来的 application.yml 配置的基础上修改
spring:
application:
name: hystrix
server:
port: 7006
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://localhost:7000/eureka/
feign:
hystrix:
enabled: true
创建回调类
注意,必须要实现对应的接口
@Component
public class ProductServiceHystrix implements ProductService {
@Override
public Product selectProductById(Integer id) {
return new Product(id, "不存在", 1, 0D);
}
}
添加 fallback 属性
在 @FeignClient 中添加指定 fallback 类,在服务熔断的时候返回 fallback 类中的内容
@FeignClient(value = "eureka-provider",fallback = ProductServiceHystrix.class)
public interface ProductService {
/**
* 根据主键查询商品
* @param id
* @return
*/
@GetMapping("/product/{id}")
Product selectProductById(@PathVariable("id") Integer id);
}
3.Hystrix 监控
Hystrix 除了可以实现服务容错之外,还提供了近乎实时的监控功能,将服务执行结果和运行指标,请求数量成功数量等等这些状态通过 Actuator 进行收集,然后访问 /actuator/hystrix.stream 即可看到实时的监控数据。
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
配置文件
开启 hystrix.stream 端点。如果希望所有端点暴露,配置为 '*'。
management:
endpoints:
web:
exposure:
include: hystrix.stream
启动类
@SpringBootApplication
@EnableHystrix
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
}
启动之后,访问http://localhost:7006/actuator,可以看到已经开启了 hystrix.stream 端点。
进而通过访问http://localhost:7006/actuator/hystrix.stream来监控 Hystrix 的数据。可以看到,这种方式其实不利于观察服务运行状态,Hystrix提供了一套监控中心来进行查看
4.Hystrix Dashboard
添加依赖
<!-- spring boot actuator 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- spring cloud netflix hystrix 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- spring cloud netflix hystrix dashboard 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
配置文件
没有变化
启动类
在 Spring Boot 的启动类上面引入注解 @EnableHystrixDashboard,启用 Hystrix Dashboard 功能。
@SpringBootApplication
@EnableHystrix
@EnableHystrixDashboard
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
}
启动应用,然后再浏览器中输入http://localhost:7006/hystrix 即可访问。监控中心页面如下
输入能够返回监控数据的URL:http://localhost:7006/actuator/hystrix.stream,就能得到如下的监控中心图示。
5.Hystrix 监控数据聚合 Turbine
通过 Hystrix Dashboard 我们只能实现对服务当个实例的数据展现,在生产环境我们的服务是肯定需要做高可用的,那么对于多实例的情况,我们就需要将这些度量指标数据进行聚合
POM 配置
在 pom.xml 中添加以下依赖
<dependencies>
<!-- spring-cloud netflix hystrix 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- spring cloud netflix hystrix dashboard 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!-- spring cloud netflix turbine 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
</dependencies>
启动类
在启动类上使用 @EnableTurbine 注解开启 Turbine
配置文件
在 application.yml 加入 Eureka 和 Turbine 的相关配置
spring:
application:
name: turbine
server:
port: 7007
eureka:
client:
service-url:
defaultZone: http://localhost:7000/eureka/
turbine:
app-config: eureka-provider,feign-consumer
cluster-name-expression: "default"
combine-host-port: true
参数说明
turbine.app-config参数指定了需要收集监控信息的服务名,需要注意,这些被收集的服务都要像上文所述那样添加Hystrix 监控才可以;turbine.cluster-name-expression参数指定了集群名称为default,当我们服务数量非常多的时候,可以启动多个 Turbine 服务来构建不同的聚合集群,而该参数可以用来区分这些不同的聚合集群,同时该参数值可以在 Hystrix 仪表盘中用来定位不同的聚合集群,只需要在 Hystrix Stream 的 URL 中通过 cluster 参数来指定;turbine.combine-host-port参数设置为true,可以让同一主机上的服务通过主机名与端口号的组合来进行区分,默认情况下会以 host 来区分不同的服务,这会使得在本地调试的时候,本机上的不同服务聚合成一个服务来统计。
同理,在监控中心页面输入对应URL即可,以我的示例为例,输入http://localhost:7007/turbine.stream
相关代码已提交至github仓库,有兴趣的朋友可以自行对比查看