一、构建环境

1.1.新建cloud-provider-hystrix-payment8001

创建Maven模块

Hhystrix案例演示_ci

设置模块名为:cloud-provider-hystrix-payment8001

Hhystrix案例演示_ci_02

1.2.在pom.xml中引入依赖

这里直接复制即可

<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>com.augus.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

1.3.创建配置文件

早resources目录下创建application.yml 内容如下:

server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

1.4.创建主启动类

创建包 com.augus.cloud 下面创建实体类:

package com.augus.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}

1.5.创建service层

如下图所示:创建 class PaymentService 内容如下:

package com.augus.cloud.service;

import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {
/**
* 正常访问
* @param id
* @return
*/
public String paymentInfo(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo,id:"+id+"\t"+"正常访问";
}

/**
* 模拟超时
* @param id 为超时时间
* @return
*/
public String paymentInfoTimeOut(Integer id){
int timeNumber = 3;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + " paymentInfoTimeOut,id:" + id + "\t" + "耗时(秒)" + timeNumber;
}
}

1.6.创建controller 

在下面创建:PaymentController 内容如下:

package com.augus.cloud.controller;

import com.augus.cloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class PaymentController {
@Autowired
private PaymentService paymentService;


/**
* 正常访问
*
* @param id
* @return
*/
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return paymentService.paymentInfo(id);
}

/**
* 超时访问
* @param id
* @return
*/
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return paymentService.paymentInfoTimeOut(id);
}
}

1.7.进行测试

  • 启动eureka7001
  • 启动eureka-provider-hystrix-payment8001
  • 访问:http://localhost:8001/payment/hystrix/ok/1 可以正常访问

Hhystrix案例演示_spring_03

  • 访问:http://localhost:8001/payment/hystrix/timeout/5 等待3s可以加载出来

Hhystrix案例演示_ci_04

二、高并发测试

2.1.利用jmeter进行测试

开启Jmeter, 来20000个对于8001服务的,paymentInfoTimeOut服务进行压测,如下图所示:

Hhystrix案例演示_spring_05

再去浏览器访问:http://localhost:8001/payment/hystrix/timeout/1 ,会发现两个服务都在加载中,浏览器会显示没有加载完毕

2.2.jmeter压测得出结论

上面只是服务提供者8001自己进行测试,访问量过大就会出现卡死,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80访问超时或者错误,服务端8001直接被拖死。

2.3.进一步模拟,加入服务消费者80,进行演示

2.3.1.创建 cloud-consumer-feign-hystrix-order80 

常见maven项目模块:

Hhystrix案例演示_ci_06

设置模块名为:cloud-consumer-feign-hystrix-order80

Hhystrix案例演示_ide_07

2.3.2.在pom.xml中引入依赖

这里的内容,直接复制即可:

<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>com.augus.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2.3.3.创建配置文件

创建配置文件,内容如下:

server:
port: 80
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

2.3.4.设置主启动类

创建包com.augus.cloud 在下面创建主启动类:OrderHystrixMain80 

package com.augus.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}

2.3.5.创建service包

下面使用 OpenFeign 去调用服务提供者8001,所以写的接口,内容如下:

package com.augus.cloud.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") //指定调用的服务的名字
public interface PaymentHystrixService {

//下面指定了调用服务的那些方法
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo(@PathVariable("id") Integer id);

@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfoTimeOut(@PathVariable("id") Integer id);
}

2.3.6.创建controller

在下面创建 OrderHyrixController 内容如下:

package com.augus.cloud.controller;

import com.augus.cloud.service.PaymentHystrixService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderHyrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;

@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo(id);
}

@GetMapping("/consumer/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfoTimeOut(id);
}
}

2.3.7.基本测试

启动刚刚创建的消费者80,在浏览器访问:http://localhost/consumer/payment/hystrix/ok/1,一切访问正常

Hhystrix案例演示_ci_08

2.3.8.高并发下访问

还是和之前一样,通过jmeter实现高并发去模访问8001服务中模拟超时操作的接口 ,地址是:http://localhost:8001/payment/hystrix/timeout/1

在使用消费者80在浏览器去访问正常的接口,http://localhost/consumer/payment/hystrix/ok/1  会发现浏览器转圈或者访问超时,访问超时如下图所示:

Hhystrix案例演示_ide_09

2.3.9.故障分析

