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基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Spring Gateway动态路由实现方案

《SpringGateway动态路由实现方案》本文主要介绍了SpringGateway动态路由实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录前沿何为路由RouteDefinitionRouteLocator工作流程动态路由实现尾巴前沿S

idea+spring boot创建项目的搭建全过程

《idea+springboot创建项目的搭建全过程》SpringBoot是Spring社区发布的一个开源项目,旨在帮助开发者快速并且更简单的构建项目,:本文主要介绍idea+springb... 目录一.idea四种搭建方式1.Javaidea命名规范2JavaWebTomcat的安装一.明确tomcat

SpringBoot中ResponseEntity的使用方法举例详解

《SpringBoot中ResponseEntity的使用方法举例详解》ResponseEntity是Spring的一个用于表示HTTP响应的全功能对象,它可以包含响应的状态码、头信息及响应体内容,下... 目录一、ResponseEntity概述基本特点:二、ResponseEntity的基本用法1. 创

springboot依靠security实现digest认证的实践

《springboot依靠security实现digest认证的实践》HTTP摘要认证通过加密参数(如nonce、response)验证身份,避免明文传输,但存在密码存储风险,相比基本认证更安全,却因... 目录概述参数Demopom.XML依赖Digest1Application.JavaMyPasswo

Spring Boot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)

《SpringBoot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)》本文将以一个实际案例(用户管理系统)为例,详细解析SpringBoot中Co... 目录引言:为什么学习Spring Boot分层架构?第一部分:Spring Boot的整体架构1.1

利用Python操作Word文档页码的实际应用

《利用Python操作Word文档页码的实际应用》在撰写长篇文档时,经常需要将文档分成多个节,每个节都需要单独的页码,下面:本文主要介绍利用Python操作Word文档页码的相关资料,文中通过代码... 目录需求:文档详情:要求:该程序的功能是:总结需求:一次性处理24个文档的页码。文档详情:1、每个

Spring的基础事务注解@Transactional作用解读

《Spring的基础事务注解@Transactional作用解读》文章介绍了Spring框架中的事务管理,核心注解@Transactional用于声明事务,支持传播机制、隔离级别等配置,结合@Tran... 目录一、事务管理基础1.1 Spring事务的核心注解1.2 注解属性详解1.3 实现原理二、事务事

SpringBoot集成WebService(wsdl)实践

《SpringBoot集成WebService(wsdl)实践》文章介绍了SpringBoot项目中通过缓存IWebService接口实现类的泛型入参类型,减少反射调用提升性能的实现方案,包含依赖配置... 目录pom.XML创建入口ApplicationContextUtils.JavaJacksonUt