文章目录
1 概述
1.1 分布式面临的问题
复杂的分布式中,应用程序有数十个依赖关系,每个依赖关系在某些时候不可避免的会出现失败。
服务血崩
多个微服务调用的时候,假设A服务调用B服务和C服务,B服务和C服务有调用其他服务,这就是所谓的扇出
,如果扇出的链路上某个服务的出现调用超时或者服务宕机不可用,对于服务A的调用就会占用越来越多的资源,进行引起系统崩溃,这就是服务血崩。
雪崩效应就是因为一个服务宕机后,另外的服务还在相继的调用它,导致其他的服务也宕机,这就是雪崩效应。
对于高流量的应用来说,单一的后端依赖可能会导致所有的服务器上的所有资源都在几秒内饱和,比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收浏览,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障或者叫雪崩
1.2 Hystric是什么
是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证再一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方法返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出方法无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要的占用,从而避免了故障再分布式系统的蔓延,乃至雪崩
1.3 Hystric能干啥
服务降级
服务熔断
接近实时的监控
1.4 Hystrix重要概念
- 服务降级
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
哪些情况会触发降级
程序运行异常、超时、服务熔断触发、线程池打满也会导致服务降级
-
服务熔断
类似保险丝达到最大的访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回 -
友好提示,
就是保险丝:服务的降级->进行熔断->恢复调用链路 -
服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
2 服务降级
什么是服务降级:
- 超时导致服务器变慢(转圈):超时不在等待
- 出错(宕机或程序运行出错):出错要有异常处理
服务降级的处理维度:
针对服务降级,主要有两个维度:自身(提供者)和 调用者(消费者)
- 自身接口有自己的超时时间和异常处理,比如自身规定接口的超时时间为5s,超过5s就返回超时提示
- 消费者侧在调用时候,如果提供者超时了或者宕机了,消费者也可以进行消费者自己的调用超时处理或者提供者宕机逻辑。
2.1 服务提供者侧服务降级
自身接口有自己的超时时间和异常处理,比如自身规定接口的超时时间为5s,超过5s就返回超时提示
项目搭建
- 新建一个model进行演示
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入我们的api-common-->
<dependency>
<groupId>study.wyy</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<!--Hystric starter-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
Hystric starter
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- application.yml
server:
port: 8001
spring:
application:
name: cloud-consumer-hystic
- 提供两个方法,测试超时和服务异常
package study.wyy.cloud.hystrix.service;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 2:27 下午
*/
@Component
public class HystrixService {
// 超时时间为5s
private final Long TIME_OUT = 5000L;
public String testTimeout(@PathVariable Integer time) throws TimeoutException {
long l = System.currentTimeMillis();
try {
// 模拟接口调用时间
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
long cost = (System.currentTimeMillis() - l) ;
// 模拟超时,超时时间为5s
if(cost>TIME_OUT){
throw new TimeoutException("time out");
}
return "线程:" + Thread.currentThread().getName() + " invoke success cost time " + cost/1000 + "秒";
}
public String testError() {
// 模拟个错误
int a = 1/0;
return "线程:" + Thread.currentThread().getName() + " invoke success";
}
}
- 提供rest接口测试上面的方法
package study.wyy.cloud.hystrix.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import study.wyy.cloud.hystrix.service.HystrixService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 2:25 下午
*/
@RestController
public class HystrixController {
private final HystrixService hystrixService;
@Autowired
public HystrixController(HystrixService hystrixService) {
this.hystrixService = hystrixService;
}
@GetMapping("/provider/hystrix/timeout/{time}")
public String testTimeout(@PathVariable Integer time) throws TimeoutException {
return hystrixService.testTimeout(time);
}
@GetMapping("/provider/hystrix/error")
public String testError(){
return hystrixService.testError();
}
}
- 启动类
package study.wyy.cloud.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 2:51 下午
*/
@SpringBootApplication
public class PaymentHystrix {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrix.class, args);
}
}
- 测试两个方法
7秒已经超过了设置的超时时间5秒,所以抛出超时异常。
第二个也会抛出by zero的异常。
服务降级
@EnableCircuitBreaker
开启断路器功能
package study.wyy.cloud.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 2:51 下午
*/
@SpringBootApplication
@EnableCircuitBreaker
public class PaymentHystrix {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrix.class, args);
}
}
- 使用
@HystrixCommand
配置服务降级处理
package study.wyy.cloud.hystrix.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 2:27 下午
*/
@Component
public class HystrixService {
private final Long TIME_OUT = 5000L;
// fallbackMethod: 指定的就是异常处理方法
@HystrixCommand(fallbackMethod = "timeoutHandler",commandProperties = {
// 配置超时时间为5秒钟,超过5s,超时异常处理方法为timeoutHandler
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value ="5000" )
})
public String testTimeout(Integer time) throws TimeoutException {
long l = System.currentTimeMillis();
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
long cost = (System.currentTimeMillis() - l) ;
// 模拟超时,超时时间为5s
if(cost>TIME_OUT){
throw new TimeoutException("time out");
}
return "线程:" + Thread.currentThread().getName() + " invoke success cost time " + cost/1000 + "秒";
}
@HystrixCommand(fallbackMethod = "errorHandler")
public String testError() {
// 模拟个错误
int a = 1/0;
return "线程:" + Thread.currentThread().getName() + " invoke success";
}
public String timeoutHandler(Integer time){
System.out.println("timeoutHandler : " + time);
return "线程:" + Thread.currentThread().getName() + " 系统超时,请稍候再试 ";
}
public String errorHandler(){
return "线程:" + Thread.currentThread().getName() + " 系统异常,请稍候再试 ";
}
}
- 再次测试
调用超时:
未超时:
注意两个返回的线程名字,Hystric会开启一个线程去处理超时
2.2 消费者侧服务降级
消费者侧在调用时候,如果提供者超时了或者宕机了,消费者也可以进行消费者自己的调用超时处理或者提供者宕机逻辑。
服务提供者注册到Eureka注册中心
将服务提供者注册到注册中心Eureka
- 引入Eureka客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 配置
eureka:
client:
register-with-eureka: true
service-url:
# 设置与Eureka Server交互的地址 查询服务和注册服务都需要依赖这个地址
defaultZone: http://localhost:7001/eureka/
@EnableDiscoveryClient
开启服务注册发现
package study.wyy.cloud.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 2:51 下午
*/
@SpringBootApplication
@EnableCircuitBreaker
@EnableDiscoveryClient
public class PaymentHystrix {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrix.class, args);
}
}
搭建服务消费者
- 新建一个model,导入Eureka,open feign,hystrix依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入我们的api-common-->
<dependency>
<groupId>study.wyy</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<!--使用Eureka做注册中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--open feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
- 定义一个Feign接口,调用服务提供者
package study.wyy.cloud.hystrix.order.consumer;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.concurrent.TimeoutException;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 6:00 下午
*/
@FeignClient(value = "cloud-consumer-hystic")
public interface PaymentHystrixService {
/**
* 使用Feign的时候,如果参数中带有
*
* @PathVariable 形式的参数,则要用value=""标明对应的参数,否则会抛出IllegalStateException异常
*/
@GetMapping("/provider/hystrix/timeout/{time}")
String testTimeout(@PathVariable Integer time) throws TimeoutException;
@GetMapping("/provider/hystrix/error")
String testError();
}
- application配置
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://localhost:7001/eureka/
## 配置一下超时时间,默认就是1s
ribbon:
ReadTimeout: 8000
ConnnectTimeout: 5000
- controller
package study.wyy.cloud.hystrix.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import study.wyy.cloud.hystrix.service.HystrixService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 2:25 下午
*/
@RestController
public class HystrixController {
private final HystrixService hystrixService;
@Autowired
public HystrixController(HystrixService hystrixService) {
this.hystrixService = hystrixService;
}
@GetMapping("/provider/hystrix/timeout/{time}")
public String testTimeout(@PathVariable Integer time) throws TimeoutException {
return hystrixService.testTimeout(time);
}
@GetMapping("/provider/hystrix/error")
public String testError(){
return hystrixService.testError();
}
}
- 启动类,开启feign还有服务发现
package study.wyy.cloud.hystrix.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 6:19 下午
*/
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class OrderHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixApplication.class,args);
}
}
- controller
package study.wyy.cloud.hystrix.order.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import study.wyy.cloud.hystrix.order.consumer.PaymentHystrixService;
import java.util.concurrent.TimeoutException;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 6:13 下午
*/
@RestController
public class OrderHystrixController {
private final PaymentHystrixService paymentHystrixService;
public OrderHystrixController(PaymentHystrixService paymentHystrixService) {
this.paymentHystrixService = paymentHystrixService;
}
@GetMapping("/consumer/hystrix/timeout/{time}")
public String testTimeout(@PathVariable Integer time) throws TimeoutException {
return paymentHystrixService.testTimeout(time);
}
@GetMapping("/consumer/hystrix/error")
public String testError(){
return paymentHystrixService.testError();
}
}
- 启动测试是否可以调用成功
- 启动注册中心Eureka
- 启动服务提供者
- 启动消费者
测试超时效果:
服务消费者侧服务降级
服务提供者内部认为5秒是超时时间,但是我们消费者认为调用外部的这个服务,超过3s就认为是超时了呢,所以消费者侧也可以进行服务降级配置
- 刚刚依赖已经导入了
- 使用
@EnableHystrix
开启Hystrix
package study.wyy.cloud.hystrix.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 6:19 下午
*/
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
@EnableHystrix
public class OrderHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixApplication.class,args);
}
}
- 降级配置
package study.wyy.cloud.hystrix.order.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import study.wyy.cloud.hystrix.order.consumer.PaymentHystrixService;
import java.util.concurrent.TimeoutException;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 6:13 下午
*/
@RestController
public class OrderHystrixController {
private final PaymentHystrixService paymentHystrixService;
public OrderHystrixController(PaymentHystrixService paymentHystrixService) {
this.paymentHystrixService = paymentHystrixService;
}
@GetMapping("/consumer/hystrix/timeout/{time}")
@HystrixCommand(
fallbackMethod ="invokeProviderTimeoutHandler",
commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
}
)
public String testTimeout(@PathVariable Integer time) throws TimeoutException {
return paymentHystrixService.testTimeout(time);
}
@HystrixCommand(
fallbackMethod ="invokeProviderErrorHandler"
)
@GetMapping("/consumer/hystrix/error")
public String testError(){
return paymentHystrixService.testError();
}
public String invokeProviderTimeoutHandler(Integer time){
return "consumer invoke provider timeout, please wait";
}
public String invokeProviderErrorHandler(){
return "consumer invoke provider error, please wait";
}
}
- 测试
4秒的时候,就执行超时处理
2秒正常
testError接口测试:
2.3 Feign和Hystrix结合
刚刚上面其实和刚刚服务提供者一样的玩法,controller里面的接口只是调用了提供者的方法,没有调用消费者自身的业务逻辑,所以是可以这么玩的,我不能把每个调用外部接口的地方单独包装一下,使用HystrixCommand注解去做,是否可以将Feign和Hystrix结合呢,直接在Feign的接口上做文章呢,当然是可以的啦: 如何玩呢
FeignClient这个注解的fallback属性来完成这件事,指定这个Feign接口的服务降级处理的类
- 这个类必须实现这个Feign接口
- 将controller中关于服务降级的代码都去掉:
package study.wyy.cloud.hystrix.order.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import study.wyy.cloud.hystrix.order.consumer.PaymentHystrixService;
import java.util.concurrent.TimeoutException;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 6:13 下午
*/
@RestController
public class OrderHystrixController2 {
private final PaymentHystrixService paymentHystrixService;
public OrderHystrixController2(PaymentHystrixService paymentHystrixService) {
this.paymentHystrixService = paymentHystrixService;
}
@GetMapping("/consumer2/hystrix/timeout/{time}")
public String testTimeout(@PathVariable Integer time) throws TimeoutException {
return paymentHystrixService.testTimeout(time);
}
@GetMapping("/consumer2/hystrix/timeout2/{time}")
public String testTimeout2(@PathVariable Integer time) throws TimeoutException {
return paymentHystrixService.testTimeout2(time);
}
@GetMapping("/consumer2/hystrix/error")
public String testError(){
return paymentHystrixService.testError();
}
}
这里提供了两个方法测试方法去调用testTimeout接口,往下看就知道了。。。
- 定义fallback类,实现Feign接口,并注入到spring容器中
package study.wyy.cloud.hystrix.order.consumer;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.concurrent.TimeoutException;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 6:00 下午
*/
@FeignClient(value = "cloud-consumer-hystic",fallback = PaymentHystrixFallbackService.class)
public interface PaymentHystrixService {
/**
* 使用Feign的时候,如果参数中带有
*
* @PathVariable 形式的参数,则要用value=""标明对应的参数,否则会抛出IllegalStateException异常
*/
@GetMapping("/provider/hystrix/timeout/{time}")
String testTimeout(@PathVariable(value = "time") Integer time) throws TimeoutException;
@GetMapping("/provider/hystrix/timeout/{time}")
String testTimeout2(@PathVariable(value = "time") Integer time) throws TimeoutException;
@GetMapping("/provider/hystrix/error")
String testError();
}
package study.wyy.cloud.hystrix.order.consumer;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeoutException;
/**
* @author by wyaoyao
* @Description
* @Date 2020/12/20 7:40 下午
*/
@Component
public class PaymentHystrixFallbackService implements PaymentHystrixService{
@Override
public String testTimeout(Integer time) throws TimeoutException {
return "invoke provider time out on testTimeout";
}
@Override
public String testTimeout2(Integer time) throws TimeoutException {
return "invoke provider time out on testTimeout2";
}
@Override
public String testError() {
return "invoke provider time error";
}
}
- 如何指定配置超时时间呢
# 开启feign的熔断
feign:
hystrix:
enabled: true
hystrix:
command:
# 全局默认
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 3000
# 如何单独指定,类名#方法名(参数类型)为指定的接口方法配置超时时间,
# 刚刚增加testTimeout2就是为了测试这个滴
PaymentHystrixService#testTimeout2(Integer):
fallback:
enabled: true
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon超时时间为准,为true,则超时以熔断时间为准,默认就是true
enabled: true
isolation:
#隔离策略,有THREAD和SEMAPHORE
#THREAD - 它在单独的线程上执行,并发请求受线程池中的线程数量的限制
#SEMAPHORE - 它在调用线程上执行,并发请求受到信号量计数的限制
#对比:https://www.cnblogs.com/java-synchronized/p/7927726.html
thread:
timeoutInMilliseconds: 400
- 测试这两个接口
testTimeout:
testTimeout2:
2.4 全局服务降级处理
刚刚每个方法都配置了服务降级处理,如果每个都配置,就会出现代码膨胀,有没有全局降级处理呢?