上述8001同一层次的其他接口被卡死,虽然服务提供者高并发压测的是我们写的模拟超时的接口,但是tomcat线程池里面的工作线程已经被挤占完毕,所以导致之前正常访问的接口也出现了故障,当80此时调用8001,客户端访问响应缓慢,转圈圈,甚至出现响应超时。

上述故障的出现  才有我们的降级/容错/限流等技术诞生

2.3.10.如何解决,实现的目标

  • 超时导致服务器变慢(转圈),那么就超时不在等待
  • 出错(宕机或程序运行出错),要求超时需要有兜底的方式。

解决实现:

  • 服务提供者8001超时了,服务消费者80不能一直卡死等待,必须有服务降级。
  • 服务提供者8001 down机 ,调用者80不能一直卡死等待,必须有服务降级。
  • 服务提供者8001 访问正常,但是调用者80自己有故障或有自我要求(自己的等待时间小于服务提供者),这时候80 本身要需有服务降级策略。

三、实现服务降级

3.1.服务提供者8001存在的问题解决

设置自身调用超时 时间的峰值,峰值内可以正常运行,  超过了需要有兜底的方法处理,做服务降级fallback

3.2.服务提供者8001实现服务降级

3.2.1.给主启动类添加注解

添加注解:@EnableCircuitBreaker 表示使用激活使用Hystrix,如下:

package com.augus.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}

3.2.2.业务类设置

在8001的service中,修改 PaymentService,在里面设置服务降级的兜底方法如下:

package com.augus.cloud.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {
/**
* 正常访问
* @param id
* @return
*/
public String paymentInfo(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo,id:"+id+"\t"+"正常访问";
}

/**
* 模拟超时
* @param id 为超时时间
* @return
*/
@HystrixCommand(fallbackMethod = "paymentInfoTimeOutHandle",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000") //设置当前线程的超时时间为3s
}) //指定服务降级的方法
public String paymentInfoTimeOut(Integer id){
int i = 5/0;
//模拟超时
//int timeNumber = 6;
/*try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
return "线程池:" + Thread.currentThread().getName() + " paymentInfoTimeOut,id:" + id + "\t" + "耗时(秒)";
}

/**
* 服务降级采用的方法
* @param id
* @return
*/
public String paymentInfoTimeOutHandle(Integer id){
return "线程池:" + Thread.currentThread().getName() + " 当前系统繁忙或者出错,请稍微重试" + id + "\t" + "实现对于方法的服务降级处理";
}
}

注意:

  里面分别模拟了方法内容代码错误(除零异常)和超时错误,均可以被服务降级兜底的方法处理

3.2.3.测试

重启8001服务,在浏览器访问:http://localhost:8001/payment/hystrix/timeout/1 如下图:

Hhystrix案例演示_spring_10

 无论请求的方法中有代码错误或者超时错误,都能被服务降级方法处理掉。

3.3.对于消费者80实现服务降级fallback

订单微服务,也需要设置进行服务的降级保护

3.3.1.修改消费者80的pom.xml,添加hystix依赖

依赖内容为:

<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

3.3.2.修改配置文件

在application.yml中添加如下内容,复制即可:

feign:
hystrix:
enabled: true

3.3.3.修改主启动类

这里需要给 主启动类添加 @EnableHystrix 注解

package com.augus.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}

3.3.4.修改业务类

修改80服务 controller包下的 OrderHyrixController ,设置服务降级

package com.augus.cloud.controller;

import com.augus.cloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderHyrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;

@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo(id);
}

@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1500") //设置当前线程的超时时间为3s
}) //指定服务降级的方法
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfoTimeOut(id);
}

/**
* 实现服务降级
* @param id
* @return
*/
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "消费者80,支付系统繁忙请稍后重试试或者80本身运行出错请检查自己";
}
}

为了演示看到效果,所以对于服务提供者8001,的service层进行修改,修改后如下:

package com.augus.cloud.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {
/**
* 正常访问
* @param id
* @return
*/
public String paymentInfo(Integer id){
return "线程池:"+Thread.currentThread().getName()+"paymentInfo,id:"+id+"\t"+"正常访问";
}

/**
* 模拟超时
* @param id 为超时时间
* @return
*/
@HystrixCommand(fallbackMethod = "paymentInfoTimeOutHandle",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000") //设置当前线程的超时时间为3s
}) //指定服务降级的方法
public String paymentInfoTimeOut(Integer id){
//int i = 5/0;
//模拟超时
int timeNumber = 2;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + " paymentInfoTimeOut,id:" + id + "\t" + "耗时(秒)";
}

