本节示例代码在: https://github.com/laolunsi/spring-boot-stack
在没有良好异常处理机制的微服务架构中,可以预见的是,一旦某个服务发生故障,依赖于此服务的服务就会产生连环性的破坏,导致“雪崩效应”。
为了解决这一问题,提出了 断路器
的概念。
官网:
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
,引入 consul-discovery
依赖,并添加一个测试接口即可。(与之前的教程基本一致)
这里不具体描述了,可以直接看源码: https://github.com/laolunsi/spring-cloud-examples/tree/master/05-ServiceHystrix/service-producer
或者参考之前的教程
下面我们就在使用feign和ribbon这两种服务调用方式中,分别如何去使用断路器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就会使得这个类生效,返回一个默认值。
而如果使用断路器,我们来测试看看:
而如果不使用断路器,且被调用的服务断了,那么会报异常:
com.netflix.client.ClientException: Load balancer does not have available server for client: service-producer
基于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
,然后我们只要实现这个方法即可。
测试:
如果没有开启断路器,而请求未开启服务的接口,就会报错:
java.lang.IllegalStateException: Request URI does not contain a valid hostname: http://service_producer/producer/hello/ye
参考: