在微服务架构中,我们将服务拆分成了许多独立的单元,这些服务部署在不同的地方,服务之间通过服务注册,服务消费互相调用。但是在服务的调用过程当中可能由于网络的原因或者是被调用方自身的故障出现响应延迟的情况。而这种情况也会导致调用方出现响应延迟,而此时如果请求不断,会造成请求的积压 从而导致服务瘫痪。
由于存在着许多的服务单元,单一的服务出现问题,可能会因为依赖的关系导致故障的蔓延。举个例子来说:在一个项目中存在着 订单服务,库存服务,商品服务... 在创建一个订单的时候,需要判断库存是否足够,扣减库存,订单服务需要依赖于库存服务,当在调用的时候,库存服务由于自身的处理逻辑处理慢,导致订单服务长时间获取不到响应,创建订单失败。此时在高并发的状况下,调用库存服务的线程因为等待而被挂起,后续请求创建订单的线程被阻塞,最终导致订单服务也不可用。这样的情况比单体应用更加不稳定,为了解决这样的情况出现了断路器这样的服务容错机制。
针对上述的问题,Spring Cloud Hystrix实现了断路器,通过对调用的请求进行监控,向调用方返回一个错误的信息而不是长时间的等待,这样就不会由于线程长时间的等待不释放造成故障在分布式环境中的蔓延。
Spring Cloud Hystrix 实现了断路器,线程隔离等一系列的服务保护功能。他是基于Netflix的开源框架 Hystrix实现的,该框架的目标在于通过控制那些访问远程系统,服务,第三方框架的节点,对延迟和故障有更加强大的容错能力。Hystrix具备服务降级,服务熔断,线程和信号隔离,请求缓存,请求合并以及服务监控等强大的功能。
首先构建一个服务互相调用的场景:
需要一个服务注册中心 三个Eureka Client,其中包括一个服务调用方我们使用Ribbon来实现,还有两个提供相同服务的实例,启动在不同的端口。
注册中心:
首先是注册中心: 启动在8080端口。
server:
port: 8080
eureka:
instance:
hostname: localhost
server:
#关闭自我保护
enable-self-preservation: false
client:
#关闭向注册中心注册自己
register-with-eureka: false
#由于自己就是注册中心 自己就维护着服务实例 不需要去检索服务
fetch-registry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
通过在浏览器中访问 http://localhost:8080 ,可以发现当前还没有服务注册。(关于服务提供者服务消费者的代码可以看 Spring Cloud Ribbon 服务消费者 )
服务消费者:
启动在8092端口,服务名为:consumer。
server:
port: 8092
spring:
application:
name: consumer
eureka:
instance:
hostname: localhost
client.serviceUrl.defaultZone: http://localhost:8080/eureka/
启动项目之后刷新 http://localhost:8080 发现消费者已经成功的注册了。
服务提供方
我们在这里启动两个提供方的实例来进行测试:(通过指定端口为随机数启动两次,同时指定不同的服务的实例id)
server:
port: ${random.int[1000,2000]}
spring:
application:
name: hello-service
eureka:
instance:
hostname: localhost
instance-id: ${random.int[100,200]}
client.serviceUrl.defaultZone: http://localhost:8080/eureka/
再次观察注册中心
发现和我们预想中的一样 ,提供方有两个实例。
实际测试中出现的问题:在指定多实例的随机端口时发现通过 ${random.int}的方式来指定 在实际的使用中注册中心的端口与实际启动的端口不符? 改用指定随机端口 server.port=0 这样设置的话 是正常的。 !!!!
大家可以自己进行测试一下。 http://localhost:8080/eureka/apps 可以通过这样的方式来查看实际的实例详情。
断路器的测试详情:
首先进行消费者端的测试:直接在浏览器中访问:http://localhost:8092/consumer-hello
在没有加入断路器的情况下,关闭一个服务提供者的实例,浏览器会出现下面的输出:
添加断路器:
①首先在服务消费方的pom.xml 中添加hystrix的依赖:
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
②在主类上添加注解: @EnableCircuitBreaker
或者干脆将主类上的注解直接换为 @SpringCloudApplication 点进去观察源码 可以发现包含有注解 @SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker 说明一个典型的SpringCloud服务 是包含服务注册 服务发现以及断路器的。
③修改消费者端原先的代码 增加原来的逻辑处理到service层;
package com.wc.study.ribbon.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.wc.study.ribbon.bean.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* Created by wangchen on 2019/1/23.
*/
@Service
public class HelloService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "errorHello")
public String hello(){
return restTemplate.getForObject("http://HELLO-SERVICE/hello?name={1}",String.class,"test");
}
@HystrixCommand(fallbackMethod = "saveFallBack")
public String save(){
Student student = new Student("乔巴",12);
ResponseEntity entity = restTemplate.postForEntity("http://HELLO-SERVICE/save",student,Student.class);
return entity.getBody().toString();
}
private String errorHello(){
return "error";
}
private String saveFallBack(){
Student student = new Student("error",12);
return student.toString();
}
}
package com.wc.study.ribbon.controller;
import com.wc.study.ribbon.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by wangchen on 2019/1/22.
*/
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/consumer-hello")
public String index(){
return helloService.hello();
}
@GetMapping("/consumer-save")
public String save(){
return helloService.save();
}
}
重新启动之后,测试访问可以正常进行。
测试开始:
首先我们将提供方的服务实例断开一个,直接停止,然后再次调用消费者端的代码会发现直接返回了error信息 而没有报错,说明我们的断路器起作用了。
然后我们重试一种方法,模仿服务提供方处理逻辑缓慢,直接放线程随机的阻塞 1~3 s。并在消费者端打印执行的时间。
生产者端代码改动:
消费者端代码改动:
观察打印的时间:
会发现时间在超过2s的时候 会触发断路器打印出error。
在实际的使用中我们可能会用Hystrix命令来实现服务的降级,但是在一些情况下也可以不使用:
忽略某些异常:
在碰到调用过程中出现异常时,Hystrix会调用我们在@HystrixCommand(fallbackMethod="XXX") 配置的XXX方法。我们也可以配置在遇到什么异常时不触发。 ignoreException={}
进行异常捕获:
在调用的过程中如果触发了异常,我们需要知道具体的异常情况的话,也可以直接在fallbackMethod 指定的方法中 直接传入参数Throwable 即可进行异常捕获。