Hystrix断路器在微服务网关中的应用(Spring Cloud Gateway)

2023-10-07 20:40

本文主要是介绍Hystrix断路器在微服务网关中的应用(Spring Cloud Gateway),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前文回顾

在之前的一篇文章:微服务网关Zuul迁移到Spring Cloud Gateway,我们讲解了如何从Zuul迁移到新的组件:Spring Cloud Gateway,以及扩展了微服务网关的功能,包括限流过滤器、断路器过滤器等。然而很多读者在使用的时候反馈,使用POSTMAN发送GET请求测试断路器是正常的,然而POST请求会出现:

{"timestamp": "2018-10-11T13:07:07.790+0000","path": "/user/body","status": 500,"error": "Internal Server Error","message": "fallbackcmd failed and fallback failed."
}

看一下网关服务的控制台,HystrixGatewayFilterFactory也报错。

com.netflix.hystrix.exception.HystrixRuntimeException: fallbackcmd failed and fallback failed.at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:825) ~[hystrix-core-1.5.12.jar:1.5.12]at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804) ~[hystrix-core-1.5.12.jar:1.5.12]at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140) ~[rxjava-1.3.8.jar:1.3.8]at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87) ~[rxjava-1.3.8.jar:1.3.8]at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87) ~[rxjava-1.3.8.jar:1.3.8]at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1472) ~[hystrix-core-1.5.12.jar:1.5.12]at com.netflix.hystrix.AbstractCommand$FallbackHookApplication$1.onError(AbstractCommand.java:1397) ~[hystrix-core-1.5.12.jar:1.5.12]at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87) ~[rxjava-1.3.8.jar:1.3.8]at rx.internal.reactivestreams.SubscriberAdapter.onError(SubscriberAdapter.java:59) ~[rxjava-reactive-streams-1.2.1.jar:1.2.1]

本文主要是解决Hystrix过滤器应用过程中的报错问题,并提供正确的使用方式。

问题分析

熔断机制和日常生活中见到电路保险丝是非常相似的,当出现了问题之后,保险丝会自动烧断,以保护我们的电器。在我们的对外提供服务时,当现在服务的提供方出现了问题之后整个的程序将出现错误的信息显示,而这个时候如果不想出现这样的错误信息,而希望替换为一个错误时的内容。

一个服务挂了后续的服务跟着不能用了,这就是雪崩效应。

我们在网关配置了Hystrix断路器的过滤器:

      routes:- id: hytstrix_routeuri: lb://userorder: 6000predicates:- Path=/user/**filters:- StripPrefix=1- name: Hystrixargs:name: fallbackcmdfallbackUri: forward:/fallbackcontroller?a=123

出现错误之后可以 fallback 错误的处理信息。此外,Hystrix断路器经常结合 Feign一起使用,还需要在Feign(客户端)进行熔断的配置。

依赖版本

spring-boot-starter-parent的版本为2.0.3.RELEASE
Spring Cloud的版本为Finchley.RELEASE,对应的spring-cloud-gateway版本为2.0.0.RELEASE

报错分析

使用POSTMAN发送GET请求,不会出现第一小节的异常。当改为POST请求之后,HystrixGatewayFilterFactory抛出异常。使得刚开始的猜想往为什么不支持POST请求上考虑。打开debug日志,我们得到如下更为详细的输出:

AbstractCommand$22.call[821] : HystrixCommand execution COMMAND_EXCEPTION and fallback failed.java.lang.IllegalArgumentException: Actual request host must not be null
at org.springframework.util.Assert.notNull(Assert.java:193)
at org.springframework.web.cors.reactive.CorsUtils.isSameOrigin(CorsUtils.java:74)
at org.springframework.web.cors.reactive.DefaultCorsProcessor.process(DefaultCorsProcessor.java:70)
at org.springframework.web.reactive.handler.AbstractHandlerMapping.lambda$getHandler$1(AbstractHandlerMapping.java:152)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:107)
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:1640)
at

从上面的日志可以看出,最后的错误定位到了默认的CORS处理器DefaultCorsProcessor的实现。

