返回介绍

路由详解

发布于 2024-08-18 11:12:34 字数 16225 浏览 0 评论 0 收藏 0

在“快速入门”一节的请求路由示例中,我们对Spring Cloud Zuul中的两类路由功能已经做了简单的使用介绍。在本节中,我们将进一步详细介绍关于Spring Cloud Zuul的路由功能,以帮助读者更好地理解和使用它。

传统路由配置

所谓的传统路由配置方式就是在不依赖于服务发现机制的情况下,通过在配置文件中具体指定每个路由表达式与服务实例的映射关系来实现API网关对外部请求的路由。

没有Eureka等服务治理框架的帮助,我们需要根据服务实例的数量采用不同方式的配置来实现路由规则。

- 单实例配置: 通过zuul.routes.<route>.path与zuul.routes.<route>.url参数对的方式进行配置,比如:

zuul.routes.user-service.path=/user-service/**

zuul.routes.user-service.url=http://localhost:8080/

该配置实现了对符合/user-service/**规则的请求路径转发到http://localhost:8080/地址的路由规则。比如,当有一个请求http://localhost:5555/user-service/hello 被发送到 API 网关上,由于/user-service/hello 能够被上述配置的 path 规则匹配,所以 API 网关会转发请求到http://localhost:8080/hello地址。

- 多实例配置: 通过zuul.routes.<route>.path与zuul.routes.<route>.serviceId参数对的方式进行配置,比如:

zuul.routes.user-service.path=/user-service/**

zuul.routes.user-service.serviceId=user-service

ribbon.eureka.enabled=false

user-service.ribbon.listOfServers=http://localhost:8080/,http://localhost:8081/

该配置实现了对符合/user-service/** 规则的请求路径转发到http://localhost:8080/和http://localhost:8081/两个实例地址的路由规则。它的配置方式与服务路由的配置方式一样,都采用了 zuul.routes.<route>.path与zuul.routes.<route>.serviceId参数对的映射方式,只是这里的serviceId是由用户手工命名的服务名称,配合ribbon.listOfServers 参数实现服务与实例的维护。由于存在多个实例,API网关在进行路由转发时需要实现负载均衡策略,于是这里还需要 Spring Cloud Ribbon的配合。由于在Spring Cloud Zuul中自带了对Ribbon的依赖,所以我们只需做一些配置即可,比如上面示例中关于Ribbon的各个配置,它们的具体作用如下所示。

- ribbon.eureka.enabled:由于 zuul.routes.<route>.serviceId 指定的是服务名称,默认情况下 Ribbon 会根据服务发现机制来获取配置服务名对应的实例清单。但是,该示例并没有整合类似 Eureka 之类的服务治理框架,所以需要将该参数设置为false,否则配置的serviceId获取不到对应实例的清单。

- user-service.ribbon.listOfServers:该参数内容与 zuul.routes.<route>.serviceId 的配置相对应,开头的 user-service 对应了serviceId的值,这两个参数的配置相当于在该应用内部手工维护了服务与实例的对应关系。

不论是单实例还是多实例的配置方式,我们都需要为每一对映射关系指定一个名称,也就是上面配置中的<route>,每一个<route>对应了一条路由规则。每条路由规则都需要通过 path 属性来定义一个用来匹配客户端请求的路径表达式,并通过 url 或serviceId属性来指定请求表达式映射具体实例地址或服务名。

服务路由配置

对于服务路由,我们在快速入门示例中已经有过基础的介绍和体验,Spring Cloud Zuul通过与Spring Cloud Eureka的整合,实现了对服务实例的自动化维护,所以在使用服务路由配置的时候,我们不需要向传统路由配置方式那样为serviceId指定具体的服务实例地址,只需要通过zuul.routes.<route>.path与zuul.routes.<route>.serviceId参数对的方式进行配置即可。

比如下面的示例,它实现了对符合/user-service/**规则的请求路径转发到名为user-service的服务实例上去的路由规则。其中<route>可以指定为任意的路由名称。

zuul.routes.user-service.path=/user-service/**

zuul.routes.user-service.serviceId=user-service

对于面向服务的路由配置,除了使用path与serviceId映射的配置方式之外,还有一种更简洁的配置方式:zuul.routes.<serviceId>=<path>,其中<serviceId>用来指定路由的具体服务名,<path>用来配置匹配的请求表达式。比如下面的例子,它的路由规则等价于上面通过path与serviceId组合使用的配置方式。

zuul.routes.user-service=/user-service/**

传统路由的映射方式比较直观且容易理解,API网关直接根据请求的URL路径找到最匹配的path表达式,直接转发给该表达式对应的url或对应serviceId下配置的实例地址,以实现外部请求的路由。那么当采用path与serviceId以服务路由的方式实现时,在没有配置任何实例地址的情况下,外部请求经过API网关的时候,它是如何被解析并转发到服务具体实例的呢?

在本章一开始,我们就提到了 Zuul 巧妙地整合了 Eureka 来实现面向服务的路由。实际上,我们可以直接将API网关也看作Eureka服务治理下的一个普通微服务应用。它除了会将自己注册到Eureka服务注册中心上之外,也会从注册中心获取所有服务以及它们的实例清单。所以,在Eureka的帮助下,API网关服务本身就已经维护了系统中所有serviceId与实例地址的映射关系。当有外部请求到达API网关的时候,根据请求的URL路径找到最佳匹配的path规则,API网关就可以知道要将该请求路由到哪个具体的serviceId上去。由于在API网关中已经知道serviceId对应服务实例的地址清单,那么只需要通过Ribbon的负载均衡策略,直接在这些清单中选择一个具体的实例进行转发就能完成路由工作了。

服务路由的默认规则

虽然通过Eureka与Zuul的整合已经为我们省去了维护服务实例清单的大量配置工作,剩下只需要再维护请求路径的匹配表达式与服务名的映射关系即可。但是在实际的运用过程中会发现,大部分的路由配置规则几乎都会采用服务名作为外部请求的前缀,比如下面的例子,其中 path 路径的前缀使用了 user-service,而对应的服务名称也是user-service。

zuul.routes.user-service.path=/user-service/**

zuul.routes.user-service.serviceId=user-service

对于这样具有规则性的配置内容,我们总是希望可以自动化地完成。非常庆幸,Zuul默认实现了这样的贴心功能,当我们为Spring Cloud Zuul构建的API网关服务引入Spring Cloud Eureka之后,它为Eureka中的每个服务都自动创建一个默认路由规则,这些默认规则的path会使用serviceId配置的服务名作为请求前缀,就如上面的例子那样。

由于默认情况下所有 Eureka 上的服务都会被 Zuul 自动地创建映射关系来进行路由,这会使得一些我们不希望对外开放的服务也可能被外部访问到。这个时候,我们可以使用zuul.ignored-services 参数来设置一个服务名匹配表达式来定义不自动创建路由的规则。Zuul在自动创建服务路由的时候会根据该表达式来进行判断,如果服务名匹配表达式,那么 Zuul 将跳过该服务,不为其创建路由规则。比如,设置为zuul.ignored-services=*的时候,Zuul 将对所有的服务都不自动创建路由规则。在这种情况下,我们就要在配置文件中逐个为需要路由的服务添加映射规则(可以使用path与 serviceId 组合的配置方式,也可使用更简洁的 zuul.routes.<serviceId>=<path>配置方式),只有在配置文件中出现的映射规则会被创建路由,而从Eureka中获取的其他服务,Zuul将不会再为它们创建路由规则。

自定义路由映射规则

我们在构建微服务系统进行业务逻辑开发的时候,为了兼容外部不同版本的客户端程序(尽量不强迫用户升级客户端),一般都会采用开闭原则来进行设计与开发。这使得系统在迭代过程中,有时候会需要我们为一组互相配合的微服务定义一个版本标识来方便管理它们的版本关系,根据这个标识我们可以很容易地知道这些服务需要一起启动并配合使用。比如可以采用类似这样的命名:userservice-v1、userservice-v2、orderservice-v1、orderservice-v2。默认情况下,Zuul自动为服务创建的路由表达式会采用服务名作为前缀,比如针对上面的userservice-v1和userservice-v2,它会产生/userservice-v1和/userservice-v2两个路径表达式来映射,但是这样生成出来的表达式规则较为单一,不利于通过路径规则来进行管理。通常的做法是为这些不同版本的微服务应用生成以版本代号作为路由前缀定义的路由规则,比如/v1/userservice/。这时候,通过这样具有版本号前缀的URL路径,我们就可以很容易地通过路径表达式来归类和管理这些具有版本信息的微服务了。

针对上面所述的需求,如果我们的各个微服务应用都遵循了类似 userservice-v1这样的命名规则,通过-分隔的规范来定义服务名和服务版本标识的话,那么,我们可以使用Zuul中自定义服务与路由映射关系的功能,来实现为符合上述规则的微服务自动化地创建类似/v1/userservice/**的路由匹配规则。实现步骤非常简单,只需在 API 网关程序中,增加如下Bean的创建即可:

@Bean

public PatternServiceRouteMapper serviceRouteMapper(){

return new PatternServiceRouteMapper(

"(? <name>^.+)-(? <version>v.+$)",

"${version}/${name}");

}

PatternServiceRouteMapper对象可以让开发者通过正则表达式来自定义服务与路由映射的生成关系。其中构造函数的第一个参数是用来匹配服务名称是否符合该自定义规则的正则表达式,而第二个参数则是定义根据服务名中定义的内容转换出的路径表达式规则。当开发者在API网关中定义了PatternServiceRouteMapper实现之后,只要符合第一个参数定义规则的服务名,都会优先使用该实现构建出的路径表达式,如果没有匹配上的服务则还是会使用默认的路由映射规则,即采用完整服务名作为前缀的路径表达式。

路径匹配

不论是使用传统路由的配置方式还是服务路由的配置方式,我们都需要为每个路由规则定义匹配表达式,也就是上面所说的path参数。在Zuul中,路由匹配的路径表达式采用了Ant风格定义。

Ant风格的路径表达式使用起来非常简单,它一共有下面这三种通配符。

我们可以通过下表中的示例来进一步理解这三个通配符的含义并进行参考使用。

另外,当我们使用通配符的时候,经常会碰到这样的问题:一个URL路径可能会被多个不同路由的表达式匹配上。比如,有这样一个场景,我们在系统建设的一开始实现了user-service服务,并且配置了如下路由规则:

zuul.routes.user-service.path=/user-service/**

zuul.routes.user-service.serviceId=user-service

但是随着版本的迭代,我们对 user-service 服务做了一些功能拆分,将原属于user-service 服务的某些功能拆分到了另外一个全新的服务 user-service-ext 中去,而这些拆分的外部调用URL路径希望能够符合规则/user-service/ext/**,这个时候我们需要就在配置文件中增加一个路由规则,完整配置如下:

zuul.routes.user-service.path=/user-service/**

zuul.routes.user-service.serviceId=user-service

zuul.routes.user-service-ext.path=/user-service/ext/**

zuul.routes.user-service-ext.serviceId=user-service-ext

此时,调用 user-service-ext 服务的 URL 路径实际上会同时被/userservice/**和/user-service/ext/**两个表达式所匹配。在逻辑上,API 网关服务需要优先选择/user-service/ext/**路由,然后再匹配/user-service/**路由才能实现上述需求。但是如果使用上面的配置方式,实际上是无法保证这样的路由优先顺序的。

从下面的路由匹配算法中,我们可以看到它在使用路由规则匹配请求路径的时候是通过线性遍历的方式,在请求路径获取到第一个匹配的路由规则之后就返回并结束匹配过程。所以当存在多个匹配的路由规则时,匹配结果完全取决于路由规则的保存顺序。

@Override

public Route getMatchingRoute(final String path){

...

ZuulRoute route=null;

if(! matchesIgnoredPatterns(adjustedPath)){

for(Entry<String,ZuulRoute> entry : this.routes.get().entrySet()){

String pattern=entry.getKey();

log.debug("Matching pattern:"+pattern);

if(this.pathMatcher.match(pattern,adjustedPath)){

route=entry.getValue();

break;

}

}

}

log.debug("route matched="+route);

return getRoute(route,adjustedPath);

}

下面所示的代码是基础的路由规则加载算法,我们可以看到这些路由规则是通过LinkedHashMap保存的,也就是说,路由规则的保存是有序的,而内容的加载是通过遍历配置文件中路由规则依次加入的,所以导致问题的根本原因是对配置文件中内容的读取。

protected Map<String,ZuulRoute> locateRoutes(){

LinkedHashMap<String,ZuulRoute> routesMap=new LinkedHashMap<String,ZuulRoute>();

for(ZuulRoute route : this.properties.getRoutes().values()){

routesMap.put(route.getPath(),route);

}

return routesMap;

}

由于properties的配置内容无法保证有序,所以当出现这种情况的时候,为了保证路由的优先顺序,我们需要使用YAML文件来配置,以实现有序的路由规则,比如使用下面的定义:

zuul:

routes:

user-service-ext:

path: /user-service/ext/**

serviceId: user-service-ext

user-service:

path: /user-service/**

serviceId: user-service

忽略表达式

通过path参数定义的Ant表达式已经能够完成API网关上的路由规则配置功能,但是为了更细粒度和更为灵活地配置路由规则,Zuul 还提供了一个忽略表达式参数zuul.ignored-patterns。该参数可以用来设置不希望被API网关进行路由的URL表达式。

比如,以快速入门中的示例为基础,如果不希望/hello 接口被路由,那么我们可以这样设置:

zuul.ignored-patterns=/**/hello/**

zuul.routes.api-a.path=/api-a/**

zuul.routes.api-a.serviceId=hello-service

然后,可以尝试通过网关来访问 hello-service 的/hello 接口http://localhost:5555/api-a/hello。虽然该访问路径完全符合 path 参数定义的/api-a/**规则,但是由于该路径符合zuul.ignored-patterns参数定义的规则,所以不会被正确路由。同时,我们在控制台或日志中还能看到没有匹配路由的输出信息:

o.s.c.n.z.f.pre.PreDecorationFilter : No route found for uri: /api-a/hello

另外,该参数在使用时还需要注意它的范围并不是对某个路由,而是对所有路由。所以在设置的时候需要全面考虑URL规则,防止忽略了不该被忽略的URL路径。

路由前缀

为了方便全局地为路由规则增加前缀信息,Zuul提供了zuul.prefix参数来进行设置。比如,希望为网关上的路由规则都增加/api前缀,那么我们可以在配置文件中增加配置:zuul.prefix=/api。另外,对于代理前缀会默认从路径中移除,我们可以通过设置zuul.stripPrefix=false来关闭该移除代理前缀的动作,也可以通过zuul.routes.<route>.strip-prefix=true来对指定路由关闭移除代理前缀的动作。

注意,在使用zuul.prefix参数的时候,目前版本的实现还存在一些Bug,所以请谨慎使用,或是避开会引发Bug的配置规则。具体会引发Bug的规则如下:

假设我们设置zuul.prefix=/api,当路由规则的path表达式以/api开头的时候,将会产生错误的映射关系。可以进行下面的配置实验来验证这个问题:

zuul.routes.api-a.path=/api/a/**

zuul.routes.api-a.serviceId=hello-service

zuul.routes.api-b.path=/api-b/**

zuul.routes.api-b.serviceId=hello-service

zuul.routes.api-c.path=/ccc/**

zuul.routes.api-c.serviceId=hello-service

这里配置了三个路由关系:/api/a/**、/api-b/**、/ccc/**,这三个路径规则都将被路由到hello-service服务上去。当我们没有设置zuul.prefix=/api的时候,一切运作正常。但是在增加了zuul.prefix=/api配置之后,会得到下面这样的路由关系:

o.s.c.n.zuul.web.ZuulHandlerMapping   : Mapped URL path[/api/api/a/a/**]onto

handler of type[class org.springframework.cloud.netflix.zuul.web.ZuulController]

o.s.c.n.zuul.web.ZuulHandlerMapping   : Mapped URL path[/api/api-b-b/**]onto

handler of type[class org.springframework.cloud.netflix.zuul.web.ZuulController]

o.s.c.n.zuul.web.ZuulHandlerMapping   : Mapped URL path[/api/ccc/**]onto

handler of type[class org.springframework.cloud.netflix.zuul.web.ZuulController]

从日志信息中我们可以看到,以/api开头的路由规则解析除了两个看似就有问题的映射 URL,我们可以通过该输出的 URL 来访问,实际是路由不到正确的服务接口的,只有非/api开头的路由规则/ccc/**能够正常路由。

上述实验基于Brixton.SR7和Camden.SR3测试均存在问题,所以在使用该版本或以下版本时候,务必避免让路由表达式的起始字符串与zuul.prefix参数相同。

本地跳转

在Zuul实现的API网关路由功能中,还支持forward形式的服务端跳转配置。实现方式非常简单,只需通过使用path与url的配置方式就能完成,通过url中使用forward来指定需要跳转的服务器资源路径。

下面的配置实现了两个路由规则,api-a路由实现了将符合/api-a/**规则的请求转发到http://localhost:8001/;而api-b路由则使用了本地跳转,它实现了将符合/api-b/**规则的请求转发到API网关中以/local为前缀的请求上,由API网关进行本地处理。比如,当API网关接收到请求/api-b/hello,它符合api-b的路由规则,所以该请求会被API网关转发到网关的/local/hello请求上进行本地处理。

zuul.routes.api-a.path=/api-a/**

zuul.routes.api-a.url=http://localhost:8001/

zuul.routes.api-b.path=/api-b/**

zuul.routes.api-b.url=forward:/local

这里要注意,由于需要在API网关上实现本地跳转,所以相应的我们也需要为本地跳转实现对应的请求接口。按照上面的例子,在API网关上还需要增加一个/local/hello的接口实现才能让api-b路由规则生效,比如下面的实现。否则Zuul在进行forward转发的时候会因为找不到该请求而返回404错误。

@RestController

public class HelloController {

@RequestMapping("/local/hello")

public String hello(){

return "Hello World Local";

}

}

Cookie与头信息

默认情况下,Spring Cloud Zuul在请求路由时,会过滤掉HTTP请求头信息中的一些敏感信息,防止它们被传递到下游的外部服务器。默认的敏感头信息通过zuul.sensitiveHeaders参数定义,包括Cookie、Set-Cookie、Authorization三个属性。所以,我们在开发Web项目时常用的Cookie在Spring Cloud Zuul网关中默认是不会传递的,这就会引发一个常见的问题:如果我们要将使用了 Spring Security、Shiro等安全框架构建的Web应用通过Spring Cloud Zuul构建的网关来进行路由时,由于Cookie信息无法传递,我们的Web应用将无法实现登录和鉴权。为了解决这个问题,配置的方法有很多。

- 通过设置全局参数为空来覆盖默认值,具体如下:

zuul.sensitiveHeaders=

这种方法并不推荐,虽然可以实现Cookie的传递,但是破坏了默认设置的用意。在微服务架构的API网关之内,对于无状态的RESTful API请求肯定是要远多于这些Web类应用请求的,甚至还有一些架构设计会将Web类应用和App客户端一样都归为API网关之外的客户端应用。

- 通过指定路由的参数来配置,方法有下面两种。

# 方法一:对指定路由开启自定义敏感头

zuul.routes.<router>.customSensitiveHeaders=true

# 方法二:将指定路由的敏感头设置为空

zuul.routes.<router>.sensitiveHeaders=

比较推荐使用这两种方法,仅对指定的Web应用开启对敏感信息的传递,影响范围小,不至于引起其他服务的信息泄露问题。

重定向问题

在解决了 Cookie 问题之后,我们已经能够通过网关来访问并登录到我们的 Web 应用了。但是这个时候又会发现另外一个问题:虽然可以通过网关访问登录页面并发起登录请求,但是登录成功之后,我们跳转到的页面URL却是具体Web应用实例的地址,而不是通过网关的路由地址。这个问题非常严重,因为使用API网关的一个重要原因就是要将网关作为统一入口,从而不暴露所有的内部服务细节。那么是什么原因导致了这个问题呢?

通过浏览器开发工具查看登录以及登录之后的请求详情,可以发现,引起问题的大致原因是由于Spring Security或Shiro在登录完成之后,通过重定向的方式跳转到登录后的页面,此时登录后的请求结果状态码为302,请求响应头信息中的 Location 指向了具体的服务实例地址,而请求头信息中的Host也指向了具体的服务实例IP地址和端口。所以,该问题的根本原因在于Spring Cloud Zuul在路由请求时,并没有将最初的Host信息设置正确。那么如何解决这个问题呢?

针对这个问题,目前在spring-cloud-netflix-core-1.2.x版本的Zuul中增加了一个参数配置,能够使得网关在进行路由转发前为请求设置Host头信息,以标识最初的服务端请求地址。具体配置方式如下:

zuul.addHostHeader=true

由于 Spring Cloud 的版本依赖原因,目前的 Brixton 版本采用了 spring-cloud-netflix-core-1.1.x,只有 Camden 版本采用了 spring-cloud-netflix-core-1.2.x。所以重定向的问题如果要在Zuul中解决,最简单的方法就使用Camden版本。如果要在Brixton版本中解决,可以参考Camden的PreDecorationFilter的实现扩展过滤器链来增加Host信息。

Hystrix和Ribbon支持

在“快速入门”一节中介绍 spring-cloud-starter-zuul 依赖时,我们提到了它自身就包含了对 spring-cloud-starter-hystrix 和 spring-cloud-starterribbon模块的依赖,所以Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。但是需要注意,当使用path与url的映射关系来配置路由规则的时候,对于路由转发的请求不会采用 HystrixCommand 来包装,所以这类路由请求没有线程隔离和断路器的保护,并且也不会有负载均衡的能力。因此,我们在使用Zuul的时候尽量使用path和serviceId的组合来进行配置,这样不仅可以保证API网关的健壮和稳定,也能用到Ribbon的客户端负载均衡功能。

我们在使用Zuul搭建API网关的时候,可以通过Hystrix和Ribbon的参数来调整路由请求的各种超时时间等配置,比如下面这些参数的设置。

- hystrix.command.default.execution.isolation.thread.timeoutIn Milliseconds:该参数可以用来设置 API 网关中路由转发请求的HystrixCommand 执行超时时间,单位为毫秒。当路由转发请求的命令执行时间超过该配置值之后,Hystrix会将该执行命令标记为TIMEOUT并抛出异常,Zuul会对该异常进行处理并返回如下JSON信息给外部调用方。

{

"timestamp": 1481350975323,

"status": 500,

"error": "Internal Server Error",

"exception": "com.netflix.zuul.exception.ZuulException",

"message": "TIMEOUT"

}

- ribbon.ConnectTimeout:该参数用来设置路由转发请求的时候,创建请求连接的超时时间。当ribbon.ConnectTimeout的配置值小于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 配置值的时候,若出现路由请求出现连接超时,会自动进行重试路由请求,如果重试依然失败,Zuul会返回如下JSON信息给外部调用方。

{

"timestamp": 1481352582852,

"status": 500,

"error": "Internal Server Error",

"exception": "com.netflix.zuul.exception.ZuulException",

"message": "NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED"

}

如果ribbon.ConnectTimeout的配置值大于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds配置值的时候,当出现路由请求连接超时时,由于此时对于路由转发的请求命令已经超时,所以不会进行重试路由请求,而是直接按请求命令超时处理,返回TIMEOUT的错误信息。

- ribbon.ReadTimeout:该参数用来设置路由转发请求的超时时间。它的处理与ribbon.ConnectTimeout 类似,只是它的超时是对请求连接建立之后的处理时间。当 ribbon.ReadTimeout 的配置值小于 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds配置值的时候,若路由请求的处理时间超过该配置值且依赖服务的请求还未响应的时候,会自动进行重试路由请求。如果重试后依然没有获得请求响应,Zuul会返回NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED错误。如果ribbon.ReadTimeout的配置值大于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds配置值,若路由请求的处理时间超过该配置值且依赖服务的请求还未响应时,不会进行重试路由请求,而是直接按请求命令超时处理,返回TIMEOUT的错误信息。

根据上面的介绍我们可以知道,在使用Zuul的服务路由时,如果路由转发请求发生超时(连接超时或处理超时),只要超时时间的设置小于Hystrix的命令超时时间,那么它就会自动发起重试。但是在有些情况下,我们可能需要关闭该重试机制,那么可以通过下面的两个参数来进行设置:

zuul.retryable=false

zuul.routes.<route>.retryable=false

其中,zuul.retryable 用来全局关闭重试机制,而 zuul.routes.<route>.retryable=false则是指定路由关闭重试机制。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文