一、简介

1、概述

Hystrix是Netflix开源的一个用于处理分布式系统延迟和容错的库,主要目标是防止在复杂的分布式系统中出现级联故障,从而提高系统的弹性。

2、核心概念

  • Circuit breaker

断路器是软件开发中使用的一种设计模式,它在监控故障达到特定阈值时开启,从而防止对故障服务的进一步调用,提高系统弹性。

Hystrix是实现了断路器模式的库,它提供了使用HTTP或其他协议调用远程服务时处理故障的机制:当达到指定的错误阈值时,断路器会打开,并在指定时间段后再次关闭,允许继续尝试调用;它还提供了回退机制来优雅地处理故障。

  • Command

有断路器功能的服务调用。

  • Fallback

一种备份机制,在命令失败或断路器打开时提供默认行为,防止应用程序因故障而崩溃。

  • Timeout

如果命令超过此时间,则中止该命令,并调用回退机制(Fallback)。

  • Rolling Window

Hystrix监控服务运行状况的时间窗口,通过计算此窗口内的错误百分比和请求量等指标来决定断路器是否开启。

  • Isolation

Hystrix使用单独的线程、线程池或信号量来提供命令的隔离,确保一个命令中的故障不会影响其他命令,从而提供容错能力。

  • Metrics

Hystrix收集的数据指标,例如成功率、失败率、延迟和并发等;这些指标用于决定是否开启断路器以及用于监视和调试。

  • Thread Pool

Hystrix用于异步执行命令的线程池,每个命令都可以有对应的线程池,从而防止命令之间的资源竞争。

二、样例

1、纯Hystrix

  • 依赖
<dependencies>
	<dependency>
		<groupId>com.netflix.hystrix</groupId>
		<artifactId>hystrix-core</artifactId>
		<version>1.5.18</version>
	</dependency>
</dependencies>
  • Command

服务调用类:

import com.netflix.hystrix.HystrixCommand;

public class RemoteCallCommand extends HystrixCommand<String>{

	private String input;
	private long time;
	
	public RemoteCallCommand(Setter setter, String input) {
		super(setter);
		this.input = input;
	}

	@Override
	protected String run() throws Exception {
		//模拟服务不稳定,随机Sleep 0~1000ms
        time = (long) (Math.random() * 1000);
        Thread.sleep(time);
		return String.format("[SUCCESS] input: %s, time: %s", input, time);
	}

	@Override
	protected String getFallback() {
		return String.format("[Fallback] input: %s, time: %s", input, time);
	}
}
  • 主程序
import com.netflix.hystrix.HystrixCommand.Setter;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixThreadPoolProperties;

public class App {

	public static void main(String[] args) throws Exception{
		Setter setter = getHystrixCommandSetter();
		RemoteCallCommand command = null;
		for(int i = 1; i <= 20; i++) {
			String input = String.format("command-%s", i);
			command = new RemoteCallCommand(setter, input);
			String ret = command.execute();
			System.out.println(String.format("%s:%s", i, ret));
		}
	}
	
	private static Setter getHystrixCommandSetter() {
		Setter setter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("demoGroup"));
		//线程池配置
		HystrixThreadPoolProperties.Setter threadPollProps = HystrixThreadPoolProperties.Setter();
		threadPollProps.withCoreSize(5);
		threadPollProps.withMaxQueueSize(10);
		setter.andThreadPoolPropertiesDefaults(threadPollProps);
		//熔断器、超时配置
		HystrixCommandProperties.Setter commandProps = HystrixCommandProperties.Setter();
		commandProps.withCircuitBreakerEnabled(true);
		commandProps.withCircuitBreakerRequestVolumeThreshold(5);
		commandProps.withCircuitBreakerErrorThresholdPercentage(50);
		commandProps.withCircuitBreakerSleepWindowInMilliseconds(3000);
		commandProps.withExecutionTimeoutInMilliseconds(800);
		setter.andCommandPropertiesDefaults(commandProps);
		return setter;
	}
}
  • HystrixCommand配置

    • withCircuitBreakerEnabled(true)

    是否启用熔断器,默认值为true。

    • withMetricsRollingStatisticalWindowInMilliseconds(int)

    熔断器做失败率统计时的滑动窗口长度,默认为10000ms(10s)。

    • withCircuitBreakerRequestVolumeThreshold(5)

    滑动窗口内触发熔断的最小请求数,默认值为20。

    上面的设置表示如果10秒内的总请求数 <5,即使全失败也不会打开熔断器,防止少量调用毛刺导致误判。

    • withCircuitBreakerErrorThresholdPercentage(50)

    达到上面的最小请求数后,如果失败率 >=50% 即开启断路。

    • withCircuitBreakerSleepWindowInMilliseconds(3000)

    熔断器断路后的休眠时间,即这段时间内的所有请求直接走fallback,休眠结束后进入半开状态,允许一条探测请求通过;默认5000ms(5s)。

    • withExecutionTimeoutInMilliseconds(800)

    单条命令的执行超时,超时后run()方法会被中断(线程池模式)并进入getFallback()方法;设为0代表永不超时,默认1000ms(1s)。

  • 运行

