目录
3.1:什么是服务降级(对单独的方法进行隔离,防止拖累整个程序)
1:什么是hystrix
在分布式框架中,服务可能很复杂,服务之间互相调用,形成链路,如下图所示。
当其中的一个服务发生错误或者超时的时候,会导致整个链路都会产生异常,比如一个订单服务产生了延迟卡顿,那么调用订单服务的机器在大的并发下,系统资源迅速被占满了,导致该机器上的其他服务也产生的卡顿。链路中的任何一个服务出错都会拖累整个服务。
复杂分布式体系结构中的应用程序有许多依赖项,每个依赖项在某些时候都不可避免地会失败。如果主机应用程序没有与这些外部故障隔离,那么它有可能被他们拖垮。
例如,对于一个依赖于30个服务的应用程序,每个服务都有99.99%的正常运行时间,你可以期望如下:
99.9930 = 99.7% 可用
也就是说一亿个请求的0.03% = 3000000 会失败
如果一切正常,那么每个月有2个小时服务是不可用的
在高流量的情况下,一个后端依赖项的延迟可能导致所有服务器上的所有资源在数秒内饱和(PS:意味着后续再有请求将无法立即提供服务)
2:hystrix的作用
Hystrix设计原则是什么?
1:防止任何单个依赖项耗尽所有容器(如Tomcat)用户线程。多个controller方法,一个是高并发,导致其他的方法卡顿
2:在任何可行的地方提供回退,以保护用户不受失败的影响。提供fallback机制
3:是分布式系统高可用
依赖如下:
<dependencies>
<!--Spring web 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Springcloud的eureka组件 client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--Springcloud的hystrix组件 服务降级 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--图形监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
ue 热部署才有效 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
3:hystrix服务降级(熔断默认开启的)
3.1:什么是服务降级(对单独的方法进行隔离,防止拖累整个程序)
比如在我们的一个controller中通常有多个业务方法,其中一个是订单下单方法,还有其他的查询,登陆等业务方法。在大并发的新情况下。下单很多人,导致系统CPU资源沾满,tomcat的线程池沾满。这个时候其他人的登陆查询就会很慢。甚至不可用。这个时候我们就需要对这个下单方法进行降级,防止拖累整个程序
3.2:超时降级
针对服务调用超时,假如我们调用一个服务,他有时候返回值很慢,但是我们不能一致等待,所以就有了超时降级的方式。
/**
* controller的add1方法,在给别人调用的时候可能会错和超时
*
* 服务降级 该服务超时3000毫秒没有返回值或者代码出错 就会走time_out方法,timeoutInMilliseconds 默认1秒
* @return
*/
@HystrixCommand(fallbackMethod = "time_out", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
@RequestMapping(value = "add1",method = RequestMethod.GET)
public String add1(){
//服务超时 和服务出错 都会走fallbackMethod
int a=10/0;
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("服务睡眠端口号:"+port);
return "端口号:"+port;
}
public String time_out(){
return "端口号:"+port+Thread.currentThread().getName()+":服务异常,降级处理";
}
3.3:线程池隔离
所谓线程池隔离,就是每个过来的请求,系统会单独将这个请求的执行放在一个线程或线程池里,这个根据自己的需求进行配置,以后,相同的请求不管有多少打进来,都会在指定数量的线程池内进行,因此不会出现诸如tomcat线程池耗尽的情况,这种隔离方式在某些场景下是非常有用的,下面来看具体的代码
//限流策略:线程池方式
@HystrixCommand(
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "10000"),
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD")//线程池
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "3"),//核心线程
@HystrixProperty(name = "maxQueueSize", value = "5"),//最大线程数
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "100")//等待队列
},
fallbackMethod = "back2"
)
@RequestMapping("/test4")
public String test4() {
System.out.println("线程id:"+Thread.currentThread().getId()+":"+Thread.currentThread().getName());
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
return "下单成功,订单号是:";
}
public String back2() {
System.err.println("-------超时线程池降级策略执行------------");
return "-------超时线程池降级策略执行------------";
}
3.4:信号量隔离(调用会阻塞,不能指定超时)
ExecutionIsolationStrategy.SEMAPHORE,同时配置信号量个数,默认为10。客户端需向依赖服务发起请求时,首先要获取一个信号量才能真正发起调用,由于信号量的数量有限,当并发请求量超过信号量个数时,后续的请求都会直接拒绝,进入fallback流程。信号量隔离主要是通过控制并发请求量,防止请求线程大面积阻塞,从而达到限流和防止雪崩的目的
信号量代码:
/**
* 信号量SEMAPHORE
* @return
*/
@HystrixCommand(
commandKey="test5",
commandProperties= {
@HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE"),//信号量
@HystrixProperty(name="execution.isolation.semaphore.maxConcurrentRequests", value="6")//6个信号
},
fallbackMethod = "back3"
)
@RequestMapping("/test5")
public String test5() {
System.out.println("信号量线程id:"+Thread.currentThread().getId()+":"+Thread.currentThread().getName());
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
return "信号量执行策略";
}
public String back3() {
System.err.println("-------信号量执行策略------------");
return "-------信号量执行------------";
}
3.5:三种方式对比
隔离方式 | 是否支持超时 | 是否支持熔断 | 隔离原理 | 资源消耗 | |
线程池隔离 | 支持,可直接返回(可设置超时时间) | 支持,当线程池到达maxSize后,再请求会触发fallback接口进行熔断 | 每个服务单独用线程池 | 大,大量线程的上下文切换,容易造成机器负载高 | |
信号量隔离 | 不支持,如果阻塞,只能通过调用协议 不支持超时时间 | 支持,当信号量达到maxConcurrentRequests后。再请求会触发fallback | 通过信号量的计数器 |
小,只是个计数器 |
4:hystrix服务熔断
4.1:什么是服务熔断?
前边我们说了服务降级的三种方式,虽然我们对服务进行了降级,但是服务无论是否有错误,都是能都被外部访问的,我们想如果一个服务多次访问出错,那么我们就像让这个服务暂时不可用断路器打开,等他等会可用的话,断路器在关闭,可以被外部访问。
熔断开open:熔断开关打开,禁止访问资源,但是设计了一个时钟选项,默认的时钟达到一定时间(这个时间设置成平均故障时间也就是MTTR),达到这个时间之后进入半熔断状态。
熔断关close:熔断开关关闭,随意访问资源,当访问出错到一定程度,进入open状态
熔断半开half-open: 成功计数器归0,成功+1,到达一定成功次数进入close,否则进入open
4.2:代码演示
/**
* 熔断方法 指定fallback方法可参数
* id参数为- 代码抛出异常,在10秒内10此请求以上如果错误率60以上就会触发断路器,导致服务不可用
* @param id
* @return
*/
@RequestMapping("/test3/{id}")
@HystrixCommand(fallbackMethod = "back1",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),//打开断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),//阀值,判断熔断的最少请求数,默认是10;只有在一个统计窗口内处理的请求数量达到这个阈值,才会进行熔断与否的判断
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),//快照时间,默认10秒 10秒以内阀值10以上 成功率60一下会熔断
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")//表示在一个统计窗口内有50%的请求处理失败,会触发熔断
})
public String hello3(@PathVariable("id") int id) {
System.out.println("jinru");
if (id > 0) {
System.out.println("断路器关闭,直接返回");
return "断路器关闭,直接返回";
}
else {
//参数为- 代码抛出异常,在10秒内10此请求以上如果错误率60以上就会触发断路器,导致服务不可用
System.out.println("断路器打开,开启熔断");
throw new RuntimeException();
}
}
public String back1(@PathVariable("id") int id){
return "----服务熔断处理------";
}
5:hystrix服务限流
服务限流也就是上边的线程池和信号量限流
6:hystrix图形化Dashboard
图形化监控页面可以查看断路器状态,成功、超时、失败率等参数
6.1:pom文件
<dependencies>
<!--监控依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--Spring web 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Springcloud的hystrix组件 服务降级 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--引入openfeignjar-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--Springcloud的eureka组件 client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--图形监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
6.2:启动类开启注解
@SpringBootApplication
@ComponentScan(basePackages= {"com.thit"})
@EnableFeignClients //启动类 feign调用注解
@EnableCircuitBreaker //主启动类激活服务降级注解
@EnableHystrix //断路器
@EnableHystrixDashboard//监控注解
public class Consumer2Application {
public static void main(String[] args) {
SpringApplication.run(Consumer2Application.class,args);
}
@Bean
public ServletRegistrationBean getServlet()
{
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
6.3:浏览器查询