/**
* 服务降级采用的方法
* @param id
* @return
*/
public String paymentInfoTimeOutHandle(Integer id){
return "线程池:" + Thread.currentThread().getName() + " 当前系统繁忙或者出错,请稍微重试" + id + "\t" + "实现对于方法的服务降级处理";
}
}

设置8001本身最大超时,时间为3s,本身方法执行需要2s,这样服务提供者8001,本身不会报错,但是由于消费者80的最大超时时间是1.5s,所以当超时的时候,就会走自己的服务降级处理方法。

3.3.5.测试

重启服务8001和80,然后访问:http://localhost/consumer/payment/hystrix/timeout/1 如下图所示:

Hhystrix案例演示_ci_11

 注意:由于8001本身没有问题,最大超时时间是3s,方法执行只需要2s,不会出错,但是80的最大超时时间为1.5s,在80去调用8001的时候,会出现超时,这是消费者80本身出的错,所以走的是80自己的服务降级处理方法。

3.4.当前问题所在

目前每个业务方法对应一个兜底的方法处理服务降级,这会让你的代码膨胀,所以就需要采用统一和自定义的分开,如何解决呢?

以服务消费者80为案例,这里只需要给80服务,controller中的方法设置统一的服务降级方法设置:

  • 这里主要是给类上添加一个 @DefaultProperties 注解,进行指定全局的服务降级方法,
  • 同时注意设置全局的服务降级的方法的时候,下面的樊方法上还需要添加 @HystrixCommand注解
  • 但是如果你既设置了全局的方法,又设置了单独的,那么就是就近原则生效
package com.augus.cloud.controller;

import com.augus.cloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //指定全局统一的服务降级处理方法
public class OrderHyrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;

@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo(id);
}

/*@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1500") //设置当前线程的超时时间为3s
}) //指定服务降级的方法*/
@HystrixCommand
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfoTimeOut(id);
}

/**
* 实现服务降级
* @param id
* @return
*/
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "消费者80,支付系统繁忙请稍后重试试或者80本身运行出错请检查自己";
}

/**
* 全局fallback
*
* @return
*/
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后重试.";
}
}

测试,重启80服务,然后访问http://localhost/consumer/payment/hystrix/timeout/1 如下图所示表示成功。

Hhystrix案例演示_ide_12

 

 

3.5.解决3.4中遗留问题

3.4中虽然解决了代码膨胀的问题,设置了全局的统一服务降级方法,但是可以发现服务降级的方法和业务方法混合在一起,导致代码维护性很差,所以下面就对这个问题进行解决,本次案例服务降级处理是在客户端80实现完成,与服务提供者8001没有关系  只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。

3.5.1.微服务的调用时面对的异常类型

后续在进行服务项目开发调用的时候,一般遇到的就是下面的问题。

  • 运行时错误
  • 请求超时
  • 服务宕机

3.5.2.修改cloud-consumer-feign-hystrix-order80

根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现接口,统一为接口里面的方法进行异常处理,具体操作就是在80的service包下创建PaymentFallbackService类实PaymentFeginService接口,代码如下:

package com.augus.cloud.service;

import org.springframework.stereotype.Component;

@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo(Integer id) {
return "这是对于paymentInfo方法的服务降级处理";
}

@Override
public String paymentInfoTimeOut(Integer id) {
return "这是对于paymentInfoTimeOut方法的服务降级处理";
}
}

如图所示:

Hhystrix案例演示_ide_13

3.5.3.修改PaymentFeginService接口

这里注意需要在 @FeignClient(value = "服务名称", fallback =处理服务降级的实现类的字节码) 内容如下:

package com.augus.cloud.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class) //指定调用的服务的名字
public interface PaymentHystrixService {

//下面指定了调用服务的那些方法
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo(@PathVariable("id") Integer id);

@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfoTimeOut(@PathVariable("id") Integer id);

}

3.5.4.对于controller中内容进行修改

因为我们现在直接在service层已经实现了服务降级的处理,所以原来的controller中的实现的方式就不需要了,这个时候,我们就需要删除controller中服务降级的处理,处理后代码内容如下:

package com.augus.cloud.controller;

import com.augus.cloud.service.PaymentHystrixService;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderHyrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;

@GetMapping("/consumer/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo(id);
}

@GetMapping("/consumer/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfoTimeOut(id);
}
}

