SpringCloud-容错处理Hystrix熔断器
前言:微服务架构应用的特点就是多服务,而服务层之间通过网络进行通信,从而支撑起整个应用系统,所以,各个微服务之间不可避免的存在耦合依赖关系。但任何的服务应用实例都不可能永远的健康或网络不可能永远的都相安无事,所以一旦某个服务或局部业务发生了故障,会导致系统的不可用,我们知道当故障累积到一定程度就会造成系统层面的灾害,也就是级联故障,也叫雪崩效应,所以微服务需要在故障累计到上限之前阻止或疏通这些故障以保证系统的稳固安全,在市面上已经有很多这样的框架来解决这样的问题,如Twitter的Finagle、Netflix的Hystrix和Google的Stubby等,下面就简单介绍下Hystrix和Hystrix在SpringCloud中的应用。
一、Hystrix简介
Hystrix(https://github.com/Netflix/Hystrix)是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性、容错性与局部应用的弹性,是一个实现了超市机制和断路器模式的工具类库。
二、Hystrix如何解决依赖隔离
1、包裹请求:使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立的线程中执行,使用了设计模式中的“命令模式”;
2、跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间;
3、资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程已满,则发向该依赖的请求就会被立即拒绝,而不是排队等候,从而加速失败判定;
4、监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等;
5、回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑,回退逻辑由开发人员自行提供,如返回一个缺省值;
6、自我修复:断路器打开一段时间后,会自动进入“半开”状态,此时断路器可允许一个请求访问依赖的服务,若请求成功,则断路器关闭,否则断路器转为“打开”状态;
三、Hystrix在SpringCloud中的简单应用
1、简单整合Hystrix(在这部分会用到Ribbon测试的相关项目)
①、创建基于Eureka和Ribbon的服务端和两个客户端生产者、消费者:
Server:
Client生产者:
复制项目eureka-ribbon-client,将AritfactId修改为eureka-hystrix-client;
②、在消费者中添加依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cn</groupId>
<artifactId>eureka-hystrix-client</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 添加spring-boot的maven插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties:
server.port=8765
spring.application.name=client-hystrix-8765
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
③、在启动类ClientApplication上添加@EnableHystrix或@EnableCircuitBreaker;
package com.cn; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; /** * @program: springcloud-example * @description: * @author: * @create: 2018-06-15 15:51 **/ @SpringBootApplication @EnableDiscoveryClient @EnableHystrix public class ClientApplication { public static void main(String[] args) { SpringApplication.run(ClientApplication.class, args); } /** * @Description: * @Param: * @return: * @Author: * @Date: 2018/6/15 */ @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
④、修改HystrixController,在其中添加getUser方法,并添加熔断回调方法注解及回调方法:
package com.cn.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @program: springcloud-example
* @description:
* @author:
* @create: 2018-06-15 15:55
**/
@RestController
public class HystrixController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "getDefaultUser")
@RequestMapping("/getUser")
public String getUser() {
return restTemplate.getForObject("http://client-87/getUser", String.class);
}
private String getDefaultUser() {
System.out.println("熔断,默认回调函数");
return "{\"username\":\"admin\",\"age\":\"-1\"}";
}
@GetMapping("/loadInstance")
public String loadInstance() {
ServiceInstance choose = this.loadBalancerClient.choose("client-87");
System.out.println(choose.getServiceId()+":"+choose.getHost()+":"+choose.getPort());
return choose.getServiceId() + ":" + choose.getHost() + ":" + choose.getPort();
}
}
⑤、分别启动eureka-ribbon-server、eureka-ribbon-client2、eureka-hystrix-client,调用localhost:8765/getUser,如图:
⑥、关掉eureka-ribbon-client2后,再次调用,发现熔断器调用了回调函数,如图:
关于Hystrix的配置属性,可以自行测试了解:https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica#configuration
而我们如何知道hystrix是否开启,可以通过SpringBoot Actuator组件查看,注意:出发回调并不是hystrix打开,他的状态还处在关闭,若失败率达到阈值(默认为5秒内20次失败)后才会打开;
2、基于Feign使用Hystrix
通常情况下的Hystrix是通过注解@HystrixCommand的fallbackMethod属性实现回调的,而在Feign中,由于Feign是用借口实现的声明式Rest,所以Hystrix的通用方法在这里就不适用于Feign了,实际上在Feign与SpringCloud的依赖库中已经默认的将Hystrix加入其中了,如图:
那么,该怎么实现呢?
- 将上面通用Hystrix的项目中的消费者模块修改为:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cn</groupId>
<artifactId>eureka-feign-hystrix-client</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 添加spring-boot的maven插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties属性配置文件:
server.port=8762
spring.application.name=client-8762
#默认feign的hystrix为关闭状态
feign.hystrix.enabled=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
ClientApplication.java启动类:
package com.cn; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; /** * @program: springcloud-example * @description: * @author: * @create: 2018-06-15 15:51 **/ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ClientApplication { public static void main(String[] args) { SpringApplication.run(ClientApplication.class, args); } }
FeignController.java控制层:
package com.cn.controller; import com.cn.feign.UserFeign; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @program: springcloud-example * @description: * @author: * @create: 2018-06-15 15:55 **/ @RestController public class FeignController { @Autowired private UserFeign userFeign; @RequestMapping("/getUser") public String getUser() { return userFeign.getUser(); } }
feign的接口:
package com.cn.feign; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; //接口类上加入的注解中添加属性fallback,指定回调类 @FeignClient(name = "CLIENT-87",fallback = FeignClientFallback.class) public interface UserFeign { @RequestMapping("/getUser") public String getUser(); }
创建回调类:
package com.cn.feign; import org.springframework.stereotype.Component; /** * @Description: 回调实现类 * @Param: * @return: * @Author: * @Date: 2018/6/19 */ @Component class FeignClientFallback implements UserFeign { @Override public String getUser() { System.out.println("熔断,默认回调函数"); return "{\"username\":\"admin\",\"age\":\"-1\"}"; } }
2.测试结果如下:
在未关闭生产者服务实例时:
关闭实例后:
代码示例:https://gitee.com/lfalex/springcloud-example( eureka-hystrix-client、 eureka-feign-hystrix-client、 eureka-ribbon-client2、 eureka-ribbon-server)
参考书籍:《SpringCloud与Docker微服务架构实战》周力著