解决Spring Gateway配置单个路由超时时间不生效的问题

之前springcloud gateway项目是的路由配置都是静态配置在项目的application.yml文件中,不能实现路由的热更新。前期业务发展也比较缓慢,新增路由的场景频率不是很高,最近业务越来越广,新增项目频率明显升高,所以想着把路由配置提到apollo中,本想着很简单的一个事情,结果谁知坑一个比一个深,接下来讲一讲都有啥坑,又该如何填这些坑。

如何接入apollo

springcloud gateway支持通过propertiesyml进行配置,下文简称gateway。

properties示例

spring.cloud.gateway.routes[0].id = geektao
spring.cloud.gateway.routes[0].uri = http://www.geektao.ai
spring.cloud.gateway.routes[0].predicates[0] = Path=/geektao/**
spring.cloud.gateway.routes[0].filters[0] = StripPrefix=1

yml示例

spring:
  cloud:
    gateway:
      routes:
      - id: geektao
        uri: http://www.geektao.ai
        predicates:
        - Path=/geektao/**
        filters:
        - StripPrefix=1

使用properties进行配置的话,为了遵循springboot项目的规范需要指定routes的索引,还得有序递增,太麻烦,这时候yml格式就很香,所以建议使用yml格式进行配置。

首先,创建namesapce
在这里插入图片描述

apollo默认的namespaceapplicationproperties格式的,不符合我们的使用场景,所以新建一个yml格式的namespace
在这里插入图片描述

gateway项目引入apollo客户端依赖。

<dependency>
  <groupId>com.ctrip.framework.apollo</groupId>
  <artifactId>apollo-client</artifactId>
  <version>${apollo.version}</version>
</dependency>

在gateway项目resources目录下新建META_INF目录,创建app.properties配置文件,这里有个在apollo中,properties格式的namespace在引入时只需要用名称就可以,但是其它格式的namespace在引入时需要按照「名称.格式」的规则来,否则无法正常加载

# apollo
apollo.bootstrap.enabled=true
apollo.meta=http://xxxx
apollo.accesskey.secret=xxxx
apollo.bootstrap.namespaces=application,base-yml.yml
app.id=xxxx

开启apollo注册。这里也需要指定value的值,不指定默认只加载application。

@EnableApolloConfig(value = {"application","base-yml.yml"})

启动项目,访问被代理服务的地址,成功响应表示完成。

实现动态路由配置

完成上述步骤,路由配置还是静态的,我们修改完apollo后,除非再次从apollo获取配置文件,这时候才能获取到最新的值,但是无法同步更新运行中的springboot项目中的上下文配置数据,所以我们需要监听apollo中配置的变化,然后手动更新springboot和getaway的配置数据。

核心代码贴出来了,这里在配置@ApolloConfigChangeListener(value = ROUTE_NAMESPACE)记着也得使用base-yml.yml的namspace,不然无法正常监听。

@Configuration
@Slf4j
public class RuteDynamicRefresher implements ApplicationContextAware, ApplicationEventPublisherAware {

    private static final String ROUTE_NAMESPACE = "base-yml.yml";

    private ApplicationContext applicationContext;

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    @ApolloConfigChangeListener(value = ROUTE_NAMESPACE)
    public void routeChange(ConfigChangeEvent changeEvent) {
        refreshGatewayProperties(changeEvent);
    }

    private void refreshGatewayProperties(ConfigChangeEvent changeEvent) {
        try {
            // 打印变更日志
            log.info("[RuteDynamicRefresher] update {}", JSONObject.toJSONString(changeEvent));

            // 更新 项目环境配置
            EnvironmentChangeEvent environmentChangeEvent = new EnvironmentChangeEvent(changeEvent.changedKeys());
            this.applicationContext.publishEvent(environmentChangeEvent);
            log.info("[RuteDynamicRefresher] publish environmentChangeEvent {}", environmentChangeEvent);

            // 更新路由配置
            RefreshRoutesEvent refreshRoutesEvent = new RefreshRoutesEvent(this);
            log.info("[RuteDynamicRefresher] publish refreshRoutesEvent {}", refreshRoutesEvent);
            this.publisher.publishEvent(refreshRoutesEvent);
        } catch (Exception e) {
            log.error("[refreshGatewayProperties] 更新失败!", e);
        }
    }
}

这时候可以尝试新增、删除、修改路由的配置,就可以正常使用了。

还有坑,上边我们只是配置了必要的路由配置,实际生产使用过程中,不同的服务之间接口响应的时长阈值也是不一样的,耗时长的可能需要好几秒(当然这种建议改成异步或轮训的方式),短的可能几十毫秒,那就需要为每个路由单独设置超时时间。

gateway支持配置全局超时时间,所有路由都生效。

# 全局的响应超时时间,网络链接后,后端服务多久不返回网关就报错
#response-timeout: PT5S
# 全局的TCP连接超时时间,多长时间获取不到超时就报错
#connect-timeout: 5000

还支持配置单个路由超时时间,单位时间毫秒。

spring:
  cloud:
    gateway:
      routes:
      - id: geektao
        uri: http://www.geektao.ai
        predicates:
        - Path=/geektao/**
        filters:
        - StripPrefix=1
        metadata:
          response-timeout: 2000
          connect-timeout: 2000

但是在apollo中配置之后进行测试,发现未生效。走的还是全局的超时时间5秒。这里我直接说原因了,大家下来可以自己debug看一看。

问题就出在org.springframework.cloud.gateway.filter.NettyRoutingFilter这个类的getResponseTimeout(Route route)方法。

private Duration getResponseTimeout(Route route) {
		Object responseTimeoutAttr = route.getMetadata().get(RESPONSE_TIMEOUT_ATTR);
		if (responseTimeoutAttr != null && responseTimeoutAttr instanceof Number) {
			Long routeResponseTimeout = ((Number) responseTimeoutAttr).longValue();
			if (routeResponseTimeout >= 0) {
				return Duration.ofMillis(routeResponseTimeout);
			}
			else {
				return null;
			}
		}
		return properties.getResponseTimeout();
	}

其实直接使用项目中的yml配置文件是没有问题的,因为yml配置文件中的value值是有类型的,Integer就是Integer,String就是String。所以在项目中配置的yml文件中读取到的response-timeout值就是Number类型,这个方法就可以正常返回值。

但是在apollo配置中,底层使用的是Properties进行配置数据的存储,value都被转成了String类型,那走到if (responseTimeoutAttr != null && responseTimeoutAttr instanceof Number)时必然就是false了,那就返回了全局的超时时间。

那既然知道了这里有问题,我搜了搜从apollo下手有些困难,所以我们从gateway下手,因为这个类不是jvm中原生类,那就意味着是通过应用类加载器加载的,那我们可以试着在自己项目中创建org.springframework.cloud.gateway.filter.NettyRoutingFilter类,然后重写getResponseTimeout方法,当然我这里直接暴力改了,默认这个配置只会配置为Long类型,大家实际用时最好加上异常处理。

private Duration getResponseTimeout(Route route) {
        Object responseTimeoutAttr = route.getMetadata().get(RESPONSE_TIMEOUT_ATTR);
        Long routeResponseTimeout;
        if (responseTimeoutAttr != null) {
            Long routeResponseTimeout = Long.parseLong((String)responseTimeoutAttr)

            if (routeResponseTimeout >= 0) {
                return Duration.ofMillis(routeResponseTimeout);
            } else {
                return null;
            }
        }
        return properties.getResponseTimeout();
    }

再次测试单路由的超时配置,问题解决。

关注公众号「极客涛」,同步更新文章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/488555.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第44期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

数据结构:Trie(前缀树/字典树)

文章目录 一、介绍Trie1.1、Trie的结点结构1.2、Trie的整体结构 二、Trie的操作2.1、Trie插入操作2.2、Trie查找操作2.3、Trie前缀匹配操作2.4、Trie删除操作 三、实战3.1、实现Trie&#xff08;前缀树&#xff09; 一、介绍Trie Trie 又称字典树、前缀树和单词查找树&#xff…

flask_restful渲染模版

渲染模版就是在 Flask_RESTful 的类视图中要返回 html 片段代码&#xff0c;或 者是整个html 文件代码。 如何需要浏览器渲染模板内容应该使用 api.representation 这个装饰器来定 义一个函数&#xff0c; 在这个函数中&#xff0c;应该对 html 代码进行一个封装&#xff…

vue2 key的作用和原理

我们在写v-for的时候都会绑定一个key值,这个key在vue中有什么作用呢,不写可以吗? 目标 1 key有什么作用 2 如何不写key会产生什么影响 3 key使用原理 key的作用 可以看vue2官网上给的解释,“给vue一个提示,以便跟踪每个节点的身份”,这样听着很模棱两可,到底是什么作用…

解决“Pycharm中Matplotlib图像不弹出独立的显示窗口”问题

matplotlib的绘图的结果默认显示在SciView窗口中, 而不是弹出独立的窗口, 这样看起来就不是很舒服&#xff0c;不习惯。 通过修改设置&#xff0c;改成独立弹出的窗口。 File—>Settings—>Tools—>Python Scientific—>Show plots in toolwindow 将√去掉即可

在for循环加判断条件当条件都满足时,同时显现的解决方法

一、代码示例 function fu(s) {str ;ste ;console.log(s);let Things s;for (let i 0; i < Things.length; i) {if (Things[i].pid kk) {console.log(Things[i].pid);ste <div class"commodity_nei"><div class"zxc_pic"><div cl…

数据库专题(oracle基础和进阶)

前言 本专题主要记录自己最近学的数据库&#xff0c;有兴趣一起补习的可以一起看看&#xff0c;有补充和不足之处请多多指出。希望专题可以给自己还有读者带去一点点提高。 数据库基本概念 本模块有参考&#xff1a;数据库基本概念-CSDN博客 数据库管理系统是一个由互相关联的…

Java两地经纬度通过高德api获取两地距离(公里)

代码如下&#xff1a; String startLongitude entity.getLONGITUDE(); // 起点&#xff08;当前位置&#xff09;经度String startLatitude entity.getLATITUDE(); // 起点纬度String endLongitude entity.getLO(); // 终点经度String endLatitude entity.getLA(); …

Midjourney AI绘图工具介绍及使用

介绍 Midjourney是一款目前被誉为最强的AI绘图工具。只要输入想到的文字&#xff0c;就能通过人工智能产出相对应的图片。 官网只是宣传和登录入口&#xff0c;提供个人主页、订阅管理等功能&#xff0c;Midjourney实际的绘画功能&#xff0c;是在另外一个叫discord的产品中实…

计算机基础(中断、IO)

操作系统 设备交互 CPU 与 IO 设备交互过程 CPU 通过设备控制器&#xff08;驱动&#xff1f;&#xff09;与计算机外设进行交互。可以将控制器想象成编程语言中的接口&#xff0c;然后不同地计算机外设的控制器去实现这个接口&#xff0c;CPU 只需要调用接口而无需关注具体地…

记录三菱:Works2-计数器

参数设置&#xff1a;D200-D511掉电保持&#xff0c;这个范围可以更改 加减计数器 第一种&#xff1a; 第二种&#xff1a; 第三种&#xff1a; 例如&#xff1a;完成下面的功能 可以在触摸屏上仿真测试一下

unity学习(70)——编译游戏发生错误2

1.全屏问题其实无所谓&#xff0c;windows用tab可以切出来的。 2.现在主要问题是服务器try了以后虽然不崩溃了&#xff0c;但不再显示2个实例对象了&#xff0c;unity和exe此时都只能看到一个实例对象 2.1把之前报错位置的try-catch先注释掉 2.2 unity中此时登录666账号&…

2015年认证杯SPSSPRO杯数学建模D题(第二阶段)城市公共自行车全过程文档及程序

2015年认证杯SPSSPRO杯数学建模 D题 城市公共自行车 原题再现&#xff1a; 城市交通问题直接影响市民的生活和工作。在地形平坦的城市&#xff0c;公共自行车出行系统是一种很好的辅助手段。一般来说&#xff0c;公共自行车出行系统由数据中心、驻车站点、驻车桩、自行车&…

【Linux】信号量与信号

目录 先导知识 信号量 信号 信号概念及产生信号的一般方式 进程递达、阻塞和捕捉 信号集操作函数 信号的捕捉 可重入函数 先导知识 信号量与信号没有任何关系&#xff0c;它们是两个完全不同的概念&#xff01; 操作系统的本质&#xff0c;就是一个死循环&#xff1b;…

Django日志(四)

一、Filters介绍 过滤器用于从logger传递给handler的哪些日志要做额外控制 默认情况下,满足日志级别的任何消息都将处理。只要级别匹配,任何日志消息都会被处理。不过,也可以通过添加 filter 来给日志处理的过程增加额外条件。例如,可以添加一个 filter 只允许某个特定来源…

【C++】模板与泛型编程

文章目录 1. 泛型编程2. 函数模板2.1 函数模板概念2.2 函数模板格式2.3 函数模板的原理2.4 函数模板的实例化2.5 模板参数的匹配原则 3. 类模板3.1 类模板的定义格式3.2 类模板的实例化 4. 非类型模板参数5. 模板的特化5.1 概念5.2 函数模板特化5.3 全特化5.4 偏特化5.5 类模板…

Android 系统应用 pk8签名文件转jks或keystore教程

一、介绍 签名文件对于我们在做应用开发中&#xff0c;经常遇到&#xff0c;且签名文件不仅仅是保护应用安全&#xff0c;还会涉及到应用与底层之间的数据共享和API文件等问题。 在Android中&#xff0c;签名文件同样也存在这个问题。但是android中又区分系统应用和普通应用。系…

汉明校验·简明教程

汉明校验 一、简介 汉明码是由 Richard Hanming 于 1950 年提出的&#xff0c;它具有一位纠错能力。 新增的汉明码校验位数应满足如下关系&#xff1a; 2 k ⩾ n k 1 2^{k}\geqslant nk1 2k⩾nk1&#xff0c;其中k为校验位位数&#xff0c;n位数据位数。 二、汉明码生成 确…

centos7 的redis的安装

文章目录 查看本机redis⾸先安装 scl 源, 再安装 redis 基本配置启动redis停止redis 查看本机redis ⾸先安装 scl 源, 再安装 redis 安装scl源 yum install centos-release-scl-rh安装redis5 yum install rh-redis5-redis安装成功 基本配置 修改etc/redis/redis.conf 文件…