3.5.4.测试

  • 启动eureka先启动7001、7002
  • 启动服务提供者PaymentHystrixMain8001
  • 访问http://localhost/consumer/payment/hystrix/timeout/1,如下图,显示的是我们在service层进行的服务降级。

Hhystrix案例演示_spring_14

 四、服务熔断

4.1.什么是熔断?

熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。 在互联网系统中,当下游服务(服务提供者)因访问压力过大而响应变慢或失败,上游服务(消费者)为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。

熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间过长时,会进行服务降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当监测到该节点微服务调用响应正常后,回复调用链路。

在springcloud框架里,可以通过Hystrix实现熔断机制。Hystrix会监控微服务间调用的状况,当失败的调用到一定的阈值后,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand

4.2.服务熔断案例演示

这里简单演示,只修改cloud-provider-hystrix-payment8001里面的内容

对于service层进行修改

修改 PaymentService 给里面添加一个方法和方法所对应的服务降级处理方法

//---服务的熔断
@HystrixCommand(
fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),//失败率达到多少后跳闸
}
)
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if (id<0) {
throw new RuntimeException("******id不能为负数");
}
//String simpleUUID = IdUtil.simpleUUID();
UUID uuid = UUID.randomUUID();
return Thread.currentThread().getName()+"\t" + "成功调用,流水号是:" + uuid;
}

//给指定了降级的方法
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id不能为负数,请稍后再试";
}

对于controller层进行修改

添加如下内容

//测试服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
return paymentService.paymentCircuitBreaker(id);
}

测试

重启 cloud-provider-hystrix-payment8001 ,对其进行自测,上面的方法已经进行了设置,输入为负数会出现服务降级,当连续多次输入错误的时候(上面设置的是错误率60%),就会触发熔断机制,当你请求正确的时候,也不会马上进行成功的处理,需要慢慢的等你请求的成功率上了,才会正常的进行处理。

  • 输入错误,先触发服务降级,多次错误达到错误率上线,出现熔断

Hhystrix案例演示_ci_15

 

  •  出现熔断机制后,输入正确也是显示走的服务降级,不会正确处理

Hhystrix案例演示_ide_16

 

  •  输入的正确请求次数多了,成功率上来了,就显示正常了

Hhystrix案例演示_ci_17

4.3.服务熔断原理说明

熔断类型

  • 熔断打开:请求不再调用当前服务,内部设置一般为MTTR(平均故障处理时间),当打开长达导所设时钟则进入半熔断状态
  • 熔断关闭:熔断关闭后不会对服务进行熔断
  • 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

Hhystrix案例演示_ci_18

断路器流程说明

官网地址 :https://github.com/Netflix/Hystrix/wiki/How-it-Works 整体的工作流程参考如下:

Hhystrix案例演示_spring_19

具体说明

1.创建对象

HystrixCommand

用在依赖的服务返回单个操作结果的时候

HystrixObservableCommand

用在依赖的服务返回多个操作结果的时候

2.命令执行

HystrixCommand

execute()

同步执行,从依赖的服务返回一个单一的结果对象,或是在发生错误的时候抛出异常。

queue()

异步执行,直接返回一个Future对象。其中包含了服务执行结果时要返回的单一对象。

HystrixObservableCommand

observe()

返回Observable对象,它代表了操作的多个结果,它是一个Hot Observable(不论“事件源”是否有“订阅者”都会在创建后对事件进行发布,所以对于Hot Observable的每一个“订阅者”都有可能是从“事件源”的中途开始的,并可能只是看到了整个操作的局部过程)。

toObservable()

同样会返回Observable对象,也代表了操作的多个结果,但是它返回的是一个Cold Observable(没有“订阅者”的时候并不会发布事件,而是进行等待,直到有“订阅者”之后才发布事件,所以对于Cold Observable的订阅者,它可以保证从一开始看到整个操作的全部过程)。

3.若当前命令的请求缓存功能是被启用的,并且该命令在缓存中,那么缓存的结果会立即以Observable对象的形式返回。

4.检查断路器是否为打开状态,如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(第8步);如果断路器是关闭的,检查是否有可用资源来执行命令(第5步)

5.线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满,那么Hystrix也不会执行命令,而是转接到fallback处理逻辑(第8步)

6.Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。

HystrixCommand

run()

返回一个单一的结果,或者抛出异常。

HystrixObservableCommand

construct()

返回一个Observable对象来发送多个结果,或通过onError发送错误通知

7.Hystrix会将“成功”、“失败”、“拒绝”、“超时”等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。

