09 Spring Cloud的集群保护框架Hystrix

1.概述

  在很多系统架构中都需要考虑横向扩展、单点故障等问题,对于一个庞大的应用集群,部分服务或机器出现问题不可避免。在出现问题时,如何减少故障的影响、保障集群的高可用,成为一个重要的课题。在微服务集群中,不管是服务器,还是客户端,都支持集群部署,本节将介绍Spring Cloud中所用的集群保护框架:Hystrix.

 1.1实际问题

    假设有如下应用程序

  用户范围销售模块,服务通过Web接口或者其他方式访问会员模块,会员模块访问数据库。如果数据库因为某些原因变得不可用,会员模块就会得到“数据库无法访问”的信息,并且会将此信息告知销售模块。在实际问题中,用户会不断地向销售模块发请求,而销售模块这继续请求会员模块,会员模块会不断地请求连接有问题的数据库直到超时,但是还是会有大量的用户请求(包括重试的)会发过来,导致整个应用不堪重负。可能情况会比这个更糟糕,用户的请求不停的发送给销售模块,而由于数据库的原因,会员模块迟迟没有响应,有可能导致整个机房的网络阻塞,受害的不仅仅是这个应用程序,机房中的所有服务都有可能因为网络的原因而瘫痪。

 1.2传统的解决方式

    对于前面遇到的实际问题,可以选择在连接数据库的同时加上超时的配置,让会员模块快速响应。但这仅仅是解决了其中的一种情况,在实际情况中,会员模块有可能出现问题,例如部分线程阻塞、进程假死等,在这些情况下,对外的服务销售模块面对大量的用户与有故障的会员模块,仍然无法独善其身,前面所说的问题依旧会出现。

  在当今的互联网时代,面对大量的用户请求,传统或者单一的解决方式在复杂的急群中显得力不从心,我们需要跟优雅更完善的方案来解决这些问题。

2.集群容错框架Hystrix

  在分布式环境中,总会有一些被依赖的服务会失效,例如像网络短暂无法访问、服务器宕机等情况。Hystrix是Netflix下的一个java库,Spring Cloud将Hystrix整合到Netflix项目中,Hystrix通过添加延迟阈值以及容错的逻辑,来帮助我们控制分布式系统间组件的交互。Hystrix通过隔离服务间的访问点、停止他们之间的级联故障、提供可回退操作来实现容错。

  例如我们之前讲到的问题,如果数据库层面出现问题,销售模块在访问会员模块时必然会出现超时的情况,此时可以将会员模块隔离开来,销售模块短时间内不再调用会员模块,并且会快速响应用户的请求,从而保证销售模块自身乃至整个集群的稳定性,这是Hystrix可以解决的问题。加入了容错机制,当会员模块或者数据库不可用时,销售模块将对其进行“熔断”,在一定时间内,销售模块不会再调用会员模块,以维持自身的稳定,结构图就变成下面的图了

        

  Hystrix主要实现以下的功能:

  > 当所依赖的网络服务发生延迟或者失败时,对访问的客户端程序进行保护,就像上面的例子对销售模块进行保护一样;

  > 在分布式系统中停止级联故障;

  > 网络服务恢复正常后,可以快速恢复客户端的访问能力;

  > 调用失败时执行服务回退;

  > 可支持实时监控、报警和其他操作。

 3.第一个Hystrix程序

  本例将编写一个简单的Hello World程序,展示Hystrix的基本功能。

 3.1 创建服务提供者

    使用SpringBoot的spring-boot-starter-web项目,创建一个普通的web项目,发布两个测试服务用于测试,项目目录及代码清单如下

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>

    <groupId>com.triheart</groupId>
    <artifactId>hystrixserver</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>1.5.4.RELEASE</version>
        </dependency>
    </dependencies>
</project>
View Code

ServerApp.java

package com.triheart.hystrixserver;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

/**
 * @author 阿遠
 * Date: 2018/8/29
 * Time: 21:16
 */
@SpringBootApplication
public class ServerApp {
    public static void main(String[] args) {
        new SpringApplicationBuilder(ServerApp.class).run(args);
    }
}
View Code

    MyController.java

package com.triheart.hystrixserver;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * @author 阿遠
 * Date: 2018/8/29
 * Time: 21:18
 */
@RestController
public class MyController {

    @GetMapping("/normalHello")
    public String normalHello(HttpServletRequest request) {
        return "normal Hello!";
    }

    @GetMapping("/errorHello")
    public String errorHello(HttpServletRequest request) throws Exception{
        // 模拟处理让线程睡眠10秒
        Thread.sleep(10000);
        return "error Hello";
    }
}
View Code

  在MyController控制器中,我们提供了一个正常的服务,提供了一个需要等待10秒才有返回的服务。

 3.2 创建客户端并使用Hystrix

  使用Hystri来请求Web服务,与原来的方式不太一样,新建项目hystrixclient,项目的目录结构如下

  

  添加相关的依赖,pom.xml的代码清单如下

  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>

    <groupId>com.triheart</groupId>
    <artifactId>hystrixclient</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-core</artifactId>
            <version>1.5.12</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <version>1.7.25</version>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>
    </dependencies>
</project>
View Code

  客户端除了要使用Hystrix外,还会使用HttpClient模块来访问Web服务,因此要加入httpclient的依赖。新建命令类

  HelloCommand.java

package com.triheart.hystrixclient;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

/**
 * @author 阿遠
 * Date: 2018/8/29
 * Time: 21:43
 */
public class HelloCommand extends HystrixCommand<String> {

    private String url;
    CloseableHttpClient httpclient;

    public HelloCommand(String url) {
        // 调用父类的构造器,设置命令组的key,默认用来作为线程池的key
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        // 创建HttpClient客户端
        this.httpclient = HttpClients.createDefault();
        this.url = url;
    }

    protected String getFallback() {
        System.out.println("执行 HelloCommand 的回退方法");
        return "error";
    }

    protected String run() throws Exception {
        try {
            // 调用 GET 方法请求服务
            HttpGet httpget = new HttpGet(url);
            // 得到服务响应
            HttpResponse response = httpclient.execute(httpget);
            // 解析并返回命令执行结果
            return EntityUtils.toString(response.getEntity());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}
View Code

  新建运行类,执行HelloCommand命令

  HelloMain.java

package com.triheart.hystrixclient;

/**
 * @author 阿遠
 * Date: 2018/8/29
 * Time: 21:49
 */
public class HelloMain {

    public static void main(String[] args) {
        // 请求正常的服务
        String normalUrl = "http://localhost:8080/normalHello";
        HelloCommand command = new HelloCommand(normalUrl);
        String result = command.execute();
        System.out.println("请求正常的服务,结果:" + result);
    }
}
View Code

  正常情况下,直接调用HttpClient的API来请求Web服务,而此处命令类和运行类则通过执行命令来执行调用的工作。在命令类HelloCommond中,实现了父类的run方法,使用HttpClient调用服务的过程,都放到了该方法中。运行HelloMain类,可以看到,结果与平常调用Web服务无异。

  假设我们所调用的Hello服务发生故障,导致无法正常访问,那么对于客户端来说,该如何自保呢?下面将演示调用异常服务的情况

  在HelloCommand类中,加入回退方法。在运行类中,调用发生故障的服务

  HelloErrorMain.java

package com.triheart.hystrixclient;

/**
 * @author 阿遠
 * Date: 2018/8/29
 * Time: 21:49
 */
public class HelloErrorMain {

    public static void main(String[] args) {
        // 请求异常的服务
        String normalUrl = "http://localhost:8080/errorHello";
        HelloCommand command = new HelloCommand(normalUrl);
        String result = command.execute();
        System.out.println("请求异常的服务,结果:" + result);
    }
}
View Code

  运行对应的类,可以看到控制台输入如下

  根据结果可知,回退方法被执行了。本例中调用的errorHello服务,会阻塞10秒才有返回。默认情况下,如果调用Web服务无法再一秒内完成,那么将触发回退。

  回退更像是一个备胎,当请求的服务无法正常返回时,就调用该备胎来实现。这样就可以很好的保护客户端,服务端所提供的服务受网络等条件的制约,如果有服务真的需要10秒才能返回结果,而客户端有没有容错的机制,后果就是客户端将一直等待返回,直到网络超时活着服务有响应,而外界会一直不停地发送请求给客户端,最终导致的结果就是客户端因为请求过多而瘫痪。

猜你喜欢

转载自www.cnblogs.com/a-yuan/p/9557313.html