Spring Cloud-Hystrix 断路器

  在Spring Cloud Netflix栈中,每个微服务都以HTTP接口的形式暴露自身服务,因此在调用远程服务时就必须使用到HTTP客户端。我们可以使用JDK原生的URLConnection、Apache的Http Client、Netty的异步HTTP Client,还有之前我们使用到Spring的RestTemplate,这些都可以实现远程调用。

1.Hystrix是什么

Hystrix(https://github.com/Netflix/Hystrix)是Netflix(https://www.netflix.com/global)的一个开源项目。

在分布式系统中,总会有一些必不可免发生的问题,比如超时、异常、服务提供者不可用等,如何保证在依赖出问题的情况下,
不会导致整体服务失败,对延迟和故障提供更强大的容错能力,这个就是Hystrix需要做的事情。

Hystrix提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用。

2.FallBack机制

有时候我们的提供者会出现故障,导致消费服务的一方也会级联故障,Hystrix里面的FallBack就是为了解决这种情况的发生,
当提供者出现问题的时候,消费方可以指定若提供服务发生故障而默认调用的逻辑。

这里写图片描述

Spring cloud官方文档其实介绍的很详细,这里给大家一个简单的例子感受一下FallBack的魅力。

这里Eureka Server和之前一样,只修改服务方的代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloud_Demo</artifactId>
        <groupId>com.ithzk.spring.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>consumer-order-hystrix</artifactId>

    <name>consumer-order</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- spring boot test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>1.4.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>1.4.3.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.7.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.20.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>
主程序加上@EnableCircuitBreaker注解表示启动断路器

OrderApp.java

package com.ithzk.spring.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * Hello world!
 *
 */
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker//启用熔断
public class OrderApp {

    //相当于xml中的bean标签 用于调用当前方法获取到指定的对象
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

    public static void main( String[] args )
    {
        SpringApplication.run(OrderApp.class);
    }
}
这里加上@HystrixCommand(fallbackMethod = "defaultMethod")注解
fallbackMethod属性标识服务故障默认调用的备用逻辑的方法名

OrderController.java

package com.ithzk.spring.cloud.controller;

import com.ithzk.spring.cloud.entity.User;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @author hzk
 * @date 2018/5/13
 */
@RestController
public class OrderController {

    //spring 提供用于访问rest接口的模板对象
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private EurekaClient eurekaClient;

    @Value("${user.url}")
    private String url;

    @GetMapping("/order/{id}")
    @HystrixCommand(fallbackMethod = "defaultMethod")
    public User getOrder(@PathVariable Integer id){
        //访问提供者 获取数据 通过rest访问获取的json数据转换为的User对象
        //PROVIDER-USER 为eureka中提供者注册服务名称
        InstanceInfo instanceInfo = eurekaClient.getNextServerFromEureka("PROVIDER-USER", false);
        //获取接口项目地址
        String homePageUrl = instanceInfo.getHomePageUrl();

        User user = restTemplate.getForObject(homePageUrl + "/user/" +id, User.class);
        return user;
    }

    /**
     * 失败后执行的回调
     * @param id
     * @return
     */
    public User defaultMethod(Integer id){
        return new User(id+1000,"faild",88);
    }
}
这样就配置成功,启动Eureka Server以及消费者,服务者不启动模拟服务提供失败,访问
http://localhost:8900/order/55依然可以看到我们想要的结果,说明HyStrix应用成功
这就是Hystrix中FallBack的简单应用

3.上下文传播

这里写图片描述

上图中若getOrder服务提供出现问题,则会调用fallback指定的方法,实质上getOrder和调用fallback指定方法都在自己独立的
线程中,若我们getOrder中有一个ThreadLocal线程内部变量想在defaultMethod中调用的话,需要在@HystrixCommand中加一个属性commandProperties
package com.ithzk.spring.cloud.controller;

import com.ithzk.spring.cloud.entity.User;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @author hzk
 * @date 2018/5/13
 */
@RestController
public class OrderController {

    //spring 提供用于访问rest接口的模板对象
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private EurekaClient eurekaClient;

    @Value("${user.url}")
    private String url;

    @GetMapping("/order/{id}")
    @HystrixCommand(fallbackMethod = "defaultMethod",commandProperties = {
            @HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE")
     })//"execution.isolation.strategy" 默认不建议大家修改,如果大家遇到问题了再修改,否则不建议
    public User getOrder(@PathVariable Integer id){
        //访问提供者 获取数据 通过rest访问获取的json数据转换为的User对象
        //PROVIDER-USER 为eureka中提供者注册服务名称
        InstanceInfo instanceInfo = eurekaClient.getNextServerFromEureka("PROVIDER-USER", false);
        //获取接口项目地址
        String homePageUrl = instanceInfo.getHomePageUrl();

        User user = restTemplate.getForObject(homePageUrl + "/user/" +id, User.class);
        return user;
    }

    /**
     * 失败后执行的回调
     * @param id
     * @return
     */
    public User defaultMethod(Integer id){
        return new User(id+1000,"faild",88);
    }
}
execution.isolation.strategy 这个默认不建议修改

4.Health健康指标

Hystrix里面包含一个健康指标的概念,只需要引用一个依赖,就能实时查看到服务的状态

pom.xml

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>
加入依赖之后只需访问http://localhost:8900/hystrix.stream

这里写图片描述

这里面就会实时刷新出服务的状态,如果提供服务正常,isCircuitBreakerOpen这个值可以看到是false的,
对于断路器里面有一个性质,若我们将服务提供关闭,然后去调用消费服务,由上面可以看出若服务异常他会调
用默认FallBack方法,若同一个接口里服务异常次数过多,重复调用FallBack指定方法,则健康指标会出现以下状态

这里写图片描述
这里写图片描述

当健康指标标识为此,则范围时间内该接口被调用多少次都默认走FallBack方法,
直到多久时间之后有多次正常调通该接口的服务,则该状态会重置,则恢复开始状态,服务异常才调FallBack Method

5.Feign Hystrix-fallback支持

@FeignClient启用回退,只需设置fallback属性,配置上对应类就可以实现

pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>1.4.3.RELEASE</version>
</dependency>

application.yml

server:
  port: 8900
spring:
  application:
    name: consumer-order-feign-hystrix
user:
  url: http://localhost:7900/user/
eureka:
  client:
    service-url:
      defaultZone:  http://user:user@localhost:8888/eureka/
feign:
  hystrix:
    enabled: true # feign启用fallback机制
OrderApp主程序添加@EnableCircuitBreaker注解启用熔断

OrderApp.java

package com.ithzk.spring.cloud;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * Hello world!
 *
 */
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
//启用熔断,启用时若接口调用失败次数过多则会启用熔断,有一定保护时间,默认再次请求该接口则直接调用fallback
//若未启用则每次都会调用接口,直到接口内部每次出错才会代用fallback
public class OrderApp {

    //相当于xml中的bean标签 用于调用当前方法获取到指定的对象
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

    public static void main( String[] args )
    {
        SpringApplication.run(OrderApp.class);
    }
}
这里自定义Feign客户端fallback属性配置实现该客户端的实现类

CustomFeignClient.java

package com.ithzk.spring.cloud.feign;

import com.ithzk.spring.cloud.entity.User;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

/**
 * @author hzk
 * @date 2018/5/20
 */
@FeignClient(name="PROVIDER-USER",fallback = CustomFeignClientHystrix.class)
//配置fallback之后若调用该接口失败会默认调用配置fallback类中同名的实现方法
public interface CustomFeignClient {

    //C版本的 spring 是不能写 GETMAPPING 的必须用RequestMapping
    @GetMapping("/user/{id}")
    User getOrder(@PathVariable("id") Integer id);

    /**
     * 如果传递复杂参数,feign默认都会以方式去请求
     * 无法访问,提供者必须为post方式消费者才可使用,如果非要使用get传递多个数据,只能以普通方式传递
     * @param user
     * @return
     */
    @PostMapping("/get_user")
    User getUser(User user);

}
自定义FeignClient实现类,重写客户端中接口方法,用于客户端调用失败默认调用

CustomFeignClientHystrix.java

package com.ithzk.spring.cloud.feign;

import com.ithzk.spring.cloud.entity.User;
import org.springframework.stereotype.Component;

/**
 * @author hzk
 * @date 2018/5/28
 */
@Component
public class CustomFeignClientHystrix implements CustomFeignClient{
    @Override
    public User getOrder(Integer id) {
        User user = new User();
        user.setId(-1000);
        user.setName("我是fallback");
        user.setAge(888);
        return user;
    }

    @Override
    public User getUser(User user) {
        return user;
    }
}

OrderController.java

package com.ithzk.spring.cloud.controller;

import com.ithzk.spring.cloud.entity.User;
import com.ithzk.spring.cloud.feign.CustomFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author hzk
 * @date 2018/5/13
 */
@RestController
public class OrderController {

    @Autowired
    private CustomFeignClient customFeignClient;

    @GetMapping("/order/{id}")
    public User getOrder(@PathVariable Integer id){
        User user = customFeignClient.getOrder(id);
        return user;
    }

    @GetMapping("/get_user")
    public User getUser(User user){
        User feignUser = customFeignClient.getUser(user);
        return feignUser;
    }
}
此时启动Eureka Server,启动消费者Order,不启动提供者User,访问http://localhost:8900/order/xx
可以发现仍然成功,返回数据是fallback配置类中重写方法的数据,则说明FeignClient配置成功

这里写图片描述

6.FallBackFactory实现回退

pom.xml 和 application.yml、OrderApp同上一致

编写一个自定义FallBack工厂,实现FallbackFactory

CustomFeignClientFactory.java

package com.ithzk.spring.cloud.feign;

import com.ithzk.spring.cloud.entity.User;
import feign.hystrix.FallbackFactory;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

/**
 * @author hzk
 * @date 2018/5/20
 */
@Component
public class CustomFeignClientFactory implements FallbackFactory<CustomFeignClient>{

    @Override
    public CustomFeignClient create(Throwable throwable) {
        return new CustomFeignClient() {
            @Override
            public User getOrder(Integer id) {
                User user = new User();
                user.setId(-1111);
                user.setName("我是fallback");
                user.setAge(888);
                return user;
            }

            @Override
            public User getUser(User user) {
                return user;
            }
        };
    }
}
@FeignClient配置fallbackFactory属性

CustomFeignClient.java

package com.ithzk.spring.cloud.feign;

import com.ithzk.spring.cloud.entity.User;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

/**
 * @author hzk
 * @date 2018/5/20
 */
@FeignClient(name="PROVIDER-USER",fallbackFactory = CustomFeignClientFactory.class)
public interface CustomFeignClient {

    //C版本的 spring 是不能写 GETMAPPING 的必须用RequestMapping
    @GetMapping("/user/{id}")
    User getOrder(@PathVariable("id") Integer id);

    /**
     * 如果传递复杂参数,feign默认都会以方式去请求
     * 无法访问,提供者必须为post方式消费者才可使用,如果非要使用get传递多个数据,只能以普通方式传递
     * @param user
     * @return
     */
    @PostMapping("/get_user")
    User getUser(User user);

}
同上fallback一样,启动服务调用消费,出现默认方法,则配置成功

这里写图片描述

7.Hystrix-DashBoard仪表板

Hystrix提供了一个仪表板实现,他可以反映每个断路器的运行状况

这里我们新建一个项目hystrix-dashboard

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
    <version>1.4.3.RELEASE</version>
</dependency>
application.yml这里暂时只需简单配置服务端口即可

application.yml

server:
  port: 9999
主服务添加@EnableHystrixDashboard注解开启仪表板

DashBoardApp.java

package com.ithzk.spring.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

/**
 * Hello world!
 *
 */
@SpringBootApplication
@EnableHystrixDashboard
public class DashBoardApp
{
    public static void main( String[] args )
    {
        SpringApplication.run(DashBoardApp.class);
    }
}
只需简单几步即可,启动服务,访问localhost:9999/hystrix即可访问到仪表板首页

这里写图片描述

这里可以看到需要填写对应的服务健康指标地址,启动Eureka server以及上面带有健康指标和回路的任一服务
这里我只启动了端口8900的消费服务,未启动对应的提供服务,访问消费接口出现调用设置的默认方法数据
此时只需将localhost:8900/hystrix.stream填入dashboard首页中

这里写图片描述

拟定一个自定义标题即可进入到断路器状态界面,这里可以很好的看到断路器的各种状态

这里写图片描述

猜你喜欢

转载自blog.csdn.net/u013985664/article/details/80424769