1、简介
Hystix,即熔断器。
主页:https://github.com/Netflix/Hystrix/
Hystix 是 Netflix 开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。
2、雪崩问题
微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个服务接口才能实现,会形成非常复杂的调用链路:
如图,一次业务请求,需要调用 A、P、H、I 四个服务,这四个服务又可能调用其它服务。
如果此时,某个服务出现异常:
例如微服务 I 发生异常,请求阻塞,用户不会得到响应,则 tomcat 的这个线程不会释放,于是 越来越多的用户请求到来,越来越多的线程会阻塞:
服务器支持的线程和并发数有限,请求一致阻塞,会导致服务器资源耗尽,从而导致所有其他服务都不可用,形成雪崩效应。
Hystix 解决雪崩效应的手段有两个:
线程隔离服务熔断
3、线程隔离、服务降级
原理
线程隔离示意图:
解读:
Hystrix 为每个依赖服务调用分配一个小的线程池,如果线程池已满,调用将立即被拒绝,默认不采用排队、加速失败判定时间。
用户的请求将不再直接访问服务,而是通过线程池中的闲置线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理,什么是服务降级?
服务降级:优先保证核心服务,而非核心服务不可用或弱可用。
用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息)。
服务降级虽然会导致请求失败,但不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其他服务没有响应。
触发 Hystrix 服务降级的情况:
线程池已满请求超时
4、动手实践 —— 在服务的消费方降级 —— 自己失败
引入依赖
首先在user-consumer中引入 Hystix 依赖:
<!-- Hystix 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
开启熔断
启动项加注解 @EnableCircuitBreaker
package cn.ys;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient // 开启 Eureka 客户端
@EnableCircuitBreaker // 开启熔断
@SpringBootApplication
public class ConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
可以用 注解 @SpringCloudApplication 替代上面的三个注解
改造消费者
我们改造 user-consumer,添加一个用来访问的 user 服务的 DAO,并且声明一个失败时的回滚处理函数:
package cn.ys.consumer.controller;
import cn.ys.consumer.pojo.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("{id}")
@HystrixCommand(fallbackMethod = "queryByIdFallback")
public User queryById(@PathVariable("id") Long id ){
// 获取ip和端口信息
String url = "http://user-service/user/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
public User queryByIdFallback(Long id ){
User user = new User();
user.setId(id);
user.setName("不好意思,服务器拥挤!");
return user;
}
}
-
@HystrixCommand(fallbackMethod="queryByIdFallback"):声明一个失败回滚处理函数queryByIdFallback,当queryById执行超时(默认是1000毫秒),就会执行queryByIdFallback函数,返回错误提示。 - 为了方便查看熔断的触发时机,我们记录请求访问时间。
改造服务提供者
改造服务提供者,随机休眠一段时间,模拟超时,以触发熔断:
启动测试
如果有多个方法,每个方法都对应一个 降级的逻辑,那么太多了。
优化 —— 通用的降级逻辑
package cn.ys.consumer.controller;
import cn.ys.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.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 = "defaultFallback")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("{id}")
@HystrixCommand
public String queryById(@PathVariable("id") Long id ){
// 获取ip和端口信息
String url = "http://user-service/user/" + id;
String s = restTemplate.getForObject(url, String.class);
return s;
}
public String defaultFallback(){
return "不好意思,服务器拥挤!";
}
}
优化:自定义超时时长
如果某个方法查询的对应时间比较长,单独设置超时时长:
全局超时时长设置:
虽然熔断实现了,但是我们的重试机制似乎没有生效,是这样吗?
其实这里是因为我们的 Ribbon 超时时间设置的是1000ms:
而 Hystix 的超时时间默认也是1000ms,因此重试机制没有被触发,而是先触发了熔断。
所以,Ribbon 的超时时间一定要小于 Hystix 的超时时间。
我们可以通过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设 置Hystrix 超时时间。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 2000 # 设置hystrix的超时时间为2000ms
也可以设置单独的方法超时时长:
hystrix:
command:
user-server:
execution:
isolation:
thread:
timeoutInMillisecond: 2000 # 设置hystrix的超时时间为2000ms
5、服务熔断:
熔断原理:
熔断器,也叫断路器,其英文单词为:Circuit Breaker
Hystrix 的熔断状态机模型:
模拟环境: