Hystrix
关于概念,网上很多了,例如这一篇:点击查看
服务降级(fallback)
什么情况下需要服务降级,例如程序运行出异常了,服务调用超时了,服务熔断了,线程池/信号量打满了...
什么意思呢,现在假如A,B两台服务器,B服务的某个接口被高并发访问中,导致B服务的其他接口也会被拖慢,A服务这个时候要使用B服务的这个接口,这就意味着A服务也会变慢,假如B服务被拖死了,A服务随之也会被拖死。
A和B两台服务器可能出现的情况如下:
-
B服务当前正在卡着,A服务访问B服务,A服务需要等待B服务的响应,所以A服务也被卡死。
-
B服务现在宕机了,A服务访问B服务,怎么也得不到结果,A服务被拖慢了,最终还是会被卡死。
-
B服务好好的,但是A服务发生了一点故障,导致A服务变慢了,最终照样被卡死。
为了防止这个情况,那么现在就应该加入服务降级,防止A服务被拖死了,也可以在B服务中加入降级防止自己被拖死。
发现问题
现在模拟一下这个情况。
在Provider里面写三个接口:
@Value("${server.port}")
private String serverPort;
@GetMapping("normal")
public String normal() {
return "返回了一个信息!" + serverPort + " --> " + UUID.randomUUID().toString();
}
@GetMapping("timeout")
public String timeout() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "超时返回了一个信息!!!" + serverPort + " --> " + UUID.randomUUID().toString();
}
@GetMapping("exception")
public String exception() {
int i = 10 / 0;
return "返回异常...";
}
三个接口分别是正常,超时,异常。
然后用在Consumer
端用OpenFeign
去调用这三个接口。
@FeignClient("cloud-provider-paymanet")
public interface OpenFeignService {
@GetMapping("normal")
String normal();
@GetMapping("timeout")
String timeout();
@GetMapping("exception")
String exception();
}
@RestController
@Slf4j
public class PayController {
@Resource
private OpenFeignService openFeignService;
@GetMapping("normal")
public String normal() {
return openFeignService.normal();
}
@GetMapping("timeout")
public String timeout() {
return openFeignService.timeout();
}
@GetMapping("exception")
public String exception() {
return openFeignService.exception();
}
}
此时访问Consumer
端的接口,发现因为Provider
犯下的错误,导致Consumer
也受到了影响。
Provider规避自己的错误
现在只好让Provider
不犯错,或者自己把错误处理好,不影响别人。那么这个时候Provider
就可以用Hystrix
进行服务的降级了。
首先导入坐标:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
这样就可以使用Hystrix
了,接下来在可能超时的方法上面做点手脚:
public String timeoutHandler() {
return "对不起,服务超时了...";
}
@HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
})
@GetMapping("timeout")
public String timeout() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "超时返回了一个信息!!!" + serverPort + " --> " + UUID.randomUUID().toString();
}
@HystrixCommand
这个注解用在方法上,可以为这个方法规定一些约束,例如上面规定了他的降级方法为timeoutHandler
,然后规定了在execution.isolation.thread.timeoutInMilliseconds
等于1000
的时候进行服务降级。
execution.isolation.thread.timeoutInMilliseconds
这个长长的字符串在哪里找到的呢?
com.netflix.hystrix.HystrixCommandProperties
这个类中规定了这些属性,使用的时候可以到这个类中去寻找。
光使用上面的注解是不够的,还需要再主启动类上面使用@EnableCircuitBreaker
注解。例如下面的样子:
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
circuitbreaker
是断路器的意思。
好了,启动Provider,访问接口http://localhost:8001/timeout可以看到不再等待那么长时间了,而是使用了断路器规定的1秒钟。然后就调用了降级方法,返回了:“对不起,服务超时了...”
如果打印出线程的名称,可以看到使用的线程是Hystrix
专属的线程,例如:HystrixTimer-1
同样的对于exception
来说,可以用下面的代码:
public String exceptionHandler() {
return "对不起,服务报错了...";
}
@HystrixCommand(fallbackMethod = "exceptionHandler")
@GetMapping("exception")
public String exception() {
int i = 10 / 0;
return "返回异常...";
}
上面只是把Provider
做了服务降级的保护,当然也是可以让Consumer
做降级保护的,方法都是一样的,不重复了。
全局服务降级
@DefaultProperties(defaultFallback = "globalHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
})
@RestController
@Slf4j
public class PayController {
@Value("${server.port}")
private String serverPort;
@GetMapping("normal")
public String normal() {
return "返回了一个信息!" + serverPort + " --> " + UUID.randomUUID().toString();
}
public String globalHandler() {
return "全局的降级处理...";
}
@HystrixCommand
@GetMapping("timeout")
public String timeout() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "超时返回了一个信息!!!" + serverPort + " --> " + UUID.randomUUID().toString();
}
@HystrixCommand
@GetMapping("exception")
public String exception() {
int i = 10 / 0;
return "返回异常...";
}
}
上面代码使用了@DefaultProperties
注解重复了之前的配置,给了一个默认的降级方法globalHandler
,这样需要服务降级的方法上就可以不用写一堆的东西了,直接使用@HystrixCommand
注解就可以了。
服务降级类
当然上面的代码还有一个问题,降级方法和一堆的业务逻辑都混合在一起,显然不是很漂亮。
可以在OpenFeign
上面做手脚:
写一个类,实现标注了@FeignClient
注解的接口:
@Component
public class OpenFeignServiceFallback implements OpenFeignService {
@Override
public String normal() {
return "正常方法的服务降级...";
}
@Override
public String timeout() {
return "超时方法的服务降级...";
}
@Override
public String exception() {
return "异常方法的服务降级...";
}
}
注意一定要写上@Component
注解,不然的话启动都没法启动。
然后修改@FeignClient
,加入属性fallback
@FeignClient(value = "cloud-provider-paymanet", fallback = OpenFeignServiceFallback.class)
public interface OpenFeignService {
@GetMapping("normal")
String normal();
@GetMapping("timeout")
String timeout();
@GetMapping("exception")
String exception();
}
因为在Feign
中使用Hystrix
,但是Feign
默认是关闭了Hystrix
功能的,所以需要在配置文件中启用。
feign:
hystrix:
enabled: true
Hystrix和Ribbon超时时间问题
Hystrix
设置的超时时间和Ribbon
设置的超时时间是不冲突的,谁设置的时间短,谁会先生效:
举个例子:
如果Hystrix
设置了execution.isolation.thread.timeoutInMilliseconds
为1S
,Ribbon
配置了超时时间为3S
,调用接口需要2S
,就会在1S
的时候走降级方法。
反过来Hystrix
设置为3S
,Ribbon
设置为1S
,也会在1S
的时候走降级方法,只不过后台会报错调用超时:
2020-03-23 15:06:33.788 DEBUG [cloud-consumer-order,4a78d6a77e1687c8,3e3fcb80db6a3b12,true] 3208 --- [ider-paymanet-4] c.cy.consumer.service.OpenFeignService : [OpenFeignService#timeout] java.net.SocketTimeoutException: Read timed out
at java.base/java.net.SocketInputStream.socketRead0(Native Method)
at java.base/java.net.SocketInputStream.socketRead(SocketInputStream.java:115)
at java.base/java.net.SocketInputStream.read(SocketInputStream.java:168)
...
配置文件的Hystrix
之前的@HystrixProperty
配置的Hystrix
其实也可以写在配置文件里面:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
default
,意味着这是全局的配置
如果要为某一个方法配置一下:
hystrix:
command:
timeout:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
可以看到把default
改为了timeout
,这就意味着给timeout
方法设置了超时时间了。
服务熔断(break)
如果一个接口访问错误到达一定的比例,例如官方默认的10
秒有50%
(10个以上)的服务发生了错误。将会切断当前接口的线路,无论是否正常都会被强行切断服务,过5
秒后会再次尝试这个接口的服务是否可用,决定是否要恢复当前接口的线路。
要实现的效果就是访问接口发生错误达到一定的比例后,正确的访问也在一定的时间内不能访问了。
服务熔断也是马丁·弗勒提出的概念,大神就是令人敬仰。
和要实现的效果一样,有三种状态,Closed
,Open
,Half Open
当Closed
的时候服务可以正常的访问,当访问的时候失败的次数达到了阈值,就变成了Open
状态,也就不让访问了,过了一段时间后变成了Half Open
状态,在这个状态的时候可以尝试访问,如果访问成功,就变为Closed
状态,如果访问失败,继续保持Open
状态。
接下来用代码来实现一下,不用官方的默认值,用自己定义的值。
@RestController
@Slf4j
public class PayController {
@HystrixCommand(defaultFallback = "breakHandler", commandProperties = {
// 是否启用服务熔断,默认就是true
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 服务熔断的阈值,默认是20,也就是说访问20次,有多少次失败了...
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
// 多久之后恢复成Half Open状态,默认就是5000
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000"),
// 请求失败达到的百分比是多少,开启服务熔断,默认就是50%
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
})
@GetMapping("break/{id}")
public String breakService(@PathVariable("id") Integer id) {
if (id > 0) {
return "访问正常...";
}
throw new RuntimeException("访问异常...");
}
public String breakHandler() {
return "服务已经被熔断了...";
}
}
注释中已经解释的很详细了,怎么测试呢?
访问接口http://localhost:8001/break/-1会发生错误,会调用breakHandler
方法
然后访问http://localhost:8001/break/1接口是正常的访问。
这个时候重复的刷新错误的接口,让访问出错的频率变高,达到阈值,然后再去访问正确的接口,会发现正确的接口也无法访问了。
需要注意的是,服务熔断是针对同一个接口而言的。
Hystix Dashboard
这个东西是Hystrix
的图形化界面,他长这样:
用来观察服务的状态。
首先需要安装它,新建一个项目,然后导入坐标:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
然后规定一个端口号9001,在启动类上面加上:@EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
启动后就可以访问:http://localhost:9001/hystrix了
要被监控的服务必须要引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
还需要进行一些配置:
management:
endpoints:
web:
exposure:
include: health, info, hystrix.stream
这样就可以了!
在dashboard
界面输入:http://localhost:8001/actuator/hystrix.stream,然后点击Monitor Stream
按钮就可以进入监控界面了。
但是刚进入界面的时候是一个Loading ...
,并且啥反应都没有,解决这个问题的方法也很简单,只需要访问一下Hystrix
控制的接口一次就可以了。
Dashboard界面查看方法
服务限流(flowlimit)
服务限流后面有更加好用的工具,参考本系列第九篇SpringCloud Alibaba Sentinel
。