Spring Cloud Hystrix服务容错 (学习总结)

一、雪崩效应

“雪崩效应”: 因 '服务提供者' 的不可用导致 '服务调用者' 的不可用,并将不可用逐渐放大的过程

二、Hystrix介绍

Spring Cloud中服务之间的调用方式主要有两种,一种是Ribbon+RestTemplate,一种是Feign声明式服务调用,在实际项目中,为了服务高可用,一个服务通常会集群部署,运行多个实例, 由于网络原因或者服务自身原因,被调用的服务并不能保证100%请求成功,如果这时候有大量的请求请求这个故障的服务,由于服务之间的依赖关系,故障会进行蔓延,这时候会导致调用服务自身也出现不可用的情况,使用Hystrix可以解决这个问题。当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

三、准备工作:

本文同样有三个工程,分别是:

eureka-server: 服务注册中心,端口1111;

hystrix-service:服务提供者,端口2222和3333,需要启动多个实例;

ribbon-hystrix: Ribbon服务容错,端口4444

feign-hystrix: Feign服务容错,端口5555

对于eureka-server以及hystrix-service的搭建本文不做详细介绍,需要注意的是hystrix-service需要暴露一个接口/getInfo给外部调用:

/**
 * @Title: HystrixServiceController
 * @Description: 服务提供者
 * @Author WeiShiHuai
 * @Date 2018/9/11 9:31
 */
@RestController
public class HystrixServiceController {

    private static Logger logger = LoggerFactory.getLogger(HystrixServiceController.class);

    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping("/getInfo")
    public String getInfo(@RequestParam("name") String name) {
        ServiceInstance serviceInstance = discoveryClient.getLocalServiceInstance();
        String host = serviceInstance.getHost();
        Integer port = serviceInstance.getPort();
        String info = "hello, name = " + name + ", host = " + host + ", port = " + port;
        logger.info(info);
        return info;
    }

}

四、新建springcloud_ribbon_hystrix项目

首先讲一下在Ribbon项目中使用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.springcloud.wsh</groupId>
	<artifactId>springcloud_ribbon_hystrix</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springcloud_ribbon_hystrix</name>
	<description>Spring Cloud Hystrix Ribbon服务单元</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Camden.SR6</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</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>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-ribbon</artifactId>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

五、启动类加上@EnableCircuitBreaker注解

该注解主要是打开Hystrix断路器功能

/**
 * @Description: 应用程序启动类
 * @Author: WeiShiHuai
 * @Date: 2018/9/11 9:37
 * Spring Cloud中服务之间的调用方式主要有两种,一种是Ribbon+RestTemplate,一种是Feign声明式服务调用,在实际项目中,为了服务高可用,一个服务通常会集群部署,运行多个实例,
 * 由于网络原因或者服务自身原因,被调用的服务并不能保证100%请求成功,如果这时候有大量的请求请求这个故障的服务,由于服务之间的依赖关系,故障会进行蔓延,这时候会导致调用服务自身也出现不可用的情况,使用Hystrix可以解决这个问题
 * 当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
 * <p>
 * “雪崩效应”: 因 '服务提供者' 的不可用导致 '服务调用者' 的不可用,并将不可用逐渐放大的过程
 */
@SpringBootApplication
@EnableDiscoveryClient
// @EnableHystrix 与 @EnableCircuitBreaker注解用于开启Hystrix断路器功能
//@EnableHystrix
@EnableCircuitBreaker
//@EnableHystrixDashboard注解开启Hystrix仪表盘功能,监控请求情况
//通过Hystrix Dashboard可以直接看到各个Hystrix Command的请求响应时间,请求成功率等数据
@EnableHystrixDashboard
//@SpringCloudApplication = @SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker
public class SpringcloudRibbonHystrixApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudRibbonHystrixApplication.class, args);
    }

    /**
     * 开启Ribbon负载均衡能力,并注入spring容器
     */
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

六、新建HystrixService

  • 改造原来的服务消费方式,在使用ribbon消费服务的函数上增加@HystrixCommand注解来指定回调方法。
/**
 * @Title: HystrixService
 * @ProjectName springcloud_hystrix
 * @Description: 实现Hystrix断路器
 * @Author WeiShiHuai
 * @Date 2018/9/11 9:52
 */
@Service
public class HystrixService {

    private static Logger logger = LoggerFactory.getLogger(HystrixService.class);

    @Autowired
    RestTemplate restTemplate;

    //@HystrixCommand注解主要对getInfo()开启熔断器的功能,并指定fallbackMethod熔断方法(服务不可用时执行熔断方法)
    @HystrixCommand(fallbackMethod = "getInfoErrorFallBack")
    public String getInfo(String name) {
        String info = restTemplate.getForObject("http://hystrix-service/getInfo?name={name}", String.class, name);
        logger.info(info);
        return info;
    }

    /**
     * Hystrix熔断方法(即调用失败回调方法)
     *
     * @param name
     * @return
     */
    public String getInfoErrorFallBack(String name) {
        return "sorry, " + name + ", the hystrix service is not available! ";
    }

}

七、新建HystrixController

注入HystrixService,调用hystrixService.getInfo()接口进行断路器测试:

/**
 * @Title: HystrixController
 * @ProjectName springcloud_hystrix
 * @Description: 测试
 * @Author WeiShiHuai
 * @Date 2018/9/11 9:48
 */
@RestController
public class HystrixController {

    private static Logger logger = LoggerFactory.getLogger(HystrixController.class);

    @Autowired
    private HystrixService hystrixService;

    @RequestMapping("/getInfo")
    public String getInfo(@RequestParam("name") String name) {
        return hystrixService.getInfo(name);
    }

}

八、启动项目

依次启动eureka-server端口1111、hystrix-service可以启动两个实例2222和3333,ribbon-hystrix端口4444,

浏览器访问http://localhost:4444/getInfo?name=weixiaohuai,如下图:

接口成功调用,同时看后台打印的日志,也实现了服务的负载均衡调用

此时,我们关闭hystrix-service端口分别为2222以及3333的服务,再次访问http://localhost:4444/getInfo?name=weixiaohuai,如下图:

可以看到,当hystrix-service服务不可用时,我们访问其中的接口,执行了我们制定的fallback熔断方法。这样在Ribbon中使用Hystrix断路器功能已经实现。接下来看一下在Feign中使用Hystrix,因为Feign默认集成了Hystrix,因此不需要添加Hystrix的依赖。

九、新建springcloud_feign_hystrix工程

pom.xml只需要引入主要的Feign依赖:

<?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.sprincloud.wsh</groupId>
	<artifactId>springcloud_feign_hystrix</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springcloud_feign_hystrix</name>
	<description>Spring Cloud Feign Hystrix</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Camden.SR6</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</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-feign</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

十、启动类加上@EnableFeignClients注解

该注解主要是打开Feign远程服务调用的功能

/** 
 * @Description: 应用程序启动类
 * @Author: WeiShiHuai  
 * @Date: 2018/9/11 10:23
 *
 * Feign默认集成了Hystrix,所以只需要加入Feign的依赖即可。
 * Feign是通过@FeignClient()中指定fallback来实现Hystrix断路器功能的,当远程服务调用失败的时候就会执行这个回调方法。
 *
*/
@SpringBootApplication
@EnableDiscoveryClient
//开启Feign声明式服务调用功能
@EnableFeignClients
public class SpringcloudFeignHystrixApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringcloudFeignHystrixApplication.class, args);
	}
}

新建配置文件application.yml:

server:
  port: 5555
spring:
  application:
    name: feign-hystrix
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1111/eureka/

十一、新建Feign接口HystrixFeign

/**
 * @Title: HystrixFeign
 * @ProjectName springcloud_hystrix
 * @Description:
 * @Author WeiShiHuai
 * @Date 2018/9/11 10:25
 */
@FeignClient(value = "hystrix-service", fallback = HystrixFeignFallback.class)
public interface HystrixFeign {

    @RequestMapping("/getInfo")
    String getInfo(@RequestParam("name") String name);

}

在feign接口中,我们通过指定fallback来实现Hystrix服务容错功能

新建HystrixFeignFallback回调类,并且实现@FeignClient修饰的接口:

/**
 * @Title: HystrixFeignFallback
 * @ProjectName springcloud_feign_hystrix
 * @Description: FeignClient失败回调方法
 * @Author WeiShiHuai
 * @Date 2018/9/11 10:28
 * FeignClient失败回调方法必须实现使用@FeignClient标识的接口(implements HystrixFeign),实现其中的方法
 */
@Component
public class HystrixFeignFallback implements HystrixFeign {

    /**
     * 由于某种原因使得服务调用不成功时会执行该回调方法
     *
     * @param name
     * @return
     */
    @Override
    public String getInfo(String name) {
        return "sorry " + name + ", feign client error";
    }

}

十二、新建FeignHystrixController

暴露一个getInfo接口给外部调用,如下:

/**
 * @Title: HystrixFeignController
 * @ProjectName springcloud_feign_hystrix
 * @Description: 测试Feign
 * @Author WeiShiHuai
 * @Date 2018/9/11 10:28
 * 注入FeignClient,调用feignClient的方法实现远程方法调用
 */
@RestController
public class HystrixFeignController {

    private static Logger logger = LoggerFactory.getLogger(HystrixFeignController.class);

    @Autowired
    private HystrixFeign hystrixFeign;

    /**
     * 使用http://localhost:5555/getInfo?name=xxx访问,实际上会通过FeignClient调用服务hystrix-service提供的getInfo接口
     *
     * @param name
     * @return
     */
    @GetMapping("/getInfo")
    public String getInfo(@RequestParam("name") String name) {
        String info = hystrixFeign.getInfo(name);
        logger.info(info);
        return info;
    }

}

接下来,同样启动eureka-server、以及hystrix-service两个实例,还有启动feign-hystrix,访问http://localhost:5555/getInfo?name=weixiaohuai,结果如下:

接口已经成功调用,通过查看后台日志,同样feign也实现对服务的负载均衡调用

此时,我们停掉hystrix-service的两个实例,再次访问http://localhost:5555/getInfo?name=weixiaohuai,服务会进行熔断处理,执行了我们指定的fallback方法。

由此证明Hystrix断路器起作用了。

十三、总结

以下内容,摘自网上总结:

雪崩效应的原因:

1) 服务提供者不可用
  a.硬件故障
  b.程序Bug
  c.用户大量请求:在秒杀和大促开始前,如果准备不充分,用户发起大量请求造成服务提供者的不可用

2) 重试加大流量
  a.用户重试:用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单
  b.代码逻辑重试:服务调用端的会存在大量服务异常后的重试逻辑

3) 服务调用者不可用
  a.同步等待造成的资源耗尽:使用 同步调用 时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 造成服务雪崩效应产生


雪崩效应的解决措施:
1) 流量控制
  a.网关限流
  因为Nginx的高性能, 目前一线互联网公司大量采用Nginx+Lua的网关进行流量控制, 由此而来的OpenResty也越来越热门.
  b.用户交互限流
  具体措施:
    a21. 采用加载动画,提高用户的忍耐等待时间.
    a22. 提交按钮添加强制等待时间机制.
  c.关闭重试

2) 改进缓存模式
  a.缓存预加载
  b.同步改为异步刷新

3) 服务自动扩容
  a.AWS的auto scaling

4) 服务调用者降级服务
  a.资源隔离:主要是对调用服务的线程池进行隔离.
  b.对依赖服务进行分类
  依赖服务分为: 强依赖和若依赖. 强依赖服务不可用会导致当前业务中止,而弱依赖服务的不可用不会导致当前业务的中止.
  c.不可用服务的调用快速失败

一般通过 超时机制, 熔断器 和熔断后的 降级方法 来实现

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/82628934