Hystrix是微服务系统中一款提供保护机制的组件,是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。
在微服务中,服务的调用链路较为复杂,当其中一个服务的调用出现异常时,请求会一直阻塞,会导致服务器资源耗尽,从而导致其它服务都不可用,形成雪崩效应。
为解决雪崩效应,Hystrix提供了服务降级处理,包括线程隔离和服务熔断。
1 线程隔离和服务降级
Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满,调用会立即被拒绝,默认不采用排队,加速失败判定时间。用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理。
服务降级:优先保证核心服务,而非核心服务不可用或弱可用。
服务降级不会导致阻塞,对其它服务没有影响。
触发Hystrix服务降级的情况:线程池已满、请求超时。
1.1 添加依赖
在消费端service-consumer中添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
1.2 开启熔断
在启动类上添加注解开启熔断:
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ConsumerApplication {
}
此时可以利用@SpringCloudApplication注解替换以上三个注解:
@SpringCloudApplication
public class ConsumerApplication {
}
1.3 添加降级逻辑
在消费端service-consumer的控制层利用HystrixCommand添加降级逻辑:
@GetMapping("/{id}")
@HystrixCommand(fallbackMethod = "queryByIdFallback")
public String queryById(@PathVariable Long id){
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, User.class);
}
public String queryByIdFallback(Long id){
return "抱歉网络异常";
}
注意因为熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。
失败逻辑中返回User对象没有太大意义,一般会返回友好提示。所以把queryById的方法改造为返回String,
反正也是Json数据。这样失败逻辑中返回一个错误说明,会比较方便。
@HystrixCommand(fallbackMethod = "queryByIdFallBack")
用来声明一个降级逻辑的方法,当user-service 正常提供服务时,访问与以前一致;但是当将user-service 停机时,会发现页面返回了降级处理信息。
1.4 添加统一的降级逻辑
当需要添加降级处理的逻辑较多时,为每个方法指定一个降级处理方法并不是最优的做法,此时可以为当前类的方法提供一个统一的降级处理方法。
package com.giser.consumer.controller;
import com.giser.consumer.pojo.User;
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.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/consumer")
@DefaultProperties(defaultFallback = "defaultFallbackMethod")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/{id}")
@HystrixCommand
public User queryById(@PathVariable Long id){
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, User.class);
}
public String defaultFallbackMethod(){
return "默认的Fallback";
}
}
- 说明
@DefaultProperties(defaultFallback = “defaultFallbackMethod”)用在类上,指明统一的失败降级方法;此时该类中所有类的方法的返回类型都要与处理失败的方法的返回类型一致。
1.5 超时设置
Hystrix默认的超时时长为1s,此时可以使用hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000指定超时时间。
这个配置为作用域全局的所有方法。
2 服务熔断
在服务熔断中使用的熔断器,也称为断路器,即Circuit Breaker。Hystrix的服务熔断机制可以实现弹性容错;通过断路方式将后续的请求直接拒绝,一段时间(默认为5秒)后允许部分请求通过,如果调用成功则回到断路器关闭状态,否则断路器继续为打开状态,拒绝对服务的请求。
状态机有三种状态:Closed(关闭状态)、Open(打开状态)、HalfOpen(半开状态)。
- Closed
关闭状态,断路器关闭,所有的请求都正常访问。 - Open
打开状态,断路器打开,所有的请求都会被降级。
Hystrix会对请求情况进行统计,当一定时间内失败请求的百分比达到阈值,则触发熔断,断路器会完全打开。
默认请求此时至少不低于20次,请求失败的比例的阈值为50%。 - Half Open
半开状态,不是永久的,断路器打开后会进入休眠状态,休眠时长默认为5秒。
随后断路器会进入半开状态,此时会释放部分请求,如果这些请求正常,则会关闭断路器,否则继续保持打开,再次进入休眠。
2.1 服务熔断处理
在消费端service-consumer的控制层加入异常逻辑:
@GetMapping("/{id}")
@HystrixCommand
public User queryById(@PathVariable Long id) {
/**
* 抛出运行时异常,模拟服务熔断
*/
if (id == 1){
throw new RuntimeException("to busy");
}
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, User.class);
}
此时访问id为1的请求时(超过20次),就会触发熔断。断路器会打开,一切请求都会被降级处理。此时访问id为2的请求,会发现返回的也是失败,而且失败时间很短,只有20毫秒左右;因进入半开状态之后2是可以成功执行的。
2.2 配置熔断策略
默认的熔断策略触发条件较高,休眠的时间窗口小,我们可以参考HystrixCommandProperties类中的属性对熔断策略进行修改:
# 配置熔断策略:
hystrix:
command:
default:
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 # 熔断超时设置,默认为1秒
或者在注解中配置:
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold" , value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds" , value = "10000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage" , value = "60")
})