public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,HttpServletResponse response) throws IOException {//是否为CORS请求(包含Origin头部)if (!CorsUtils.isCorsRequest(request)) {return true; //不是则直接返回}ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);//根据serverResponse响应判断Access-Control-Allow-Originif (responseHasCors(serverResponse)) {logger.debug("Skip CORS processing: response already contains \"Access-Control-Allow-Origin\" header");return true;}ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);if (WebUtils.isSameOrigin(serverRequest)) {logger.debug("Skip CORS processing: request is from same origin");return true;}boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);if (config == null) {if (preFlightRequest) {rejectRequest(serverResponse);return false;}else {return true;}}return handleInternal(serverRequest, serverResponse, config, preFlightRequest);}

如上是报错部分的代码,这段代码的功能是基于cors的配置,处理给定的请求。首先判断是否为CORS的请求,是则直接返回true;否则判断响应中的头部Access-Control-Allow-Origin是否为空(Access-Control-Allow-Origin是HTML5中定义的一种解决资源跨域的策略。他是通过服务器端返回带有Access-Control-Allow-Origin标识的Response header,用来解决资源的跨域权限问题,表示接受哪些域名的请求);否则
基于Origin、Host、Forwarded、X-Forwarded-Proto、X-Forwarded-Host、X-Forwarded-Port等头部,校验请求是否同源。

对于非简单请求,CORS机制跨域会首先进行 preflight(一个 OPTIONS 请求), 该请求成功后才会发送真正的请求。 这一设计旨在确保服务器对 CORS 标准知情,以保护不支持CORS的旧服务器。

到这一步,会判断CORS的配置是否为空,如果为空,且不是一个preflight请求,则返回true,否则返回false;再下一步进入CORS的配置不为空的处理逻辑,此处略过。

这里我们拓展一下,浏览器将CORS请求分为两类:简单请求(simple request)和非简单请求(not-simple-request),简单请求浏览器不会预检,而非简单请求会预检。

这两种方式怎么区分?同时满足下列三大条件,就属于简单请求,否则属于非简单请求

  1. 请求方式只能是:GET、POST、HEAD
  2. HTTP请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
  3. Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain。

对于简单请求,浏览器直接请求,会在请求头信息中,增加一个origin字段,来说明本次请求来自哪个源(协议+域名+端口)。服务器根据这个值,来决定是否同意该请求,服务器返回的响应会多几个头信息字段,如下所示:

  1. Access-Control-Allow-Origin:该字段是必须的,* 表示接受任意域名的请求,还可以指定域名
  2. Access-Control-Allow-Credentials:该字段可选,是个布尔值,表示是否可以携带cookie,(注意:如果Access-Control-Allow-Origin字段设置*,此字段设为true无效)
  3. Access-Control-Allow-Headers:该字段可选,里面可以获取Cache-Control、Content-Type、Expires等,如果想要拿到其他字段,就可以在这个字段中指定。

上面的头信息中,三个与CORS请求相关,都是以Access-Control-开头。

非简单请求是对那种对服务器有特殊要求的请求,比如请求方式是PUT或者DELETE,或者Content-Type字段类型是application/json。都会在正式通信之前,增加一次HTTP请求,称之为预检。浏览器会先询问服务器,当前网页所在域名是否在服务器的许可名单之中,服务器允许之后,浏览器会发出正式的XMLHttpRequest请求,否则会报错。

回顾我们的业务场景,来自客户端的请求,到达网关后将会转发到具体服务,此时对应的服务是down的状态,返回的响应结果为空。我们在网关没有任何的CORS配置,因此按照上述的CORS处理逻辑,返回的结果为false。

当目标服务的状态是正常的,请求得到相应,CORS处理是正常的;因此,出错的根源在于,当我们的请求头中携带Origin时,目标服务的不可用将会导致如上的错误,这显然不是我们想要的结果。

解决思路

对于上述问题,围绕CORS的处理,我们有如下几种解决思路。

移除请求的头部Origin

移除请求的头部Origin,从CORS处理的逻辑得知,当该请求不是一个CORS请求(即不包含头部Origin),处理的过程就结束,这样可以避免后续的检查。

修改配置如下:

      routes:- id: hytstrix_routeuri: lb://userorder: 6000predicates:- Path=/user/**filters:- StripPrefix=1- RemoveRequestHeader=Origin- name: Hystrixargs:name: fallbackcmdfallbackUri: forward:/fallbackcontroller?a=123

再次发送请求,无论是GET还是POST,携带头部Origin都可以正常fallback。

CORS配置

我们还可以增加CORS的过滤器,手动增加响应的头部信息。

@Beanpublic WebFilter corsFilter() {return (ServerWebExchange ctx, WebFilterChain chain) -> {ServerHttpRequest request = ctx.getRequest();if (CorsUtils.isCorsRequest(request)) {ServerHttpResponse response = ctx.getResponse();HttpHeaders headers = response.getHeaders();headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);headers.add("Access-Control-Max-Age", MAX_AGE);headers.add("Access-Control-Allow-Headers",ALLOWED_HEADERS);if (request.getMethod() == HttpMethod.OPTIONS) {response.setStatusCode(HttpStatus.OK);return Mono.empty();}}return chain.filter(ctx);};}

Spring Cloud Gateway版本升级

2.0.1.RELEASE版本开始,Spring Cloud Gateway提供了全局cors的配置:

spring:cloud:gateway:globalcors:corsConfigurations:'[/**]':allowedOrigins: "*"allowedMethods: "*"

通过如上配置,可以实现与上一小节相同的功能。

小结

本文主要讲了Hystrix过滤器在网关中的应用时遇到的问题,通过错误信息,debug源码寻找问题的根源。之后我们分析了问题,并根据问题的根源提出了几种可行的解决方案。解决问题的方法有多种,本文主要是提供一个排查问题和解决问题的思路。

订阅最新文章,欢迎关注我的公众号

微信公众号

参考
  1. Hystrix filter throw exception with post request
  2. http跨域时的options请求

这篇关于Hystrix断路器在微服务网关中的应用(Spring Cloud Gateway)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/160296

相关文章

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Spring Boot 配置文件之类型、加载顺序与最佳实践记录

《SpringBoot配置文件之类型、加载顺序与最佳实践记录》SpringBoot的配置文件是灵活且强大的工具,通过合理的配置管理,可以让应用开发和部署更加高效,无论是简单的属性配置,还是复杂... 目录Spring Boot 配置文件详解一、Spring Boot 配置文件类型1.1 applicatio

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.