Spring Cloud系列之 HystrixZuulGateway
Posted AC_Jobim
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud系列之 HystrixZuulGateway相关的知识,希望对你有一定的参考价值。
Spring Cloud系列(三)之 Hystrix、Zuul、Gateway
使用SpringCloud版本:
2.1.1.RELEASE
一、Hystrix断路器
1.1 Hystrix 简介
服务雪崩:
- 在微服务架构中,服务与服务之间通过远程调用的方式进行通信,一旦某个被调用的服务发生了故障,其依赖服务也会发生故障,此时就会发生故障的蔓延,最终导致系统瘫痪。这就是所谓的
"雪崩效应"
。
什么是Hystrix?
-
是一个用于处理分布式系统的
延迟
和容错
的开源库。在分布式系统中,许多依赖不可避免的会调用失败,比如:超时、异常等原因。Hystrix 能够保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
-
Hystrix实现了断路器模式,当某个服务发生故障时,通过断路器的监控,给调用方返回一个错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程,从而防止故障的蔓延。
-
Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。
目前Hystrix停更进入维护,官网推荐我们使用 resilience4j,在国外比较流行。但是在国内,结合国内情况,推荐大家使用阿里的 Sentineal
Hystrix重要概念:
-
服务降级
- 服务降级是指当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作
- 服务降级处理是在客户端(Consumer 端)实现完成的,与服务端(Provider 端)没有关系。当某个 Consumer 访问一个 Provider 却迟迟得不到响应时执行预先设定好的一个解决方案,而不是一直等待。
-
服务熔断
- 熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速响应错误信息。当检测到该节点微服务调用响应正常后恢复调用链路。
服务降级和服务熔断的区别?
服务降级 → 当前服务还是可用的;
(比如有10个线程,谁抢到谁用,抢不到如果超时报提错误提示,下一次抢到还能继续提供服务)服务熔断 → 当前服务不可用,但是它会逐渐恢复服务提供;
(拉闸,整个家用电器都不能用;然后测试开5个电器没问题,6个也没问题,逐渐的就恢复服务提供)
-
服务限流
服务限流场景:一般应用在
秒杀
,高并发
等操作。严禁一窝蜂的过来拥挤,大家排队,一秒钟只允许通过 N 个,有序进行
Hystrix应用场景分析:
- 对于OrderFeign的案例,在非高并发的情况下,请求可以正常执行。但在高并发的情况下,tomcat 线程里面的工作线程被抢占,可能会导致请求响应缓慢地问题,所有才有
服务降级
/服务熔断
/服务限流
等技术诞生
1.2 Hystrix实现服务降级(重点)
哪些情况会触发降级?
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
服务降级即可以放在服务提供端,也可以放在服务消费端
1.2.1 服务端提供端实现服务降级
模块名称定义为:
cloud-provider-hystrix-payment8001
,来充当提供服务的角色。用到的是@HystrixCommand
注解
-
引入pom.xml 依赖
<!--引入 spring-cloud-hystrix 依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
在主启动类,添加
@EnableCircuitBreaker
注解@SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker //激活Hystrix断路器 public class PaymentHystrixMain8001 public static void main(String[] args) SpringApplication.run(PaymentHystrixMain8001.class,args);
-
在服务提供端Service中添加调用方法与服务降级方法,方法上需要添加
@HystrixCommand
注解参数
execution.isolation.thread.timeoutInMilliseconds
,在设定的时间范围内返回,即执行正常流程;超过指定时间,则执行指定的服务降级方法。@Service public class PaymentService /** * 超时访问,演示降级 */ @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000") //规定该线程的超时时间为5秒的时候,执行服务降级方法 ) public String paymentInfo_TimeOut(Integer id) int timeNumber = 10; // 设置睡眠10秒 // int age = 10/0; // 出现异常也发生服务降级 try TimeUnit.SECONDS.sleep(timeNumber); catch (InterruptedException e) e.printStackTrace(); return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\\t"+"O(∩_∩)O,耗费"+timeNumber+"秒"; public String paymentInfo_TimeOutHandler(Integer id) return "/(ㄒoㄒ)/调用支付接口超时或异常:\\t"+ "\\t当前线程池名字" + Thread.currentThread().getName();
-
测试:
- 当设置
TimeUnit.SECONDS.sleep(3)
,这种情况下,便会执行正常的方法; - 当设置
TimeUnit.SECONDS.sleep(10)
,由于10s > 5s 已经超时,便执行降级方法; - 当设置
int age = 10/0
,由于异常原因,会直接进入降级方法
- 当设置
1.2.2 消费端实现服务降级(重点)
模块名称定义为:
cloud-consumer-feign-hystrix-order80
,来充当提供消费者的角色。
-
application.yml 配置文件修改
消费端添加配置
feign.hystrix.enabled=true
。如果在消费端调用服务端时,此处涉及到Feign 超时控制
这个概念。需要配置ribbon.ConnectTimeout 和 ribbon.ReadTimeout
属性。server: port: 80 spring: application: name: cloud-provider-hystrix-order eureka: client: register-with-eureka: true #示表不向注册中心注册自己 fetch-registry: true #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务 service-url: defaultZone: http://eureka7001.com:7001/eureka/ ####################以下配置需要新增(ribbon相关配置和Feign超时相关)################## #全局配置 # 请求连接的超时时间 默认的时间为 1 秒 # 设置feign客户端超时时间(OpenFeign默认支持ribbon) ribbon: #指的是建立连接后从服务器读取到可用资源所用的时间 ReadTimeout: 5000 #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 ConnectTimeout: 5000
-
主启动类,添加
@EnableHystrix
注解@SpringBootApplication @EnableFeignClients @EnableHystrix public class OrderHystrixMain80 public static void main(String[] args) SpringApplication.run(OrderHystrixMain80.class,args);
-
消费端在controller层实现自己的服务降级
@RestController @Slf4j public class OrderHystrixController @Resource private PaymentHystrixService paymentHystrixService; @GetMapping("/consumer/payment/hystrix/ok/id") public String paymentInfo_OK(@PathVariable("id") Integer id) String result = paymentHystrixService.paymentInfo_OK(id); return result; @GetMapping("/consumer/payment/hystrix/timeout/id") @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500") ) public String paymentInfo_TimeOut(@PathVariable("id") Integer id) throws InterruptedException // System.out.println(10/0); TimeUnit.SECONDS.sleep(3); String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
-
测试
消费端根据自己的情况,进行服务降级设置,设置为1.5s。服务端
TimeUnit.SECONDS.sleep(3)
设置睡眠 3s(或者抛出异常)。显然在设置的 1.5s 内无法返回数据。最终便会执行服务端的降级方法。返回我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)
提示
1.2.2 服务降级配置存在的问题及解决
- 每个业务方法对应一个兜底的方法,代码膨胀
业务方法
和自定义降级方法
混合在一起(业务逻辑方法和处理服务降级、熔断方法 揉在一块)
1.2.2.1 代码膨胀问题
- 服务间使用 Feign 进行接口调用,针对每个业务一个兜底方法的问题。
解决方式:
我们可以定义一个全局通用的服务降级
方法,这样就可以使用全局服务降级
方法,解决代码膨胀问题。
步骤:
- 使用
@DefaultProperties(defaultFallback = "xxx")
的方式,定义全局服务降级
方法; - 在类中,定义
全局降级方法
,名称需要与defaultFallback = "xxx"
定义一致; - 在需要
服务降级
的方法上,添加@HystrixCommand
; - 只有一个
@HystrixCommand 标签
,则使用全局"服务降级"
方法;如果是@HystrixCommand(fallbackMethod = "xxx",commandProperties = xxx)
这样定义,则使用指定的"服务降级"
方法。
@RestController
@Slf4j
//使用@DefaultProperties 定义全局服务降级方法
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController
@Resource
private PaymentHystrixService paymentHystrixService;
/**
* 使用全局服务降级方法
*/
@GetMapping("/consumer/payment/hystrix/ok/id")
@HystrixCommand //加了@DefaultProperties属性注解,并且没有写具体方法名字,就用统一全局的
public String paymentInfo_OK(@PathVariable("id") Integer id)
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
/**
* 使用指定的服务降级方法
*/
@GetMapping("/consumer/payment/hystrix/timeout/id")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties =
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
)
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
//指定服务降级方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
//定义全局服务降级方法(不能有参数)
public String payment_Global_FallbackMethod()
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
1.2.2.1 业务方法和降级方法混合在一起问题(重点)
- 解决混合在一起的问题,只需要为 Feign 定义的接口
添加统一的服务降级类
即可实现解耦。即:来一个实现类,实现Feign 接口,并重写方法
(重写的就是"服务降级"处理方法)。
步骤:
- 客户端
针对使用 Feign 组件调用的接口,重新新建一个类来实现该接口,统一为接口里面的方法进行异常处理
; - 客户端 Feign 接口,添加注解
@FeignClient(value = "服务名",fallback = 接口实现类.class)
进行指定。
测试:
在 客户端
调用 服务端
接口,如果客户端此时突然 宕机
,便会触发客户端服务降级方法。最终返回的是 Feign 接口实现类
中重写的内容。
1.3 Hystrix 实现服务熔断(重点)
-
类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时
停止对该服务的调用
。过一段时间,服务器会慢慢进行恢复,直到完全恢复服务提供。 -
服务熔断过程:
请求太多
→服务器扛不住,停止服务
→一段时间后
→逐渐接受请求处理
→最终恢复服务
服务熔断,在 服务端
进行配置。使用到的还是 @HystrixCommand
注解
-
配置服务熔断(
@HystrixCommand
注解)fallbackMethod
来定义服务降级处理方式;@HystrixProperty
属性,用来定义服务熔断相关参数(具体有哪些参数可配置,你可以在HystrixCommandProperties.java 类中查看)- 下面配置的含义: 在 10s 内,10次请求如果失败率 > 60%,此时断路器开关打开,整个服务不可用。随着请求正确率的提升,服务也会逐渐恢复。
@Service public class PaymentService //=========服务熔断,配置在10秒内,10次请求中有60%都是失败的,则触发服务熔断 @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = @HystrixProperty(name = "circuitBreaker.enabled",value = "true"), // 是否开启断路器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), // 请求次数 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), // 失败率达到多少后跳闸 ) public String paymentCircuitBreaker(@PathVariable("id") Integer id) if(id < 0) throw new RuntimeException("******id 不能负数"); String serialNumber = IdUtil.simpleUUID(); // 等价于UUID.randomUUID().toString(); return Thread.currentThread().getName()+"\\t"+"调用成功,流水号: " + serialNumber; public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
-
编写服务提供者Controller
@RestController @Slf4j public class PaymentController @Resource private PaymentService paymentService; //===服务熔断 @GetMapping("/payment/circuit/id") public String paymentCircuitBreaker(@PathVariable("id") Integer id) String result = paymentService.paymentCircuitBreaker(id); log.info("*******result:"+result); return result;
-
服务熔断测试
-
进行服务熔断测试,
http://localhost:8001/payment/circuit/id
请求调用。当 id > 0,正确返回;当 id < 0 时,抛出异常,进入服务降级
方法。 -
当我们输入 id < 0,连续发送请求,满足 10s 内,10次请求如果失败率 > 60%,此时断路器开关打开。此时再次输入 id > 0 时,由于整个服务不可用,还是会进入到
服务降级
方法。连续多次发送 id > 0 请求,此时正确率逐渐提高,服务也会逐渐恢复。
-
断路器开启/关闭条件:
Hystrix 全部配置:
@HystrixCommand(fallbackMethod = "str_fallbackMethod",
groupKey = "strGroupCommand",
commandKey = "strCommand",
threadPoolKey = "strThreadPool",
commandProperties =
//设置执行隔离策略,THREAD 表示线程池 SEMAPHORE:信号量隔离 默认为THREAD线程池
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置命令执行的超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否启用超时时间
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 执行超时的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 执行被取消的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允许回调方法执行的最大并发数
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服务降级是否启用,是否执行回调函数
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 设置断路器是否起作用。
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
// 如果滚动时间窗(默认10s)内仅收到了19个请求,及时这19个请求都失败了,断路也不会打开。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过 circuitBreaker.requestVolumeThreshold 的情况下,
// 如果错误请求数的百分比超过 50,就把断路器设置为"打开"状态,否则就设置为"关闭"状态
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 该属性用来设置当断路器打开之后的休眠时间窗。休眠时间窗结束之后,会将断路器置为"半开"状态,
// 尝试熔断的请求命令,如果依然失败就将断路器继续设置为"打开"状态,如果成功就设置为"关闭"状态
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 断路器强制打开
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 断路器强制关闭
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滚动时间窗设置,该时间用于断路器判断健康度时,需要收集信息的持续时间
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 该属性用来设置滚动时间窗统计指标信息时,划分"桶"的数量,断路器在手机指标信息的时候会根据设置的时间窗长度拆分成多个"桶"来累计各度量值,每个
// "桶"记录了一段时间内的采集指标。比如 10 秒内拆分成 10 个"桶'收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 该属性用来设置对命令执行的延迟是否采用百分位数来跟踪和计算。如果设置为 false,name所有的概要统计都将返回-1
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 该属性用来设置百分位统计滚动窗口中使用 "桶" 的数量
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 该属性用来设置在执行过程中每个"桶"中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数
// 就从最初的位置开始重写。例如,将该值设置为100,滚动窗口为10秒,若在10秒内一个"桶"中发生了500次执行,
// 那么该"桶"中只保留最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 该属性用来设置采集意向断路器状态的健康快照(请求的成功、错误百分比)的间隔等待时间
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否开启请求缓存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand 的执行和事件是否打印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true")
,
threadPoolProperties =
// 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
@HystrixProperty(name = "coreSize", value = "10"),
// 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,否则将使用 LinkedBlockingQueue 实现的队列
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 该参数用来为队列设置拒绝阈值。通过该参数,即使队列没有达到最大值也能拒绝请求。该参数主要是对 LinkedBlockingQueue 队列的补充,因为LinkedBlockingQueue
// 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了
@HystrixProperty(name = "queueSizeRejectionThreshold", value = 以上是关于Spring Cloud系列之 HystrixZuulGateway的主要内容,如果未能解决你的问题,请参考以下文章
Spring Cloud系列之 RibbonOpenFeign
Spring Cloud系列之 EurekaZookeeperConsul