1.Hystrix介绍

Hystrix是Netflix下的一个开源程序库。该库旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包(request collapsing,即自动批处理,译者注),以及监控和配置等功能。

我们的分布式系统里除了自己的各种服务功能模块间的相互调用,有时也会调用第三方服务。为用户提供的某一个服务,往往是经过好几个服务的调用才完成的,如果在调用的过程中,有一个服务挂掉了或是响应延时都回造成整个服务中的其他节点出现异常或处于长时间的请求中,这样最终的结果就是导致整个服务瘫痪。我们需要优雅的处理服务之间调用出现的异常以及故障。而Hystrix可以帮我们完成这些,通过它提供的会退机制以及断路功能,我们可以自己订制面对被调用服务出现故障时,调用方如何马上响应避免等待。降低了服务之间的耦合度。

2.Hystrix的运作流程

143603_GJzI_3665821.png

3.开始使用Hystrix

我们还是在上一篇的基础上进行扩展。Hystrix实际上是在保护调用方,所以我们在invokerService中加入Hystrix依赖:

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Edgware.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

在启动类上添加注解@EnableCircuitBreaker

package com.demo;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
@ServletComponentScan
public class App {
  
    public static void main(String[] args) {
        new SpringApplicationBuilder(App.class).run(args);
    }
}

因为是在Feign的基础上使用Hystrix,并且Feign本身支持Hystrix,所以只要在配置文件里开启就好了:

spring:
  application:
    name: invoker-server1
server:
  port: 8093
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
feign:
  hystrix:
    enabled: true

改造调用客户端CallClient:

package com.demo;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;

import com.demo.CallClient.HelloCallBack;

@FeignClient(name="provider-server",fallback = HelloCallBack.class)  //申明调用服务
public interface CallClient {
    @GetMapping("/api/hello")
    String hello();
    
    @Component
    static class HelloCallBack implements CallClient{

        public String hello() {
            System.out.println("调用hello api出现错误,执行回退方法");
            return "error";
        }
        
    }
}

配置hello方法的Hystrix参数:

hystrix:
  command:
    CallClient#hello():
      execution:
        isolation:
          thread: 
            timeoutInMilliseconds: 500
      circuitBreaker:
        requestVolumeThreshold: 3

以上配置设置超时为500ms,另外请求3次失败率超过50%,断路器将被打开。现在我们在调用服务provideServer中设置超时。

package com.demo;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@RestController
public class RestApi {
    
    @GetMapping("/api/hello")
    public String Hello(){
        try {
            Thread.sleep(900);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ServletRequestAttributes requestAttr =  (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttr.getRequest();
        System.out.println("请求了:"+request.getRequestURL());
        return "hello world";
    }
}

现在改造完毕,依次启动Eureka,provideServer,invokerService。访问http://localhost:8093/call,页面显示error,控制台显示:
调用hello api出现错误,执行回退方法。
现在改造一下,察看下断路器状态。

package com.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.netflix.hystrix.HystrixCircuitBreaker;
import com.netflix.hystrix.HystrixCommandKey;

@RestController
@Configuration
public class CallService {
    @Autowired
    private CallClient ccl;

    @GetMapping("/call")
    public String call(){
        String s = ccl.hello();
         // 获取断路器
        HystrixCircuitBreaker breaker = HystrixCircuitBreaker.Factory
                .getInstance(HystrixCommandKey.Factory
                        .asKey("CallClient#hello()"));        
        System.out.println("断路器状态:" + breaker.isOpen());
        return s;
    }
}

因为我们是浏览器访问,所以模拟500ms内访问3次有点困难,所以我们把值改为1,这样我们就可以看到断路器被打开。

hystrix:
  command:
    CallClient#hello():
      execution:
        isolation:
          thread: 
            timeoutInMilliseconds: 500
      circuitBreaker:
        requestVolumeThreshold: 1

重新启动应用,访问http://localhost:8093/call,页面显示error,控制台显示:
调用hello api出现错误,执行回退方法
断路器状态:true

4.使用缓存

Hystrix是支持缓存的,用起来也比较方便,使用注解来开启缓存,提高程序的性能,现在我们就把上面的程序加入缓存。在使用之前需要先出初始化Hystrix的上下文。所以新建一个HystrixFilter在invokerService中:

package com.demo;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
@WebFilter(urlPatterns="/*",filterName="hystrixFilter")
public class HystrixFilter implements Filter{

    @Override
    public void destroy() {
        
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
         HystrixRequestContext context = HystrixRequestContext
                    .initializeContext();
            try {
                chain.doFilter(request, response);
            } finally {
                context.shutdown();
            }
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub
        
    }

}

开启缓存注解:

package com.demo;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;

import com.demo.CallClient.HelloCallBack;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;

@Component
@FeignClient(name="provider-server",fallback = HelloCallBack.class)  //申明调用服务
public interface CallClient {
    @CacheResult
    @HystrixCommand
    @GetMapping("/api/hello")
    String hello();
    
    @Component
    static class HelloCallBack implements CallClient{

        public String hello() {
            System.out.println("调用hello api出现错误,执行回退方法");
            return "error";
        }
        
    }
}

因为在客户端开启了缓存,所以当不停的请求http://localhost:8093/call,Feign会轮流把请求转发到批rovideService的两个服务的hello()方法,那么每次请求都回在控制台输出:
请求了:http://localhost:8091/api/hello
请求了:http://localhost:8092/api/hello
现在由于有了缓存,不会每次请求都输出,服务调用者直接从缓存中 就得到了响应结果,所以只输出一次。不过实际上会输出几次但是远远少于请求的次数,我也不知道这是什么原因。但是缓存的效果已经出来了。


Mike晓
95 声望18 粉丝