一、概述
1.1、为什么需要服务容错
- 雪崩效应:如下图服务a出错可能会导致服务b,c相继出现通信错误,继而引发服务间调用的级联故障;而Hystrix后备可防止级联故障,开路可停止级联故障,并让不堪重负的服务时间得以恢复。后备可以是另一个受Hystrix保护的呼叫,静态数据或合理的空值。可以将回退链接在一起,以便第一个回退进行其他业务调用,然后回退到静态数据。
- Hystrix作用:服务熔断,服务降级,依赖隔离,监控
1.2、服务降级:简单示例
- 以a-mall项目的订单服务调用商品服务的服务间通信为例:
- springcloud版本:Greenwich.SR3
- springboot版本:2.1.8.RELEASE
1、引入Hystrix依赖包
<!--引入hytrix依赖包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、启动类添加@EnableCircuitBreaker标签
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@MapperScan("com.mall.order.server.dao.mapper")
@EnableCircuitBreaker //开启熔断
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class, args);
}
}
3、在需要熔断的地方添加@HystrixCommand
- 如下所示:在order服务中使用RestTemplate 动态调用product服务中的接口;@HystrixCommand标签中fallbackMethod 的value值就是调用product服务接口失败时返回的接口方法名;这一过程叫做服务降级。
package com.mall.order.server.controller.test; 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.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.Arrays; /** * 熔断器测试controller */ @RestController @RequestMapping("/hystrix/test") public class HystrixTestController { @Autowired LoadBalancerClient loadBalancerClient; @HystrixCommand(fallbackMethod = "fallback") @GetMapping("/getProductList") public String getProductList(){ RestTemplate restTemplate = new RestTemplate(); ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT"); String result = restTemplate.postForObject( String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()) + "/productClient/providerList", Arrays.asList("984325","894327"), String.class); return result; } public String fallback(){ return "访问时间过长,请稍后再试"; } }
4、启动order服务,但是关闭product服务
- 调用3中的接口,此时product已经不能提供接口通信,触发hystrix服务降级机制:
5、给order自己的服务降级
- 如下所示:不是product服务调用失败导致了服务降级,而是order服务自己的异常也触发了服务降级;所以hystrix也是进行本地服务降级的不二之选。
package com.mall.order.server.controller.test;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
/**
* 熔断器测试controller
*/
@RestController
@RequestMapping("/hystrix/test")
public class HystrixTestController {
@Autowired
LoadBalancerClient loadBalancerClient;
@HystrixCommand(fallbackMethod = "fallback")
@GetMapping("/getProductList")
public String getProductList(){
// RestTemplate restTemplate = new RestTemplate();
// ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
// String result = restTemplate.postForObject(
// String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())
// + "/productClient/providerList",
// Arrays.asList("984325","894327"),
// String.class);
// return result;
throw new RuntimeException("error");
}
public String fallback(){
return "访问时间过长,请稍后再试";
}
}
1.3、服务降级:全局默认配置
1、controller上面设置一个全局的defaultFallBack
- 使用标签**@DefaultProperties**进行hystrix全局配置
@DefaultProperties(defaultFallback = "defaultFallback")
2、全局配置代码
- 如果没有声明此方法服务降级回调方法,默认调用**@DefaultProperties**标签中的方法
package com.mall.order.server.controller.test;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
/**
* 熔断器测试controller
*/
@RestController
@RequestMapping("/hystrix/test")
@DefaultProperties(defaultFallback = "defaultFallback")
public class HystrixTestController {
@Autowired
LoadBalancerClient loadBalancerClient;
//如果没有声明此方法服务降级回调方法,默认调用@DefaultProperties标签中的方法
@HystrixCommand
@GetMapping("/getProductList")
public String getProductList(){
RestTemplate restTemplate = new RestTemplate();
ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
String result = restTemplate.postForObject(
String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())
+ "/productClient/providerList",
Arrays.asList("984325","894327"),
String.class);
return result;
// throw new RuntimeException("error");
}
private String fallback(){
return "访问时间过长,请稍后再试";
}
private String defaultFallback(){
return "默认服务降级:访问时间过长,请稍后再试";
}
}
- 测试结果:调用了defaultFallback方法
1.4、超时设置
1、将product服务提供服务的接口方法设置一个两秒的线程,然后启动product服务
@RestController
@RequestMapping("/productClient")
public class ProductClientController {
@PostMapping("/providerList")
public List<ProductInfoVo> providerProductList(@RequestBody List<String> productIds) {
//调用时给一个2s的线程睡眠时间
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return productService.getListByProductIds(productIds);
}
}
2、在order端调用通信接口
- 可以观察到,如果超过一秒未拿到响应数据就会触发hystrix的服务降级
3、hystrix默认超时时间
- 打开HystrixCommandProperties类,发现default_executionTimeoutInMilliseconds(默认超时时间,ms)设置为了1s,所以上述在1.03s时触发了服务降级。
4、找到default_executionTimeoutInMilliseconds对应的配置名字
- 还是在HystrixCommandProperties类中ctr + f查找default_executionTimeoutInMilliseconds的配置名字
5、@HystrixCommand标签中自定义超时时间3s
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
@GetMapping("/getProductList")
public String getProductList(){
RestTemplate restTemplate = new RestTemplate();
ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
String result = restTemplate.postForObject(
String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())
+ "/productClient/providerList",
Arrays.asList("984325","894327"),
String.class);
return result;
// throw new RuntimeException("error");
}
6、重启order服务测试
- 请求成功:控制台中我们可以看到本次请求用了2.04s,但是因为我们设置的hystrix超时时间为3秒,所以就可以正常访问,不会触发服务降级
二、服务熔断
1.1、简单示例
1、服务熔断四大基本配置项
- circuitBreaker.enabled:是否使用断路器,默认为true
- circuitBreaker.requestVolumeThreshold:滑动窗口的大小,默认为20
- circuitBreaker.sleepWindowInMilliseconds:过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟
- circuitBreaker.errorThresholdPercentage:错误率,默认50%
package com.mall.order.server.controller.test;
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 com.sun.net.httpserver.Authenticator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
/**
* 熔断器测试controller
*/
@RestController
@RequestMapping("/hystrix/test")
@DefaultProperties(defaultFallback = "defaultFallback")
public class HystrixTestController {
@Autowired
LoadBalancerClient loadBalancerClient;
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000"),
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")
})
@GetMapping("/getProductList")
public String getProductList(@RequestParam("number") int number){
if(number % 2 == 0){
return "success";
}
RestTemplate restTemplate = new RestTemplate();
ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
String result = restTemplate.postForObject(
String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())
+ "/productClient/providerList",
Arrays.asList("984325","894327"),
String.class);
return result;
// throw new RuntimeException("error");
}
private String fallback(){
return "访问时间过长,请稍后再试";
}
private String defaultFallback(){
return "默认服务降级:访问时间过长,请稍后再试";
}
}
2、测试用例
- 如1中代码所示:
1、当number值为奇数时,调用restTemplate逻辑代码;但是因为现在超时时间是1秒,而product服务那边设置了2s的线程等待,所以restTemplate代码肯定会触发服务降级;
2、当number为偶数时,直接返回一个success字符串,代表请求成功没有触发服务降级 - 结果:number = 1请求次数过多时,number = 2也不能正常访问了,触发了服务降级
1.2、执行流程
1、1.1中的断路器执行流程(以代码中的配置为基础)
步骤一:当number为1时的请求次数较多,在10次请求中占比超过60%时触发断路器,断路器打开,整个接口处于断路状态;
步骤二:因为整个接口断路,number=2的正常请求不能请求了,只能触发服务降级
步骤三:10秒过后断路器模式开始重新检测,如果10次以内请求异常百分比没超过60%时,断路器关闭,number = 2请求可以正常访问
2、hystrix原理图
-
Closed:熔断器关闭状态(所有请求返回成功)
-
Open:熔断器打开状态(调用次数累计到达阈值或者比例,熔断器打开,服务直接返回错误)
-
Half Open:熔断器半开状态(默认时间过后,进入半熔断状态,允许定量服务请求,如果调用都成功,则认为恢复了,则关闭断路器,反之打开断路器)
1.3、application.yml中配置hystrix
1、default默认配置
hystrix:
command:
default:
# 断路器相关配置
circuitBreaker:
# 是否开启断路器
enabled: true
# 滑动窗口的大小,默认为20
requestVolumeThreshold: 10
# 过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟
sleepWindowInMilliseconds: 10000
# 错误率,默认50%
errorThresholdPercentage: 60
execution:
isolation:
thread:
# 配置超时时间:单位ms;默认是1000ms
timeoutInMilliseconds: 3000
- java代码:
package com.mall.order.server.controller.test;
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 com.sun.net.httpserver.Authenticator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
/**
* 熔断器测试controller
*/
@RestController
@RequestMapping("/hystrix/test")
@DefaultProperties(defaultFallback = "defaultFallback")
public class HystrixTestController {
@Autowired
LoadBalancerClient loadBalancerClient;
// @HystrixCommand(commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000"),
// @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
// @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
// @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
// @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")
// })
@HystrixCommand
@GetMapping("/getProductList")
public String getProductList(@RequestParam("number") int number){
if(number % 2 == 0){
return "success";
}
RestTemplate restTemplate = new RestTemplate();
ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
String result = restTemplate.postForObject(
String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())
+ "/productClient/providerList",
Arrays.asList("984325","894327"),
String.class);
return result;
// throw new RuntimeException("error");
}
private String fallback(){
return "访问时间过长,请稍后再试";
}
private String defaultFallback(){
return "默认服务降级:访问时间过长,请稍后再试";
}
}
- 测试:number = 1可以调用了,因为超时时间是3s,大于线程等待的2秒
2、自定义配置(HystrixCommandKey)
- 我们给getProductList单独设置一个超时时间
server:
port: 8082
spring:
application:
name: order
cloud:
config:
discovery:
enabled: true
service-id: CONFIG
profile: dev
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
redis:
host: localhost
port: 6379
password: 123456
database: 3
vcap:
application:
instance_index: ${spring.cloud.config.profile}
hystrix:
command:
default:
# 断路器相关配置
circuitBreaker:
# 是否开启断路器
enabled: true
# 滑动窗口的大小,默认为20
requestVolumeThreshold: 10
# 过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟
sleepWindowInMilliseconds: 10000
# 错误率,默认50%
errorThresholdPercentage: 60
execution:
isolation:
thread:
# 配置超时时间:单位ms;默认是1000ms
timeoutInMilliseconds: 3000
getProducts:
execution:
isolation:
thread:
# 配置超时时间:单位ms
timeoutInMilliseconds: 1200
- java代码使用commandKey指定:如果未指定commandKey值,会默认使用方法名getProductList进行匹配
@HystrixCommand(commandKey = "getProducts")
@GetMapping("/getProductList")
public String getProductList(@RequestParam("number") int number){
if(number % 2 == 0){
return "success";
}
RestTemplate restTemplate = new RestTemplate();
ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
String result = restTemplate.postForObject(
String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())
+ "/productClient/providerList",
Arrays.asList("984325","894327"),
String.class);
return result;
// throw new RuntimeException("error");
}
- 测试:因为自定义的超时时间是1.2秒,小于线程等待的2秒,所以触发了服务降级
1.4、hystrix常用配置查看
- 通过HystrixCommandProperties查看hystrix的相关配置
三、hystrix可视化配置端
3.1、简单示例
- hystrix-dashboard作用:可视化观察hystrix作用情况
1、pom文件引入hystrix-dashboard依赖
<!--引入hystrix dashboard可視化依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
2、启动类加上@EnableHystrixDashboard标签
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@MapperScan("com.mall.order.server.dao.mapper")
@EnableCircuitBreaker //开启熔断
@EnableHystrixDashboard
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class, args);
}
}
3.2、连接dashboard
1、开启服务访问hystrix首页
- 配置完毕以后点击monitor按钮开始监控
2、显示无法连接dashboard监控后台
3、解决问题
-
引入健康监控依赖包,因为接口调用也和product服务有关,所以product和order服务最好均要引入spring-boot-starter-actuator健康监控依赖包
<!--使用hystrix dashboard:必须引入健康监控依赖包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
-
order服务的application.yml文件加入下列配置:
management:
endpoints:
web:
exposure:
include: 'hystrix.stream'
- 最后monitor的url不要使用https方式:
- 为什么不能使用https:下面是springcloud官网给出的回答,我们需要将ssl证书添加到JVM中获取信任,否则后台日志会抛ssl证书授权错误,导致dashboard连接失败
4、重新monitor:成功
- 以上介绍的都是单例容错(只对一个服务进行容错),但是生产环境中需求是集群容错,集群容错使用Turbine,Turbine离不开hystrix。
3.3、使用dashboard进行熔断监控
1、将前面的那两个请求在postman里面定时运行
- 请求路径1:http://localhost:8082/hystrix/test/getProductList?number=1
- 请求路径2:http://localhost:8082/hystrix/test/getProductList?number=2
- 目的:方便观察dashboard面板的情况,手动点太累
- postman点击一次连续发送多次请求
2、观察dashboard面板
3、dashboard面板参数详解
- 曲线中间有个圆,圆的大小表示流量,流量越大,圆越大;颜色越偏向红色,代表这个服务越不健康。
- Host就是请求的频率
- 图中testHystrixCommand5下来的百分比是失败率,旁边的六个数字根据颜色不同分别对应右上角那里的Success | Short-Circuited | Bad Request | Timeout | Rejected | Failure 的颜色。
- Circuit就是熔断的状态,open就是熔断打开,closed就是关闭,还有半开half open
- 曲线代表一段时间内,流量的相对变化
- 官方文档介绍:springcloud中文文档
3.4、@SpringCloudApplication标签
- 等于下列三个标签,所以完全可以用@SpringCloudApplication标签替代这三个标签
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker