一、服务雪崩效应
基础服务的故障导致级联故障,进而造成了整个分布式系统的不可用,这种现象被称为服务雪崩效应。服务雪崩效应描述的是一种因服务提供者的不可用导致服务消费者的不可用,并将不可用逐渐放大的过程。
服务雪崩效应形成的原因
1、服务提供者不可用
- 硬件故障
- 程序Bug
- 缓存击穿
- 用户大量请求
2、重试加大流量
- 用户重试
- 代码逻辑重试
3、服务调用者不可用
- 同步等待造成的资源耗尽
服务雪崩的应对策略
1、流量控制
网关限流
用户交互限流
关闭重试
2、改进缓存模式
缓存预加载
同步改为异步刷新
3、服务自动扩容
AWS的auto scaling
4、服务调用者降级服务
资源隔离
对依赖服务进行分类
不可用服务的调用快速失败
二、hystrix的工作原理
Hystrix是Netflix开源的一个限流熔断的项目、主要有以下功能:
- 隔离(线程池隔离和信号量隔离):限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
- 优雅的降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
-融断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。
-缓存:提供了请求缓存、请求合并实现。支持实时监控、报警、控制(修改配置)
下面是他的工作流程:
Hystrix主要有4种调用方式:
- toObservable() 方法 :未做订阅,只是返回一个Observable 。
- observe() 方法 :调用#toObservable() 方法,并向 Observable 注册 rx.subjects.ReplaySubject 发起订阅。
- queue() 方法 :调用 #toObservable() 方法的基础上,调用:Observable#toBlocking() 和BlockingObservable#toFuture() 返回 Future 对象
- execute() 方法 :调用 #queue()方法的基础上,调用 Future#get() 方法,同步返#run() 的执行结果。
主要的执行逻辑:
1.每次调用创建一个新的HystrixCommand,把依赖调用封装在run()方法中.
2.执行execute()/queue做同步或异步调用.
3.判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果关闭进入步骤.
4.判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续步骤.
5.调用HystrixCommand的run方法.运行依赖逻辑,依赖逻辑调用超时,进入步骤8.
6.判断逻辑是否调用成功。返回成功调用结果;调用出错,进入步骤8.
7.计算熔断器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给熔断器,用于统计从而判断熔断器状态.
8.getFallback()降级逻辑。以下四种情况将触发getFallback调用:
- run()方法抛出非HystrixBadRequestException异常。
- run()方法调用超时
- 熔断器开启拦截调用
- 线程池/队列/信号量是否跑满
- 没有实现getFallback的Command将直接抛出异常,fallback降级逻辑调用成功直接返回,降级逻辑调用失败抛出异常.
9.返回执行成功结果
三、HystrixCommand的使用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
</dependency>
创建一个CommandHelloworldWithFallBack类,继承与HystrixCommand
import java.util.Arrays;
import java.util.List;
import com.netflix.hystrix.HystrixCommand;
public class CommandHelloworldWithFallBack extends HystrixCommand<String>{
public static List<Integer> times = Arrays.asList(100, 200, 300, 400, 500, 600,
700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100);
private String name;
private int timeIndex;
public CommandHelloworldWithFallBack(Setter setter, String name, int timeIndex) {
super(setter);
this.name = name;
this.timeIndex = timeIndex;
}
@Override
protected String getFallback() {
return "fall back timeMillSeconds is :" + times.get(timeIndex);
}
@Override
protected String run() {
try {
Thread.currentThread().sleep(times.get(this.getTimeIndex()));
} catch (InterruptedException e) {
}
return "ok timeMillSeconds is :" + times.get(timeIndex);
}
public int getTimeIndex() {
return timeIndex;
}
public void setTimeIndex(int timeIndex) {
this.timeIndex = timeIndex;
}
}
建立一个单元测试类,执行
import org.junit.Test;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
public class CommandHelloworldWithFallBackTest {
@Test
public void testHystrix() {
int count = 0;
while (true) {
String s = new CommandHelloworldWithFallBack(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("threadpoolwithfallback"))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(10)
)
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(1000)
.withCircuitBreakerSleepWindowInMilliseconds(5000)
.withCircuitBreakerErrorThresholdPercentage(50)
.withCircuitBreakerRequestVolumeThreshold(1))
, "ccc", count % 20).execute();
System.out.println(s);
count++;
try {
Thread.currentThread().sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
}
}
}
- HystrixCommand的子类需要实现两个函数,run函数和getFallback函数,正常运行时,调用run函数,熔断时调用getFallback
- 调用CommandHelloworldWithFallBack方法时,传入Setter参数,对熔断进行配置,设置超时时长为1秒,5秒熔断时间窗检查是否恢复。
运行可以看到,当run运行时长超过1秒时,系统就会熔断转而getFallback函数,并且每5秒检查一次是否恢复。
ok timeMillSeconds is :800
ok timeMillSeconds is :900
ok timeMillSeconds is :1000
fall back timeMillSeconds is :1100
fall back timeMillSeconds is :1200
fall back timeMillSeconds is :1300
熔断行为设置:
参数 | 描述 | 默认值 |
---|---|---|
circuitBreaker.enabled | 确定断路器是否用于跟踪运行状况和短路请求(如果跳闸)。 | 默认值为true |
circuitBreaker.requestVolumeThreshold | 熔断触发的最小个数/10s | 默认值:20 |
circuitBreaker.sleepWindowInMilliseconds | 熔断多少秒后去尝试请求 | 默认值:5000 |
circuitBreaker.errorThresholdPercentage | 失败率达到多少百分比后熔断 | 默认值:50,主要根据依赖重要性进行调整 |
circuitBreaker.forceOpen | 属性如果为真,强制断路器进入打开(跳闸)状态,其中它将拒绝所有请求。 | 默认值为false,此属性优先于circuitBreaker.forceClosed |
circuitBreaker.forceClosed | 该属性如果为真,则迫使断路器进入闭合状态,其中它将允许请求,而不考虑误差百分比。 | 默认值为false,如果是强依赖,应该设置为true,circuitBreaker.forceOpen属性优先,因此如果forceOpen设置为true,此属性不执行任何操作 |
注解方式
import java.util.concurrent.Future;
import org.springframework.stereotype.Service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.command.AsyncResult;
@Service
public class HystrixCommandUserServices {
@HystrixCommand(fallbackMethod="getUserIdFallback")
public String getUserId(String name){
int i = 1/0;
return "你好" + name ;
}
@HystrixCommand(fallbackMethod="getUserIdFallbackTo")
public String getUserIdFallback(String name){
int i = 1/0;
return "getUserId Fail" ;
}
public String getUserIdFallbackTo(String name){
return "getUserId Fail2" ;
}
//异步执行
@HystrixCommand(fallbackMethod="getUserIdFallback")
public Future<String> getUserName(final String name){
return new AsyncResult<String>(){
@Override
public String invoke() {
//int i = 1/0;
System.out.println("getUserName invoke!!!");
return "你好" + name ;
}
};
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.itmuch.cloud.ConsumerMovieRibbonApplication;
import com.itmuch.cloud.hystrix.HystrixCommandUserServices;
@RunWith(SpringRunner.class)
@SpringBootTest(classes=ConsumerMovieRibbonApplication.class)
public class HystrixCommandUserServicesTest {
@Autowired
private HystrixCommandUserServices hystrixCommandUserServices;
@Test
public void testUserServicegetUserId(){
String name = hystrixCommandUserServices.getUserId("Hello");
System.out.println("GetUserID=" + name);
}
@Test
public void testUserServicegetUserName() throws InterruptedException, ExecutionException{
System.out.println("testUserServicegetUserName Start!!!");
Future<String> future = hystrixCommandUserServices.getUserName("HystrixSync");
System.out.println("testUserServicegetUserName getUserName!");
System.out.println("ServicegetUserName" + future.get());
}
}