Spring Cloud Hystrix介绍

  在微服务架构中,通常会存在多个服务层调用的情况,如果基础服务出现故障可能会发生级联传递,导致整个服务链上的服务不可用
为了解决服务级联失败这种问题,在分布式架构中产生了断路器等一系列的服务保护机制。分布式架构中的断路器,有些类似于我们生活中的空气开关,当电路发生短路等情况时,空气开关会立刻断开电流,以防止用电火灾的发生。
  在Spring Cloud中,Spring Cloud Hystrix就是用来实现断路器、线程隔离等服务保护功能的。Spring Cloud Hystrix是基于Netflix的开源框架Hystrix实现的,该框架的使用目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
与空气开关不能自动重新打开有所不同的是,断路器是可以实现弹性容错的,在一定条件下它能够自动打开和关闭,其使用时主要有三种状态,如图5-2所示。

  在图5-2中,断路器的开关由关闭到打开的状态是通过当前服务健康状况(服务的健康状况=请求失败数/请求总数)和设定阈值(默认为10秒内的20次故障)比较决定的。当断路器开关关闭时,请求被允许通过断路器,如果当前健康状况高于设定阈值,开关继续保持关闭;如果当前健康状况低于设定阈值,开关则切换为打开状态。当断路器开关打开时,请求被禁止通过;如果设置了fallback方法,则会进入fallback的流程。当断路器开关处于打开状态,经过一段时间后,断路器会自动进入半开状态,这时断路器只允许一个请求通过;当该请求调用成功时,断路器恢复到关闭状态;若该请求失败,断路器继续保持打开状态,接下来的请求会被禁止通过。
  Spring Cloud Hystrix能保证服务调用者在调用异常服务时快速地返回结果,避免大量的同步等待,这是通过HystrixCommand的fallback方法实现的
  但采用fallback的方式可以给用户一个友好的提示结果,这样就避免了其他服务的崩溃问题。

Spring Cloud Hystrix的使用

  了解了为什么需要使用Hystrix,以及Hystrix在使用时的三种状态后,下面通过一个案例来讲解如何在应用中使用SpringCloud Hystrix来实现断路器的容错功能,并使用FallBack()方法为熔断或异常提供备选方案。案例中主要涉及到以下几个工程:
  ·xcservice-eureka-server工程:服务注册中心,端口为8761;
  ·xcservice-eureka-order工程:服务提供者,需要启动两个订单实例,其端口号分别为7900和7901;
  ·xcservice-eureka-user-hystrix工程:服务消费者,使用Ribbon实现的,端口号为8030,此工程可以在xcservice-eu-reka-user工程内容的基础上修改。
  在上述三个工程中,xcservice-eureka-server和xcservice-eureka-order可以使用第4章中所创建的工程,而xcservice-eureka-user-hystrix工程需要重新搭建和编写。其具体实现过程如下。
  (1)创建xcservice-eureka-user-hystrix工程,并在其pom.xml中引入eureka和hystrix的依赖,如文件5-1所示。
  文件5-1 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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.xc</groupId>
        <artifactId>xcservice-springcloud</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>com.xc</groupId>
    <artifactId>xcservice-eureka-user-hystrix</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>xcservice-eureka-user-hystrix</name>
    <description>服务消费者</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <dependency><!-- Hystrix依赖 -->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  (2)编辑配置文件。在配置文件中添加Eureka服务实例的端口号、服务端地址等,如文件5-2所示。
  文件5-2 application.yml

server:
  port: 8030 # 指定该Eureka实例的端口号

eureka:
  instance:
    prefer-ip-address: true  # 是否显示主机的IP
    #instance-id: ${spring.cloud.client.ipAddress}:${server.port} #将Status中的显示内容也以“IP:端口号”的形式显示
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/ # 指定Eureka服务端地址

spring:
  application:
    name: xcservice-eureka-user-hystrix # 指定应用名称

  (3)在工程主类Application中使用@EnableCircuitBreaker注解开启断路器功能,编辑后如文件5-3所示。
  文件5-3 Application.java

package com.xc.xcserviceeurekauserhystrix;

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

@SpringBootApplication
@EnableCircuitBreaker
@EnableEurekaClient
public class XcserviceEurekaUserHystrixApplication {

    /**
     * 实例化RestTemplate
     * RestTemplate是Spring提供的用于访问Rest服务的客户端,
     * 它提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(XcserviceEurekaUserHystrixApplication.class, args);
    }

}

  (4)修改用户控制器类。在findOrdersByUser()方法上添加@HystrixCommand注解来指定回调方法,编辑后如文件5-4所示。
  文件5-4 UserController.java

package com.xc.xcserviceeurekauserhystrix.controller;

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.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * http://localhost:8030/findOrdersByUser/1
     * 查找与用户相关的订单
     */
    @GetMapping("/findOrdersByUser/{id}")
    @HystrixCommand(fallbackMethod = "fallbackInfo")
    public String findOrdersByUser(@PathVariable String id) {
        // 假设用户只有一个订单,并且订单id为123
        // int oid = 123;
        // return restTemplate.getForObject("http://localhost:7900/order/" + oid, String.class);
        return restTemplate.getForObject("http://xcservice-eureka-order/order/" + id, String.class);
    }

    /**
     * 返回信息方法
     */
    public String fallbackInfo(@PathVariable String id) {
        return "服务不可用,请稍后再试!";
    }

}

  在上述代码中,@HystrixCommand注解用于指定当前方法发生异常时调用的方法,该方法是通过其属性fallbackMethod的属性值来指定的。这里需要注意的是,回调方法的参数类型以及返回值必须要和原方法保持一致。

  (5)分别启动注册中心、服务提供者(7900和7901)和服务消费者。
  当多次访问http://localhost:8030/findOrdersByUser/1时,后台将通过轮询的方式分别访问7900和7901端口所对应的服务。此时如果停止7901对应的服务,那么多次执行访问时,在轮询到7901端口对应的服务时,页面将显示提示信息(服务不可用,请稍后再试!),这也就说明Spring Cloud Hystrix的服务回调(fallbackInfo()方法)生效。