本节示例代码在: https://github.com/laolunsi/spring-boot-stack


在没有良好异常处理机制的微服务架构中,可以预见的是,一旦某个服务发生故障,依赖于此服务的服务就会产生连环性的破坏,导致“雪崩效应”。
file

为了解决这一问题,提出了断路器的概念。

官网:

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.RELEASESpringCloud 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

引入consulfeignhystrix的依赖:

    <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就会使得这个类生效,返回一个默认值。

而如果使用断路器,我们来测试看看:

  1. 正常情况:
    file
  2. 异常情况,比如我们关掉service-producer:
    file

而如果不使用断路器,且被调用的服务断了,那么会报异常:

com.netflix.client.ClientException: Load balancer does not have available server for client: service-producer


三、基于Ribbon使用Hystrix

基于ribbon使用hystrix,相比于feign更加简单:

引入依赖consul-discoveryribbonhystrix

    <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,然后我们只要实现这个方法即可。

测试:

  1. 正常情况:
    file
  2. 异常情况,比如关闭service-producer:
    file

如果没有开启断路器,而请求未开启服务的接口,就会报错:

java.lang.IllegalStateException: Request URI does not contain a valid hostname: http://service_producer/producer/hello/ye


参考:

  1. 方志朋-SpringCloud第四篇-断路器(Hystrix)[Finchley版]:https://blog.csdn.net/forezp/article/details/81040990
  2. 纯洁的微笑-SpringCloud(4)-熔断器(Hystrix):http://www.ityouknow.com/springcloud/2017/05/16/springcloud-hystrix.html
Last modification:December 25th, 2019 at 11:11 pm
请作者喝杯肥宅快乐水吧!