为什么要用断路器?
在微服务架构中,我们将系统拆分成了很多个服务单元,这些单元通过在服务注册中心注册与订阅互相依赖。这就可能造成一个问题,如果出现网络延迟或者依赖的服务出现问题,会直接导致调用方的对外服务也出现延迟,最终会导致调用任务不断积压导致系统崩溃。为了解决这个问题,产生了断路器等一系列的的服务保护机制。
断路器的作用就是在分布式系统中,当某个服务单元出现故障时,通过断路器的故障监控向服务的调用方返回一个错误响应,而不是长时间的等待(类似电路发生短路,通过断路器熔断保险丝来保障安全)。这样就不会使得线程因为调用故障服务长时间得不到释放,最终蔓延到整个系统导致系统崩溃。Spring Cloud Hystrix是基于Netflix的Hystrix实现的,具备服务熔断、服务降级、线程和信号隔离、请求缓存、请求合并以及服务监控等强大的功能。
模拟服务宕机故障
分别启动以下Spring Boot项目:
- eureka-server-vFinchley.Rc2:服务注册中心。
- eureka-client-vFinchley.Rc2:服务提供者,分别以8081和8082端口启动两个实例。
- ribbon-consumer-vFinchley.Rc2:消费者。
调用localhost:3333/consumer接口,发现消费者会以轮询的方式调用两个服务提供者,现在关闭8082端口的实例,再次调用接口时会出现以下错误:
Hystris快速实现
第一步,创建项目hystrix-vFinchley.Rc2,勾选如下依赖:
在pom.xml内多了Hystrix的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>
spring-cloud-starter-netflix-hystrix
</artifactId>
</dependency>
第二步,在主类Application中配置@EnableCircuitBreaker注解开启断路保护功能,你也可以直接使用@SpringCloudApplication注解代替@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker,查看源码会发现@SpringCloudApplication包含了这三个注解。
//@SpringBootApplication
//@EnableDiscoveryClient
//@EnableCircuitBreaker
@SpringCloudApplication
public class Application {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
第三步,创建HelloService,注入RestTemplate实例调用服务并加入@HystrixCommand注解来指定回调方法,添加回调方法。
@Service("helloService")
public class HelloService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod="helloFallback")
public String hello(){
return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
}
public String helloFallback(){
return "error";
}
}
第四步,创建HelloController并注入HelloService实例并调用hello()方法。
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("hello")
public String hello(){
return helloService.hello();
}
}
第五步,修改配置文件application.yml
spring:
application:
name: hystrix #为服务命名
server:
port: 3334 #指定端口
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka/ #指定服务注册中心位置
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
接下来启动服务注册中心、启动8081和8082端口的hello-service服务实例、启动消费者hystrix。请求localhost:3334/hello接口 ,依然会轮询两个hello-service服务实例,接下来关掉8082端口的服务,当轮询到8082端口的服务实例后不会报错而是返回error。
当然你也可以模拟服务阻塞的情况,断路器默认超时时间为1000ms,我们用代码模拟请求阻塞。
第一步,修改服务提供者hello-service的hello接口,让它随机睡眠0~3000ms,并且打印睡眠的时间。
@RestController
@RequestMapping("hystrix")
public class HystrixController {
@GetMapping("hello")
public String index(){
System.out.println("hello");
try {
//休眠0-3000ms
int sleepTime = new Random().nextInt(3000);
Thread.sleep(sleepTime);
System.out.println(sleepTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "hello";
}
}
第二步,修改消费者hystrix的HelloService打印调用服务的响应时间。
@HystrixCommand(fallbackMethod="helloFallback")
public String hello(){
long start = System.currentTimeMillis();
String result = restTemplate.getForEntity("http://HELLOSERVICE/hystrix/hello",String.class)
.getBody();
long end = System.currentTimeMillis();
System.out.println(end-start);
return result;
}
public String helloFallback() {
return "error";
}
发现每次控制台打印超过1000ms就会返回error,即服务消费者因为调用服务超时从而触发熔断请求,并调用回调逻辑返回结果,这就初步实现了一个简单的断路器。