概述

只介绍同步模式下简单的使用, 有助于快速接入, 会有一些官方文档中没有涉及的细节.

默认方式

public class CommandHelloWorld extends HystrixCommand<String> {

    private final String name;

    // 构造方法中传入需要用到的数据, run 方法处理逻辑.
    public CommandHelloWorld(String name) {
        // 构造 Setter 比较麻烦, 后面会进一步介绍
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.name = name;
    }

    @Override
    protected String run() {
        // a real example would do work like a network call here
        return "Hello " + name + "!";
    }

    // fallback 方法可选, 如果没有, 默认抛异常.
    @Override
    protected String getFallback() {
        return "fallback";
    }
}

构造Setter

Hystrix 讲求的大而全, 设计的比较模式, 用起来不是很方便. 下面是一个构造 Setter 的实例:

Setter setter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(site)) // groupKey 对command进行分组, 用于 reporting, alerting, dashboards, or team/library ownership, 也是 ThreadPool 的默认 key
        .andCommandKey(HystrixCommandKey.Factory.asKey(site)) // 可以根据 commandKey 具体的运行时参数
        .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(site))  // 指定 ThreadPool key, 这样就不会默认使用 GroupKey
        .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() // 初始化 Command 相关属性
                .withMetricsRollingStatisticalWindowInMilliseconds(
                        100 * 1000) // 设置统计窗口为100秒
                .withCircuitBreakerSleepWindowInMilliseconds(
                        10 * 1000) // 设置熔断以后, 试探间隔为10秒
                .withCircuitBreakerRequestVolumeThreshold(
                        10) // 设置判断熔断请求阈值为10
                .withCircuitBreakerErrorThresholdPercentage(
                        80) // 设置判断熔断失败率为80%
                .withExecutionTimeoutInMilliseconds(3 * 1000)) // 设置每个请求超时时间为3秒
        .andThreadPoolPropertiesDefaults( // 设置和threadPool相关
                HystrixThreadPoolProperties.Setter().withCoreSize(20)); // 设置 threadPool 大小为20(最大20个并发)
  • 另外threadPool还有个设置统计窗口的选项, 因为Hystrix统计维度会从主线程和线程池两个维度来统计.

  • 还有好多配置信息可以配置, 这里只提供了几个重要的,其他配置参考:hystrix-configuration

Spring cloud 注解方式

需要用到两个注解: @EnableCircuitBreaker, @HystrixCommand(fallbackMethod = "reliable")

启动类:

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.web.client.RestTemplate;

@EnableCircuitBreaker
@RestController
@SpringBootApplication
public class ReadingApplication {

  @Autowired
  private BookService bookService;

  @Bean
  public RestTemplate rest(RestTemplateBuilder builder) {
    return builder.build();
  }

  @RequestMapping("/to-read")
  public String toRead() {
    return bookService.readingList();
  }

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


服务类:

package hello;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.net.URI;

@Service
public class BookService {

  private final RestTemplate restTemplate;

  public BookService(RestTemplate rest) {
    this.restTemplate = rest;
  }

  @HystrixCommand(fallbackMethod = "reliable")
  public String readingList() {
    URI uri = URI.create("http://localhost:8090/recommended");
    return this.restTemplate.getForObject(uri, String.class);
  }

  public String reliable() {
    return "Cloud Native Java (O'Reilly)";
  }

}

动态更新配置

Hystrix 使用 Archaius 来实现动态配置. 使用 Spring 配置方式:

创建配置获取源

实现接口 PolledConfigurationSource, 并返回一个 Map. 注意 Key 为 hystrix 配置key, 可以参考:hystrix-configuration

package com.rh.config.hystrix;

import com.netflix.config.PollResult;
import com.netflix.config.PolledConfigurationSource;
import org.springframework.core.env.*;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;


import javax.annotation.Resource;
import java.util.*;

/**
 * @Auther: wangyunfei
 * @Date: 2019/12/10 14:12
 * @Description:
 */
@Component
/**
 * @Author wangyunfei
 * @Description 定期读取配置文件中的hystrix配置
 * @Date 2019/12/10 14:13
 * @Param
 * @return
 **/
public class DegradeConfigSource implements PolledConfigurationSource {
    public final String HystrixPrefix = "hystrix";

    //spring的Environment
    private ConfigurableEnvironment environment;
    private PropertySourcesPropertyResolver resolver;

