1.概述
Hystrix介绍
在微服务场景中,通常会有很多层的服务调用。如果一个底层服务出现问题,故障会被向上传播给用户。我们需要一种机制,当底层服务不可用时,可以阻断故障的传播。这就是断路器的作用。他是系统服务稳定性的最后一重保障。
在springcloud中断路器组件就是Hystrix。Hystrix也是Netflix套件的一部分。他的功能是,当对某个服务的调用在一定的时间内(默认10s),有超过一定次数(默认20次)并且失败率超过一定值(默认50%),该服务的断路器会打开。返回一个由开发者设定的fallback。
fallback可以是另一个由Hystrix保护的服务调用,也可以是固定的值。fallback也可以设计成链式调用,先执行某些逻辑,再返回fallback。
Hystrix的作用
对通过第三方客户端库访问的依赖项(通常是通过网络)的延迟和故障进行保护和控制。
在复杂的分布式系统中阻止级联故障。
快速失败,快速恢复。
回退,尽可能优雅地降级。
启用近实时监控、警报和操作控制。
官网:https://github.com/Netflix/Hystrix/wiki
2.Hystrix重要概念
1.服务降级fallback
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback.
哪些情况会发出降级:程序运行异常、超时、服务熔断触发服务降级、线程池/信号量也会导致服务降级
2.服务熔断break
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
服务的降级->进而熔断->恢复调用链路
3.服务限流flowlimit
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
3.Hystrix案例
1.构建工程cloud-provider-payment8001-hystrix
1.pom
增加以下包:
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.yml
server:
port: 8001 #服务端口
spring:
application:
name: cloud-provider-payment-hystrix #服务名
#服务注册Eureka配置
eureka:
client:
register-with-eureka: true #表示向注册中心注册自己 默认为true
fetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 入驻集群版Eureka地址
# defaultZone: http://localhost:7001/eureka/ # 入驻单机版Eureka地址
instance:
instance-id: cloud-provider-payment8001-hystrix # 服务名称
prefer-ip-address: true # 访问路径可以显示ip地址
3.主启动
package com.dt.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain8001_hystrix {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001_hystrix.class, args);
}
}
4.业务类
1.service
package com.dt.springcloud.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentService {
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 5;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (Exception e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + "paymentinfo_Timeout,id:" + id + "\t" + "耗时(秒)" + timeNumber;
}
public String paymentInfo_OK(Integer id) {
return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOutHandler,id:" + id + "\t";
}
}
2.controller
package com.dt.springcloud.controller;
import com.dt.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = this.paymentService.paymentInfo_TimeOut(id);
log.info("result="+result);
return result;
}
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id )
{
String result= this.paymentService.paymentInfo_OK(id);
log.info("result="+result);
return result;
}
}
5.测试
启动7001 和7002
启动cloud-provider-payment8001-hystrix
访问:
http://localhost:8001/payment/hystrix/ok/1
http://localhost:8001/payment/hystrix/timeout/1
6.基于上述平台进行正确->错误->降级熔断->恢复
2.构建工程cloud-consumer-feign-hystrix-order80
1.pom
<!-- hystrix 熔断降级-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- OpenFeign 远程调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka-server 服务注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.yml
server:
port: 80 #服务端口
spring:
application:
name: cloud-consumer-order80-feign-hystrix #服务名
#服务注册Eureka配置
eureka:
client:
register-with-eureka: true #表示向注册中心注册自己 默认为true
fetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 入驻集群版Eureka地址
# defaultZone: http://localhost:7001/eureka/ # 入驻单机版Eureka地址
instance:
instance-id: cloud-consumer-order80-feign-hystrix # 服务名称
prefer-ip-address: true # 访问路径可以显示ip地址
#设置OpenFeign客户端超时时间
#ribbon:
# ReadTimeout: 6000 #指的是建立连接后从服务器读取到可用资源所用的时间
# ConnectTimeout: 6000 #指的是建立连接后从服务器读取到可用资源所用的时间
devtools:
restart:
enabled: true #是否支持热部署
3.主启动类
package com.dt.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 主启动类
* 开启FeignClient 调用
*/
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients
public class Order80Main_Feign_Hystrix {
public static void main(String[] args) {
SpringApplication.run(Order80Main_Feign_Hystrix.class, args);
}
}
4.Open Feign远程调用接口
package com.dt.springcloud.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;
/**
* Open Feign远程调用接口
*/
@Component
@FeignClient(value = "cloud-provider-payment-hystrix")
public interface PaymentHystrixClient {
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
}
5.业务类
package com.dt.springcloud.controller;
import com.dt.springcloud.service.PaymentHystrixClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderController {
@Resource
private PaymentHystrixClient paymentHystrixClient;
/**
* 调用timeout 5秒超时服务, 由于openFeign默认超时时间为1秒,所以若配置文件中不增加超时配置,调用该方法会报timeout
* * @param id
* @return
*/ @GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return this.paymentHystrixClient.paymentInfo_TimeOut(id);
}
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return this.paymentHystrixClient.paymentInfo_OK(id);
}
}
3.服务降级fallback
1.降级配置
@HystrixCommand
2.处理思路
设置自身调用超时时间的峰值,峰值内可以正常运行, 超过了需要有兜底的方法处理,做服务降级fallback
3.8001支付服务增加服务降级处理
1.Service中对应方法增加服务降级注解
/**
* 超时方法增加服务降级配置
* @param id
* @return
*/@HystrixCommand(fallbackMethod ="paymentInfo_TimeOutHandler",// 定义服务降级后执行的方法名称。
commandProperties = {@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")//定义峰值超时时间为3秒,超过则出错进入兜底方法。
})
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 5;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (Exception e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + "paymentinfo_Timeout,id:" + id + "\t" + "耗时(秒)" + timeNumber;
}
2.增加服务降级后执行的方法
/**
* 服务降级后的兜底执行方法
*
* @return */public String paymentInfo_TimeOutHandler(Integer id) {
return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOutHandler,id:" + id + "\t" + " 执行服务降级方法";
}
3.主启动类增加注解
@EnableCircuitBreaker //激活服务降级
4.80订单服务增加服务降级处理
1.yml中增加配置开启hystrix
#消费端开启hystrix
feign:
hystrix:
enabled: true #在feign中开启hystrix
2.主启动类增加服务降级注解
@EnableCircuitBreaker //激活服务降级
3.业务类增加服务降级注解配置
/**
* 调用timeout 5秒超时服务, 由于openFeign默认超时时间为1秒,所以若配置文件中不增加超时配置,调用该方法会报timeout
* HystrixCommand 服务降级配置
* * fallbackMethod: 定义服务降级后执行的方法名称
* * execution.isolation.thread.timeoutInMilliseconds:定义峰值超时时间为3秒,超过则出错进入兜底方法。
*
* @param id
* @return
*/@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler2",// 定义服务降级后执行的方法名称。
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")}//定义峰值超时时间为3秒,超过则出错进入兜底方法。
)
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return this.paymentHystrixClient.paymentInfo_TimeOut(id);
}
4.增加服务降级后执行的兜底方法。
/**
* 服务降级后的兜底执行方法
*
* @return */public String paymentInfo_TimeOutHandler2(Integer id) {
return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOutHandler,id:" + id + "\t" + " 服务调用或执行异常,执行服务降级方法";
}
5.存在问题
1、每个方法有一个对应的处理方法,代码膨胀。
2、处理方法和主业务逻辑混合在一起。
6.解决方法
1.配置全局Fallback,避免每个方法配置一个,代码膨胀
1.类上增加注解@DefaultProperties(defaultFallback=“”)
@RestController
@Slf4j
@DefaultProperties(defaultFallback="payment_Global_FallbackMethod") //定义全局fallback
public class OrderController {
2.增加全局默认处理方法
/**
* 全局fallback方法
*
* @return
*/
private String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试。";
}
3.方法增加@HystrixCommand注解。
不加属性代表使用默认的全局处理方法。
@GetMapping("/consumer/payment/hystrix/paymentInfo_TestGlobal/{id}")
@HystrixCommand //不加属性代表使用默认的全局处理方法
public String paymentInfo_TestGlobal(@PathVariable("id") Integer id) {
return this.paymentHystrixClient.paymentInfo_TestGlobal(id);
}
2.针对OpenFeign接口定义全局fallback解决代码耦合度高的问题
1.定义OpenFeign接口的fallback处理类
package com.dt.springcloud.service.impl;
import com.dt.springcloud.service.PaymentHystrixClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
* 实现OpenFeign接口全局服务降级处理实现类
*/
@Component
@Slf4j
public class PaymentHystrixClientServiceImpl implements PaymentHystrixClient {
@Override
public String paymentInfo_TimeOut(Integer id) {
String s = "-----PaymentHystrixClientServiceImpl fall back-paymentInfo_TimeOut";
return s;
}
@Override
public String paymentInfo_OK(Integer id) {
String s = "-----PaymentHystrixClientServiceImpl fall back-paymentInfo_OK";
return s;
}
@Override
public String paymentInfo_TestGlobal(Integer id) {
String s = "-----PaymentHystrixClientServiceImpl fall back-paymentInfo_TestGlobal";
return s;
}
}
2.OpenFeign接口增加fallback注解
@Component
@FeignClient(value = "cloud-provider-payment-hystrix", fallback = PaymentHystrixClientServiceImpl.class)
4.服务熔断break
1.实例
1.修改cloud-provider-payment8001-hystrix
PaymentService
//====服务熔断
/**
* 在10秒窗口期中10次请求有6次是请求失败的,断路器将起作用
*
* @param id
* @return
*/@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 serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber;
}
/**
* 服务熔断后降级执行的方法。
* @param id
* @return
*/public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后重试,o(╥﹏╥)o id:" + id;
}
Controller
//====服务熔断
/**
* 服务熔断方法
* @param id
* @return
*/@GetMapping("/payment/hystrix/paymentCircuitBreaker/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id )
{
String result= this.paymentService.paymentCircuitBreaker(id);
log.info("result="+result);
return result;
}
2.Hystrix断路器使用时最常用的三个重要指标参数
在微服务中使用Hystrix 作为断路器时,通常涉及到以下三个重要的指标参数(这里是写在@HystrixProperties注解中,当然实际项目中可以全局配置在yml或properties中)
1、circuitBreaker.sleepWindowInMilliseconds
断路器的快照时间窗,也叫做窗口期。可以理解为一个触发断路器的周期时间值,默认为10秒(10000)。
2、circuitBreaker.requestVolumeThreshold
断路器的窗口期内触发断路的请求阈值,默认为20。换句话说,假如某个窗口期内的请求总数都不到该配置值,那么断路器连发生的资格都没有。断路器在该窗口期内将不会被打开。
3、circuitBreaker.errorThresholdPercentage
断路器的窗口期内能够容忍的错误百分比阈值,默认为50(也就是说默认容忍50%的错误率)。打个比方,假如一个窗口期内,发生了100次服务请求,其中50次出现了错误。在这样的情况下,断路器将会被打开。在该窗口期结束之前,即使第51次请求没有发生异常,也将被执行fallback逻辑。
综上所述,在以上三个参数缺省的情况下,Hystrix断路器触发的默认策略为:
在10秒内,发生20次以上的请求时,假如错误率达到50%以上,则断路器将被打开。(当一个窗口期过去的时候,断路器将变成半开(HALF-OPEN)状态,如果这时候发生的请求正常,则关闭,否则又打开)
3.熔断类型
熔断打开:请求不再调用当前服务,内部设置一般为MTTR(平均故障处理时间),当打开长达导所设时钟则进入半熔断状态
熔断关闭:熔断关闭后不会对服务进行熔断
熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
4.断路器开启或者关闭的条件
- 当满足一定的阈值的时候(默认10秒钟超过20个请求次数)
- 当失败率达到一定的时候(默认10秒内超过50%的请求次数)
- 到达以上阈值,断路器将会开启
- 当开启的时候,所有请求都不会进行转发
- 一段时间之后(默认5秒),这个时候断路器是半开状态,会让其他一个请求进行转发. 如果成功,断路器会关闭,若失败,继续开启.重复4和5
4.Hystrix服务监控HystrixDashboard
1.HystrixDashboard仪表盘工程搭建
cloud-consumer-hystrix-dashboard9001。
1.pom
dashboard相关操作以来spring-cloud-starter-netflix-hystrix-dashboard 、spring-boot-starter-actuator
<dependencies>
<!-- hystrix dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--引入自定义的cloud-api-common包,可以使用其中的实体-->
<dependency>
<groupId>com.dt.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>1.0.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>
<!-- 省去Get/Set方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.yml
server:
port: 9001
#服务名称
spring:
application:
name: cloud-consumer-hystrix-dashboard #服务名
#服务注册Eureka配置
eureka:
client:
register-with-eureka: true #表示向注册中心注册自己 默认为true
fetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
# defaultZone: http://localhost:7001/eureka/ # 入驻单机版Eureka地址
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 入驻集群版Eureka地址
instance:
instance-id: cloud-consumer-hystrix-dashboard #服务名
prefer-ip-address: true #访问路径可以显示IP地址
lease-renewal-interval-in-seconds: 1 # eureka客户端向服务端发送心跳的时间间隔 单位为秒(默认30秒)
lease-expiration-duration-in-seconds: 2 # eureka服务端在收到最后一次心跳后等待时间上限, 单位为秒(默认90秒), 超时将剔除服务
3.主启动@EnableHystrixDashboard
package com.dt.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
/**
* hystrixDashboard 监控看板 主启动类
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class, args);
}
}
5.管理端页面
访问:http://localhost:9001/hystrix
2.断路器演示(服务监控hystrixDashboard)
1.被监控工程改造cloud-provider-payment8001-hystrix。
1.pom
需要有以下依赖
<!--用于页面图形化展示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.主启动类增加注解
@EnableCircuitBreaker //激活服务降级
3.主启动增加Bean注入
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
2.启动应用
eureka7001
eureka7002
cloud-consumer-hystrix-dashboard9001
cloud-provider-payment8001-hystrix
3.监控测试
1.9001监控8001
2.在http://localhost:9001/hystrix/页面中填写测试地址:http://localhost:8001/hystrix.stream
Delay: 2000 ms Title: T3
3.访问请求
http://localhost:8001/payment/hystrix/paymentCircuitBreaker/1
http://localhost:8001/payment/hystrix/paymentCircuitBreaker/-1
4.观察Dashboard页面