断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行“熔断/短路”。

8.当命令执行失败的时候,Hystrix会进入fallback尝试回退处理,我们通常也称该操作为“服务降级”。

而能够引起服务降级处理的情况有下面几种:

第4步:当前命令处于“熔断/短路”状态,断路器是打开的时候。

第5步:当前命令的线程池、请求队列或者信号量被占满的时候。

第6步:HystrixObservableCommand.construce()或HystrixCommand.run()抛出异常的时候。

9.当Hystrix命令执行成功之后,它会将处理结果直接返回或以Observable的形式返回

tips:如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常,Hystrix依然会返回一个Observable对象,但是它不会发送 任何结果数据,而是通过onError方法通知命令立即中断请求,并通过onError方法将引起命令失败的异常发送给调用者

断路器在什么情况下开始起作用?

涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值

  • 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
  • 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  • 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。

Hhystrix案例演示_ci_20

断路器开启或者关闭的条件是什么?

  • 当满足一定的阈值的时候(默认10秒钟超过20个请求次数)
  • 当失败率达到一定的时候(默认10秒内超过50%的请求次数)
  • 到达以上阈值,断路器将会开启
  • 当开启的时候,所有请求都不会进行转发
  • 一段时间之后(默认5秒),这个时候断路器是半开状态,会让其他一个请求进行转发. 如果成功,断路器会关闭,若失败,继续开启.重复4和5

断路器打开之后发送了什么?

断路器打开后再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。

原来的主逻辑要如何恢复呢?

  • 对于这一问题,hystrix也为我们实现了自动恢复功能。
  • 当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,
  • 当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,
  • 主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。 

五、服务监控HystrixDashboard

5.1.HystrixDashboard是什么?

 除了隔离依赖服务的调用以为,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等.Netflix通过hystrix-event-stream项目实现了对以上指标的监控.Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。

5.2.创建监控 HystrixDashboard 模块

新建 cloud-consumer-hystrix-dashboard9001 模块

创建maven模块

Hhystrix案例演示_spring_21

 

 

设置模块名字

Hhystrix案例演示_spring_22

 

 

在pom中引入依赖

这里直接复制即可:

<dependencies>
<!--hystrix dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

创建配置文件

创建application.yml 配置文件,内容为:

server:
port: 9001

创建主启动类

创建主启动类HystrixDashboardMain9001给添加新注解@EnableHystrixDashboard, 代码如下:

package com.augus.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}

给需要监控的服务添加如下依赖

给所有服务提供类(8001/8002/8003)都需要在pom.xml中添加监控依赖部署

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

启动cloud-consumer-hystrix-dashboard9001该微服务后续将监控微服务8001

浏览器访问:http://localhost:9001/hystrix,即可打开监控仪表盘的界面

Hhystrix案例演示_spring_23

 

5.3.案例演示使用9001去监控8001的服务提供者

5.3.1. 修改cloud-provider-hystrix-payment8001

需要注意的是新版本Hystrix需要在8001主启动中指定监控路径添加的内容如下:

package com.augus.cloud;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}

/**
* 此配置是为了服务监控而配置,与服务容错本身无观,springCloud 升级之后的坑
* ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream
* 只要在自己的项目中配置上下面的servlet即可
* @return
*/
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}

5.3.2. 监控测试:

启动eureka单个服务和集群均可,启动9001监控仪表盘,启动启动8001

  • 设置9001监控8001

设置如图所示:这里需要添加填空地址为:http://localhost:8001/hystrix.stream 即可

Hhystrix案例演示_ide_24

  • 测试地址

这里以访问8001,断路器演示的地址为例,进行访问:正确的地址是:http://localhost:8001/payment/circuit/1 ,异常的访问地址是:http://localhost:8001/payment/circuit/31 ,上述访问基础后观察监控仪表盘的变化,如下图:

Hhystrix案例演示_ci_25

 

 

监控分析:

  • 七种颜色:进入监控界面后会有其中颜色的数字,其含义可以对应右上角相同颜色的单词表示的状态,其值代表该状态下触发的次数
  • 那个浅色的圈:圈的大小代表该服务的流量,圈越大流量越大
  • 一线:代表监控间隔中,服务被访问的频率的折线图

通过观察这些就可以在大量的实例中找出故障实例和高压实例进行修复和维护。

Hhystrix案例演示_spring_26

详细的参数说明

 

Hhystrix案例演示_ci_27