书籍地址: Spring Cloud 微服务架构开发实战
6.4 路由配置规则
Zuul 提供了多种机制对请求路由进行配置,如下:
- 与 Eureka 服务器整合自动根据微服务的 ID 进行映射,这是默认机制。
- 结合微服务 ID 通过自定义方式进行路由映射。
- 添加全局路由映射。
- 通过自定义路由转换器,实现更灵活的路由映射。
6.4.1 服务路由默认规则
当构建 API服务网关时,如果有 Eureka 服务器时,Zuul 会自动为注册到 Eureka服务器上的每个服务都创建一个默认路由规则:访问路径的前缀为 serviceId 配置的服务名称。
就会行程如下格式的访问路径:
http://[zuul路由服务器地址]/[serviceId]/[具体服务的端点]
当微服务部署架构中包含了 Eureka 服务时,在增加或移除一个服务时无需对 Zuul 进行任何修改,Zuul 可以自动根据 Eureka 服务器中所注册的服务自动完成路由映射、负载均衡等处理
6.4.2 自定义微服务访问路径
通过在配置文件中配置一下内容
zuul:
routes:
userservice: /user/**
我们就可以通过/user/
来访问userserivice所提供的服务。也就是说我们之前的访问路径可以更改为
http://localhost:8280/user/users/2
注意此时我们依然是通过zuul服务器做的访问
那么此时,我们再通过userservice 即:http://localhost:8280/userservice/users/2
来进行访问,是否依然可以得到正确信息呢?
其实是完全OK的。
我们访问 http://localhost:8280/routes
可以看到所有的可用的路由映射
可以发现,user的微服务存在两个 路由映射,user 和 userservice,userservice作为默认的配置,不会因为我们的自定义配置而失效,如果想让默认配置失效,需要做下面的配置才可以。
6.4.3 忽略指定微服务
zuul.ignored-services
: 该属性可以指定在默认映射中所要忽略的微服务,指定后 Zuul 的路由服务将不再代理该路径下的访问。参数的值可以设置多个服务的 ID,如果需要忽略多个服务,那么服务 ID之间用逗号隔开即可。
例:
zuul:
ignored-services: userservice
此时我们通过上面配置的/user/ 进行访问
zuul:
routes:
userservice: /user/**
如果想让 Zuul 忽略所有服务的路由映射,并全部采用自定义方式,那么只需要将 zuul.ignored-services 的值设置为
*
即可。
6.4.4 设置路由前缀
Zuul 提供了 zuul.prefix
属性可为所有的路由映射增加统一前缀,如:
zuul.prefix=/api
此时我们再通过postMan访问routes端点,如下图:
默认情况下,Zuul代理会再转发到具体服务实例时自动剥离这个前缀。
如果需要在转发时带上该前缀,可以将 zuul.stripPrefix
属性的值设置为 false 来关闭这个默认行为。
zuul.stripPrefix
只会对zuul.prefix
的前缀起作用,而对于path指定的前缀不会起作用。
6.4.5 自定义路由规则
如果路由规则比较复杂,那么我们可以定义一个转换器,让 serviceId 和路由之间使用自定义的规则进行转换。
比如下面代码中通过一个正则表达式来自动匹配,将形如servicename-vx
的服务名称映射为/vx/servicename
的访问路径
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)","${version}/${name}";
);
}
以上服务 ID 为 users-v1 的服务,就会被映射到路由为/v1/users/的路径上。但是对于所定义的命名组必须包括 servicePattern 和 routePattern 两部分。
如果 servicePattern没有匹配一个 serviceId, 那么就会使用默认的。
上例中,一个服务实例的 ID 为 users 的服务,将会被映射到路由 /users/ 中,这个特性默认是关闭的,而且只适用于已经发现的服务。
6.5 Zuul 路由其他配置
Zuul在微服务架构中,扮演的是代理服务器的角色
对Zuul服务器来说,其核心就是如何处理与下游各个微服务之间的关系
6.5.1 HttpClient 设置
Zuul 的 HTTP 客户端支持 Apache Http、Ribbon 的 RestClient 和 OKHttpClient,默认使用 Apache Http客户端。可通过如下方式启用:
#启用Ribbon的RestClient
ribbon:
restclient:
enabled: true
#启用OKHttpClient
ribbon:
okhttp:
enabled: true
如果使用 OKHttpClient,需要确保项目中已经包含 com.squareup.okhttp3 包。
6.6 Zuul 容错与回退
6.6.1 实现 Zuul 的回退
Zuul 提供了一个 ZuulFallbackProvider 接口,通过实现该接口就可以为 Zuul 实现回退功能。
我们就在我们的例子中实现响应的服务降级处理功能。
/**userservice的回退实现
* @author:turnsole
* @date:2019/11/4,15:54
* @what I say:just look,do not be be
*/
public class UserServiceFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
//这里是route的名称,而不是服务的名称
//如果这里写成大写USERSERVICE,则无法起到回退的作用
return "userservice";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
//创建一个Fallback响应
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {}
//针对回退,构建了一个Fake用户信息
@Override
public InputStream getBody() throws IOException {
JSONObject mockUserJson = new JSONObject();
mockUserJson.put("id","-3");
mockUserJson.put("nickname","fakeUser");
mockUserJson.put("avatar","haha.jpg");
return new ByteArrayInputStream(mockUserJson.toJSONString().getBytes());
}
@Override
public HttpHeaders getHeaders() {
//需要将返回的格式设置为JSON
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
};
}
}
说明:
-
getRoutes()
方法返回的是要为那个微服务提供回退功能。这里需要注意其返回的值是 route的名称,而不是微服务的名称,所以不能写为USERSERVICE
,否则该回退将不起作用。 -
fallbackResponse()
方法返回ClientHttpResponse
对象,作为我们的回退响应。这里实现简单,仅仅是返回一个假的用户对象。
6.6.2 服务超时
当 Zuul 路由服务将客户端请求转发到具体服务时,Zuul会使用 HystrixCommand来包装这些执行过程,所以Hystrix的配置及服务容错机制,对于 Zuul 的请求都适用,也会影响到 API 服务网关的行为。
就是说,默认情况下,当 Zuul 请求执行执行一个服务时间超过 1秒时,则会中断执行并返回一个 500 的错误。
我们可以通过hystrix.command.default.execution.isolation.thread.timeoutInMillseconds
属性重新设置这个值。
#超时设置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
也可针对某一个服务独立设置,比如将用户设置为 5秒,而其它服务执行超时时间不变,可如下配置:
hystrix:
command:
userservice:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
另,Ribbon 在服务执行时也有一个超时时间配置,默认 5秒,就是说当一个服务执行时间超过了5秒,那么也会被中断并返回500的错误,我们需要对Ribbon的超时时间也做一个配置,如下:
超时时间应该:zuul > hystrix > ribbon (否则重试完成之前会被熔断)
hystrix:
command:
userservice:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
userservice:
ribbon:
ReadTimeout: 5000
这里关于cloud 的超时配置,放一个链接
springcloud中hystrix和ribbon的一些超时设置
6.7 Zuul过滤器
我们之前提起过, Zuul 其实包含了两个功能: 路由 和 过滤器。
路由的功能是负责对请求的处理过程进行干预,是实现请求校验。
实际上,Zuul 路由映射和请求转发这些功能都是由几个不同的过滤器组合完成的,所以说 Zuul 的过滤器才是核心所在。
对于 API 网关,开发者希望可以为服务请求的执行统一提供安全管控、日志、监控等处理,所以概念上类似于面向切面编程。
6.7.1 过滤器特性
当我们要实现一个 过滤器时,需要继承 ZuulFilter,我们先看一下其源码(只选我们需要的看):
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
//返回过滤器类型: pre|route|post等
public abstract String filterType();
//返回过滤器执行顺序号,不需要是连续的
public abstract int filterOrder();
}
/**
* IZuulFilter 过滤器接口定义
*/
public interface IZuulFilter {
/**
* 如果返回 true, 那么 run() 将被执行,如果返回false 则不执行 run() 方法
* return true/false
*/
boolean shouldFilter();
/**
* 假如 shouldFilter() 返回 true, 那么该方法将被执行
* @return 可返回任意对象,当前实现中忽略该返回值
*/
Object run() throws ZuulException;
}
从源码中可以看到 Zuul 过滤器的关键特性有以下几点:
- Type: 定义在请求执行过程中何时被执行。
- Execution Order: 当存在多个过滤器时,用来指示执行的顺序,值越小就越早执行;
- Criteria:: 执行的条件,即该过滤器何时被触发。
- Action: 该过滤器具体要执行的动作。
对应上面的 Zuul 过滤器特性和源码,在实现一个自定义过滤器需要实现的方法有以下几点:
- filterType() 方法返回过滤器的类型;
- filterOrder() 方法返回过滤器的执行顺序;
- shouldFilter() 方法判断是否需要执行该过滤器;
- run() 方法是该过滤器所要执行的具体过滤动作;
6.7.2 过滤器类型及生命周期
在 Zuul 中定义了 4 种标准的过滤器类型,这些过滤器类型对应于一个服务请求的典型生命周期。
- PRE 过滤器: **在请求被路由之前调用;**可用来实现身份验证、在集群中选择请求的微服务、记录调试信息等;
- ROUTING 过滤器: **在调用目标服务之前被调用;**通常可以用来处理一些动态路由。比如:A/B测试,在这里可以随机让部分用户访问指定版本的服务,然后通过用户体验数据的采集和分析来决定那个版本更好。另外,还可以结合 PRE 过滤器实现不同版本服务之间的处理。
- POST 过滤器: 在目标微服务执行以后,所返回的结果在送回给客户端时被调用; 我们可以利用该过滤器实现为响应添加标准的 **HTTP Header、**数据采集、统计信息和指标、审计日志处理等。
- ERROR 过滤器: 该过滤器在处理请求过程中发生错误时被调用; 可以使用该过滤器实现对异常、错误的统一处理,从而为客户端调用显示更加友好的界面。
- 当然我们还可以自定义过滤器类型
6.7.3 自定义 Zuul 过滤器
6.7.4 禁用 Zuul 过滤器
在配置文件中只需要按照如下格式就可配置所需要禁用的过滤器:
zuul.[filter-name].[filter-type].disable=true
比如,禁用 testFilter 中的 PRE过滤器:
zuul.testFilter.pre.disable=true
6.8 @EnableZuulServer
和@EnableZuulProxy
比较
简单来说,@EnableZuulProxy
注解包含@EnableZuulServer
的所有功能,并且还加入了@EnableCircuitBreaker
和@EnableDiscoveryClient
。
当我们需要运行一个没有代理功能的 Zuul 服务,或者需要有选择地 开/关部分代理功能时,需要使用@EnableZuulServer
代替@EnableZuulProxy
。这时开发者添加的任何 ZuulFilter 类型实体类都会被自动加载,这和使用@EnableZuulProxy
一样的。
但是@EnableZuulProxy
不会自动加载任何处理过滤器。