在没有良好异常处理机制的微服务架构中,可以预见的是,一旦某个服务发生故障,依赖于此服务的服务就会产生连环性的破坏,导致“雪崩效应”。
为了解决这一问题,提出了断路器
的概念。
官网:
Netflix has created a library called Hystrix that implements the circuit breaker pattern. In a microservice architecture, it is common to have multiple layers of service calls, as shown in the following example:
Netflix提供了Hystrix库,用于实现断路器模型。在微服务架构中,通常有多层服务调用。
下面的示例演示了Hystrix
分别在ribbon和feign这两种服务调用方式中的配合使用。
本示例采用SpringBoot 2.1.7.RELEASE
和SpringCloud Greenwich.SR2
。在使用SpringBoot2.0.x和SpringCloud Finchley.RELEASE版本时,发现Feign的Hystrix开启配置是没有提示的(也会生效,但是没有代码提示,很奇怪。所以把版本换成SpringBoot 2.0.x和SpringCloud Finchley.RELEASE也可以正常运行的)
本节依然使用consul做服务中心。
service-producer
首先创建一个服务提供者service-producer
,引入consul-discovery
依赖,并添加一个测试接口即可。(与之前的教程基本一致)
这里不具体描述了,可以直接看源码:https://github.com/laolunsi/spring-cloud-examples/tree/master/05-ServiceHystrix/service-producer
或者参考之前的教程
下面我们就在使用feign和ribbon这两种服务调用方式中,分别如何去使用断路器Hystrix。
基于Feign使用Hystrix
引入consul
、feign
和hystrix
的依赖:
<properties>
<java.version>1.8</java.version>
<!--<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>-->
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入consul-discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 引入feign,用于调用其他服务的接口 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 健康检查 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- hystrix断路器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
注意这里使用的SpringCloud版本是Greenwich.SR2
,与之对应的SpringBoot版本是2.1.x.RELEASE
配置:
server:
port: 8510
spring:
application:
name: service-consumer-feign
cloud:
consul:
host: localhost
port: 8500
discovery:
register: true
instance-id: ${spring.application.name}:${server.port}
service-name: ${spring.application.name}
port: ${server.port}
# 加入这个配置,用于启动feign自带的断路器
feign:
hystrix:
enabled: true
修改启动类:
package com.example.serviceconsumerfeign;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
/*@EnableHystrix*/
public class ServiceConsumerFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerFeignApplication.class, args);
}
}
注:在配置文件中我们启用了Feign自带的Hystrix,所以即使启动类不写@EnableHystrix,断路器依然会起作用。
下面编写测试Feign的测试接口和API(与之前的教程相同):
package com.example.serviceconsumerfeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "consumer/feign")
public class ConsumerAction {
@Autowired
private ProducerApi producerApi;
@GetMapping(value = "test")
public String test(String name) {
String producerRes = producerApi.hello(name);
String res = "测试consumer/test接口,基于feign调取服务server-producer的hello接口,返回:" + producerRes;
System.out.println(res);
return res;
}
}
Api类:
package com.example.serviceconsumerfeign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* 调用service-producer服务接口
*/
@FeignClient(name = "service-producer", fallback = ProducerApiHystrix.class)
public interface ProducerApi {
@GetMapping(value = "producer/hello/{name}")
public String hello(@PathVariable("name") String name);
}
看到了没有,上面的@FeignClient
注解中,我添加了一个fallback属性,它的值对应一个自定义的class——ProducerApiHystrix
。
下面我们来实现这个class:
package com.example.serviceconsumerfeign;
import org.springframework.stereotype.Component;
/**
* ProducerApi对应的断路器
*/
@Component
public class ProducerApiHystrix implements ProducerApi {
@Override
public String hello(String name) {
return "sorry, " + name + ", this service is unavailable temporarily. We are returning the defaultValue by hystrix.";
}
}
这就是断路器类,它实际上是ProducerApi
的实现类,实现了后者需要调用的接口。那么,当请求发生异常、超时等情况时,Hysytix就会使得这个类生效,返回一个默认值。
而如果使用断路器,我们来测试看看:
- 正常情况:
- 异常情况,比如我们关掉service-producer:
而如果不使用断路器,且被调用的服务断了,那么会报异常:
com.netflix.client.ClientException: Load balancer does not have available server for client: service-producer
基于Ribbon使用Hystrix
基于ribbon使用hystrix,相比于feign更加简单:
引入依赖consul-discovery
、ribbon
、hystrix
:
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入consul-discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 引入ribbon,用于调用其他服务的接口 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!-- 健康检查 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- hystrix断路器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
配置文件不需要修改:
server:
port: 8511
spring:
application:
name: service-consumer-ribbon
cloud:
consul:
host: localhost
port: 8500
discovery:
instance-id: ${spring.application.name}:${server.port}
port: ${server.port}
service-name: ${spring.application.name}
register: true
使用@EnableHystrix注解开启断路器:
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class ServiceConsumerRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerRibbonApplication.class, args);
}
/**
* 注入RestTemplate Bean,并开启负载均衡
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
编写测试接口和对应的断路器:
package com.example.serviceconsumerribbon;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping(value = "consumer/ribbon")
public class ConsumerAction {
@Autowired
private RestTemplate restTemplate;
private static final String service_producer_name = "service-producer";
@GetMapping(value = "test")
@HystrixCommand(fallbackMethod = "testHystrix")
public String test(String name) {
String producerRes = restTemplate.getForObject(
"http://" + service_producer_name + "/producer/hello/" + name, String.class);
String res = "测试consumer/test接口,基于ribbon调取服务server-producer的hello接口,返回:" + producerRes;
System.out.println(res);
return res;
}
/**
* test接口的断路器
*/
private String testHystrix(String name) {
return "sorry, " + name + ", this service is unavailable temporarily. We are returning the defaultValue by hystrix.";
}
}
我们可以看到,ribbon服务调用中,断路器Hystrix是使用@HystrixCommand
注解在方法上进行的,对应的属性是fallBackMethod
,然后我们只要实现这个方法即可。
测试:
- 正常情况:
- 异常情况,比如关闭service-producer:
如果没有开启断路器,而请求未开启服务的接口,就会报错:
java.lang.IllegalStateException: Request URI does not contain a valid hostname: http://service_producer/producer/hello/ye
项目源码
https://github.com/laolunsi/spring-cloud-examples/tree/master/05-ServiceHystrix
参考
- 方志朋-SpringCloud第四篇-断路器(Hystrix)[Finchley版]:https://blog.csdn.net/forezp/article/details/81040990
- 纯洁的微笑-SpringCloud(4)-熔断器(Hystrix):http://www.ityouknow.com/springcloud/2017/05/16/springcloud-hystrix.html