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中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

springboot将lib和jar分离的操作方法

《springboot将lib和jar分离的操作方法》本文介绍了如何通过优化pom.xml配置来减小SpringBoot项目的jar包大小,主要通过使用spring-boot-maven-plugin... 遇到一个问题,就是每次maven package或者maven install后target中的ja

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

Spring AI Alibaba接入大模型时的依赖问题小结

《SpringAIAlibaba接入大模型时的依赖问题小结》文章介绍了如何在pom.xml文件中配置SpringAIAlibaba依赖,并提供了一个示例pom.xml文件,同时,建议将Maven仓... 目录(一)pom.XML文件:(二)application.yml配置文件(一)pom.xml文件:首

SpringBoot+MyBatis-Flex配置ProxySQL的实现步骤

《SpringBoot+MyBatis-Flex配置ProxySQL的实现步骤》本文主要介绍了SpringBoot+MyBatis-Flex配置ProxySQL的实现步骤,文中通过示例代码介绍的非常详... 目录 目标 步骤 1:确保 ProxySQL 和 mysql 主从同步已正确配置ProxySQL 的

Spring Boot整合log4j2日志配置的详细教程

《SpringBoot整合log4j2日志配置的详细教程》:本文主要介绍SpringBoot项目中整合Log4j2日志框架的步骤和配置,包括常用日志框架的比较、配置参数介绍、Log4j2配置详解... 目录前言一、常用日志框架二、配置参数介绍1. 日志级别2. 输出形式3. 日志格式3.1 PatternL

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

Springboot 中使用Sentinel的详细步骤

《Springboot中使用Sentinel的详细步骤》文章介绍了如何在SpringBoot中使用Sentinel进行限流和熔断降级,首先添加依赖,配置Sentinel控制台地址,定义受保护的资源,... 目录步骤 1: 添加 Sentinel 依赖步骤 2: 配置 Sentinel步骤 3: 定义受保护的

配置springboot项目动静分离打包分离lib方式

《配置springboot项目动静分离打包分离lib方式》本文介绍了如何将SpringBoot工程中的静态资源和配置文件分离出来,以减少jar包大小,方便修改配置文件,通过在jar包同级目录创建co... 目录前言1、分离配置文件原理2、pom文件配置3、使用package命令打包4、总结前言默认情况下,

如何使用Spring boot的@Transactional进行事务管理

《如何使用Springboot的@Transactional进行事务管理》这篇文章介绍了SpringBoot中使用@Transactional注解进行声明式事务管理的详细信息,包括基本用法、核心配置... 目录一、前置条件二、基本用法1. 在方法上添加注解2. 在类上添加注解三、核心配置参数1. 传播行为(