Spring Cloud-6-Hystrix断路器

概念

在微服务架构中,我们把系统拆分成多个服务单元,各个单元都运行在不同的进程中,他们之前如果要交互的话,就是通过远程调用的方式,这样就有可以能会因为网络原因,或者自身服务出现故障或延迟,而导致调用方的服务也出现延迟,若此时调用方的请求不断增加,最后就会因为等待出现故障的依赖方影响形成任务积压,最终导致服务瘫痪.

在微服务架构中,存在着那么多的服务单元,若一个单元出现故障,就很容易因依赖关系而引发故障的蔓延,最终导致整个系统的瘫痪这样的架构相较传统架构更加不稳定.为了解决这样的问题,产生了断路器等一系列的服务保护机制.

在分布式系统中,断路器的作用就是,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待,这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延.

实战实验

服务注册中心

启动eureka-server工程, 可以是单节点启动,也可以是高可用的方式启动 .

服务提供者

启动两个服务提供者,也就是eureka-client工程,端口分别是8081,8082

服务消费者

启动消费者工程consumer-ribbon,端口9000

在加入断路器之前,我们先来看一下没有断路器的效果, 把端口是8081的eureka-client先关掉, 然后浏览器访问 http://localhost:9000/consumer, 可以看到报500,错误信息是

1
I/O error on GET request for "http://localhost:8081/dc": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect

就是连接失败

接下来,开始引入断路器

改造消费者工程

在consumer-ribbon工程中,引入以下依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>

在启动类中添加@EnableCircuitBreaker注解,启动断路器

然后改造服务消费方式,新建一个-HelloService类,在service中调用client的接口,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class HelloService {

@Autowired
RestTemplate restTemplate;

@HystrixCommand(fallbackMethod = "helloFallback")
public String helloService(){
return restTemplate.getForObject("http://EUREKA-CLIENT/dc", String.class);
}

public String helloFallback(){
return "error";
}
}

在helloService()方法上添加了@HystrixCommand注解, 是用来指定回调方法的,也就是helloFallback()方法.

然后,修改controller类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@Slf4j
public class DcController {

@Autowired
private HelloService helloService;

@GetMapping("/consumer")
public String consumer(){
return helloService.helloService();
}
}

模拟服务关闭

然后来验证一下,通过断路器实现的服务回调逻辑, 重新启动之前关闭的8081的client,此时两个提供者都已经启动了,然后访问两次http://localhost:9000/consumer,轮询了两个client,并返回了文字信息.

这时候再关闭8081的client,然后再访问http://localhost:9000/consumer,这时当轮询到8081的实例时, 返回的不再是之前的错误信息,而是error,说明Hystrix的服务回调生效了.

模拟服务阻塞

接下来,模仿一下服务阻塞的情况,对eureka-client工程的/dc接口做一些修改,如下

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/dc")
public String dc() throws InterruptedException {
String services = "Services: " + discoveryClient.getServices();
log.info(services);
// 线程休息几秒
int sleepTime = new Random().nextInt(3000);
log.info("sleepTime:{}", sleepTime);

Thread.sleep(sleepTime);

return services;
}

就是让线程休眠,时间是一个3000以内的随机数,由于Hystrix默认超时时间为2000毫秒,所以这里采用了0至3000的随机数以让处理过程有一定概率发生超时来触发断路器.为了更精准地观察断路器的触发,在消费者调用函数中做一些时间记录,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/consumer")
public String consumer(){

long startTime = System.currentTimeMillis();

String result = helloService.helloService();

long endTime = System.currentTimeMillis();

log.info("Spend time:{}", endTime - startTime);
return result;
}

重新启动client和consumer,连续访问http://localhost:9000/consumer几次,我们可以观察到,当consumer的控制台中输出的Spend time大于2000的时候,就会返回error,即服务消费者因调用的服务超时从而触发熔断请求,并调用回调逻辑返回结果