    DegradeConfigSource(ConfigurableEnvironment environment) {
        this.environment = environment;
        this.resolver = new PropertySourcesPropertyResolver(this.environment.getPropertySources());
        this.resolver.setIgnoreUnresolvableNestedPlaceholders(true);
    }

    @Override
    public PollResult poll(boolean initial, Object checkPoint) throws Exception {
        //Map<String, Object> complete = getHystrixConfig();
        Map<String, Object> complete = getProperties();
        return PollResult.createFull(complete);
    }

    @Resource
    private ResourceLoader resourceLoader;
    /**
     * @Author wangyunfei
     * @Description 获取配置文件中的指定hystrix前缀信息
     * 缺点:1)不支持yml格式 只支持properties
     *       2)只能读取某一个配置文件中的信息 如果配置是写在不同配置文件则不支持
     * @Date 2019/12/20 14:58
     * @Param []
     * @return java.util.HashMap<java.lang.String,java.lang.String>
     **/
    public HashMap<String,Object> getHystrixConfig() throws Exception
    {
        HashMap<String,Object> hystrixMap = new  HashMap<String,Object>();
        org.springframework.core.io.Resource resource = resourceLoader.getResource("classpath:application-mod.properties");
        Properties props = new Properties();
        props.load(resource.getInputStream());
        Enumeration keys = props.propertyNames();
        while (keys.hasMoreElements()) {
            String key = (String) keys.nextElement();
            System.out.println(key + "=" + props.getProperty(key));
            if(key.contains(HystrixPrefix))
            {
                hystrixMap.put(key, props.getProperty(key));
            }
        }
        return hystrixMap;
    }

    /**
     * @Author wangyunfei
     * @Description 获取所有配置信息
     * @Date 2019/12/20 15:06
     * @Param []
     * @return java.util.Map<java.lang.String,java.lang.Object>
     **/
    public Map<String, Object> getProperties() {
        Map<String, Object> properties = new LinkedHashMap<>();
        //spring env 里面也是多个source组合的
        for (Map.Entry<String, PropertySource<?>> entry : getPropertySources().entrySet()) {
            PropertySource<?> source = entry.getValue();
            if (source instanceof EnumerablePropertySource) {
                EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
                for (String name : enumerable.getPropertyNames()) {
                    if (!properties.containsKey(name) && name.contains(HystrixPrefix)) {
                        properties.put(name, resolver.getProperty(name));
                    }
                }
            }
        }
        return properties;
    }

    //PropertySource也可能是组合的,通过递归获取
    private Map<String, PropertySource<?>> getPropertySources() {
        Map<String, PropertySource<?>> map = new LinkedHashMap<String, PropertySource<?>>();
        MutablePropertySources sources = null;
        if (environment != null) {
            sources = environment.getPropertySources();
        } else {
            sources = new StandardEnvironment().getPropertySources();
        }
        for (PropertySource<?> source : sources) {
            extract("", map, source);
        }
        return map;
    }

    private void extract(String root, Map<String, PropertySource<?>> map,
                         PropertySource<?> source) {
        if (source instanceof CompositePropertySource) {
            for (PropertySource<?> nest : ((CompositePropertySource) source)
                    .getPropertySources()) {
                extract(source.getName() + ":", map, nest);
            }
        } else {
            map.put(root + source.getName(), source);
        }
    }
}

配置并初始化自动配置

创建一个 DynamicConfiguration, 并注册一下就可以了. 注意, 我们使用了 FixedDelayPollingScheduler 来定期加载新的配置. 默认60秒加载一次.

@Configuration
public class DegradeDynamicConfig {

    @Bean
    public DynamicConfiguration dynamicConfiguration(@Autowired DegradeConfigSource degradeConfigSource) {
        AbstractPollingScheduler scheduler = new FixedDelayPollingScheduler(30 * 1000, 
																			60 * 1000, false);
        DynamicConfiguration configuration = new DynamicConfiguration(degradeConfigSource, 
																	  scheduler);
        ConfigurationManager.install(configuration); // must install to enable configuration
        return configuration;
    }
}

其中从配置文件获取hystrix配置信息等参考如下代码 http://techblog.ppdai.com/2018/05/08/20180508/

参考

https://github.com/Netflix/Hystrix/wiki/How-To-Use https://github.com/Netflix/Hystrix/wiki/Configuration https://github.com/Netflix/Hystrix/issues/1717 https://github.com/Netflix/archaius/wiki/Users-Guide https://spring.io/guides/gs/circuit-breaker/