输出:

1:[Fallback] input: command-1, time: 847
2:[SUCCESS] input: command-2, time: 717
3:[SUCCESS] input: command-3, time: 264
4:[SUCCESS] input: command-4, time: 590
5:[SUCCESS] input: command-5, time: 183
6:[Fallback] input: command-6, time: 919
7:[Fallback] input: command-7, time: 821
8:[Fallback] input: command-8, time: 897
9:[SUCCESS] input: command-9, time: 339
10:[SUCCESS] input: command-10, time: 603
11:[SUCCESS] input: command-11, time: 5
12:[SUCCESS] input: command-12, time: 81
13:[SUCCESS] input: command-13, time: 96
14:[SUCCESS] input: command-14, time: 705
15:[SUCCESS] input: command-15, time: 474
16:[SUCCESS] input: command-16, time: 172
17:[Fallback] input: command-17, time: 902
18:[Fallback] input: command-18, time: 871
19:[SUCCESS] input: command-19, time: 533
20:[SUCCESS] input: command-20, time: 642

2、使用SpringBoot

  • 依赖

pom.xml:

<properties>
	<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>

<dependencyManagement>
	<dependencies>
		<!-- 引入 Spring Cloud Hoxton 的依赖管理 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

<dependencies>
	<!-- Spring Boot Web -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
		<version>2.2.6.RELEASE</version>
	</dependency>

	<!-- 使用 Netflix Hystrix Starter -->
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
		<!-- 版本由 dependencyManagement 控制,无需在此指定 -->
	</dependency>

	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
		<version>2.15.0</version> <!-- 建议使用与Spring Boot兼容的版本 -->
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-core</artifactId>
		<version>2.15.0</version>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-annotations</artifactId>
		<version>2.15.0</version>
	</dependency>
</dependencies>
  • 服务
public interface RemoteService {

	String callService();
}
  • 服务实现类
import java.util.Random;

import org.springframework.stereotype.Service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

@Service
public class RemoteServiceImpl implements RemoteService{

	@Override
	@HystrixCommand(fallbackMethod = "reliable")
	public String callService() {
		if(new Random().nextBoolean()) {
			throw new RuntimeException("Service Failure!");
		}
		return "Successfully called service!";
	}

	public String reliable() {
		return "Default Response.";
	}
}

常用的配置项如下:

@HystrixCommand(
	fallbackMethod = "fallback",
	commandKey = "xxxCmd",
	groupKey = "xxxGroup",
	threadPoolKey = "xxxPool",
	/* 命令属性 */
	commandProperties = {
		@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="1500"),
		@HystrixProperty(name="circuitBreaker.requestVolumeThreshold", value="10"),
		@HystrixProperty(name="circuitBreaker.errorThresholdPercentage", value="60"),
		@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds", value="5000")
	},
	/* 线程池属性 */
	threadPoolProperties = {
		@HystrixProperty(name="coreSize", value="10"),
		@HystrixProperty(name="maxQueueSize", value="50")
	}
)
  • 控制器类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ServiceController {

	@Autowired
	private RemoteService remoteService;
	
	@GetMapping("/service")
	public String call() {
		try {
			return remoteService.callService();
		} catch (Exception e) {
			e.printStackTrace();
			return "Failed to call service";
		}
	}
}
  • 主程序
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;

@SpringBootApplication
@EnableCircuitBreaker
public class CircuitBreakerHystrixApp {

	public static void main(String[] args) {
		SpringApplication.run(CircuitBreakerHystrixApp.class, args);
	}
}
  • 运行

多次访问[http://localhost:8080/service]{http://localhost:8080/service},可以看到:

请求成功时显示:

Successfully called service!

请求失败时返回Service中reliable方法的内容:

Default Response.
参考资料