由于Zuul的内容较多所以单独列出一篇来讲。全是干货,如果学到东西的,动动小手给点个推荐^_^ 谢谢!
1. Router and Filter: Zuul(路由和过滤:Zuul)
路由是微服务架构不缺少的一部分。例如“/”可能映射到web服务,“/api/users”映射到用户管理服务,而“/api/shop”映射到采购服务。Zuul是Netflix中的一个基于JVM的路由器,也是一个服务端负载均衡器。
zuul有下列用途:
Authentication(权限验证)
Insights
Stress Testing(压力测试)
Canary Testing(金丝雀测试)
Dynamic Routing(动态路由)
Service Migration(服务迁移)
Load Shedding(负载削减)
Security(安全机制)
Static Response handling(静态响应处理)
Active/Active traffic management(流量管理)
注意:
1)zuul.max.host.connections已经被zuul.host.maxTotalConnections(默认值200)和zuul.host.maxPerRouteConnections(默认值20)代替了。
2)Hystrix对所有理由的默认隔离模式是SEMAPHORE,可以通过zuul.ribbonIsolationStrategy改为THREAD。
1.1 How to Include Zuul(依赖)
复制代码
1
2 org.springframework.cloud
3 spring-cloud-starter-netflix-zuul
4
复制代码
1.2 Embedded Zuul Reverse Proxy(反向代理)
Spring Cloud创建了一个内置Zuul代理来简化开发,比如有一个UI应用想要使用代理调用后端的一个或者多个服务。这可以避免为后台每个服务都配置CORS和权限系统。
在spring boot的入口类上使用@EnableZuulProxy注解来开启代理。代理使用Ribbon通过服务发现来定位后端服务实例。并且所有请求在 hystrix command中执行。所以当断路器打开时,代理将不会重试连接后端服务。
注意:Zuul starter不包含服务发现客户端,所以想要使用服务发现功能,需要提供一个服务发现客户端(比如Eureka)。
为了防止自动添加服务,可以设置zuul.ignored-services参数来避免。如果一个服务匹配到一个忽略表达式,并且又在路由映射中明确指定了,那么它就不会被忽略。例如(application.yml):
复制代码
1 zuul:
2 ignoredServices: '*'
3 routes:
4 users: /myusers/**
复制代码
你可以单独指定路径和service ID,例如(application.yml):
复制代码
1 zuul:
2 routes:
3 users:
4 path: /myusers/**
5 serviceId: users_service
复制代码
其中path是一个ant风格的表达式,所以/myusers/*仅仅匹配一层目录,而/myusers/**可以匹配任意多层级目录。
其中后端服务的位置既可以使用serviceId也可以使用url(物理位置)指定。如下(application.yml):
复制代码
1 zuul:
2 routes:
3 users:
4 path: /myusers/**
5 url: http://example.com/users_service
复制代码
这些简单的url路由不会作为HystrixCommand执行,也不会使用Ribbon负载均衡。如果要使用的话,可以指定一个服务器列表的serviceId,如下:
application.yml.
复制代码
1 zuul:
2 routes:
3 echo:
4 path: /myusers/**
5 serviceId: myusers-service
6 stripPrefix: true
7
8 hystrix:
9 command:
10 myusers-service:
11 execution:
12 isolation:
13 thread:
14 timeoutInMilliseconds: ...
15
16 myusers-service:
17 ribbon:
18 NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
19 listOfServers: http://example1.com,http://example2.com
20 ConnectTimeout: 1000
21 ReadTimeout: 3000
22 MaxTotalHttpConnections: 500
23 MaxConnectionsPerHost: 100
复制代码
另一个方法是指定一个服务路由并且为serviceId配置Ribbon客户端(这么做需要在Ribbon中禁用Eureka),如下:
application.yml.
复制代码
1 zuul:
2 routes:
3 users:
4 path: /myusers/**
5 serviceId: users
6
7 ribbon:
8 eureka:
9 enabled: false
10
11 users:
12 ribbon:
13 listOfServers: example.com,google.com
复制代码
可以使用正则表达式来配置路由规则。如下:
ApplicationConfiguration.java.
复制代码
1 @Bean
2 public PatternServiceRouteMapper serviceRouteMapper() {
3 return new PatternServiceRouteMapper(
4 "(?
^.+)-(?v.+$)",
5 "${version}/${name}");
6 }
复制代码
在上面的例子中如果serviceId为myusers-v1那么它将被映射到/v1/myusers/**。如果有一个serviceId不匹配,那么将会使用默认规则。例如,在上面的例子中一个serviceId为myusers的服务将会映射到"/myusers/**"。
给所有映射添加前缀可以使用zuul.prefix。默认情况下,请求被转发前将会去除掉其中的代理前缀(可以使用zuul.stripPrefix=false来改变默认行为)。也可以在单独的一个路由中关闭去除服务指定前缀的行为。如下:
application.yml.
复制代码
1 zuul:
2 routes:
3 users:
4 path: /myusers/**
5 stripPrefix: false
复制代码
注意:zuul.stripPrefix只是针对zuul.prefix,对路由中的路径不起作用。
在上面的例子中/myusers/101请求将会转发为/myusers/101到users服务中。
zuul.routes是绑定在ZuulProperties对象上。在这个类里可以看到retryable属性,将它设置为true,可以在请求失败时使用Ribbon客户端重试。
默认情况下,X-Forwarded-Host将会添加到转发的请求头上,可以设置zuul.addProxyHeaders = false来关闭它。默认情况下,路径中的前缀将会被跳过,转发的请求头中将会有一个X-Forwarded-Prefix头(例如上面例子中的/myusers)。
注意:如果你希望你配置的路由是有序的话,那你应该使用yml配置文件,因为使用properties文件时会丢失排序。
1.3 Zuul Http Client(Zuul HTTP客户端)
Zuul的默认HTTP客户端是Apache HTTP客户端而不是已经过时的Ribbon的RestClient。如果要使用RestClient或者okhttp3.OkHttpClient,可以设置ribbon.restclient.enabled=true 或者 ribbon.okhttp.enabled=true。
如果你想自定义Apache HTTP client 或者 OK HTTP client,那么需要提供一个ClosableHttpClient 或者 OkHttpClient类型的bean。
1.4 Cookies and Sensitive Headers(cookies和敏感头部)
可以在路由配置中指定要忽略的头部,如下:
application.yml.
复制代码
1 zuul:
2 routes:
3 users:
4 path: /myusers/**
5 sensitiveHeaders: Cookie,Set-Cookie,Authorization
6 url: https://downstream
复制代码
注意:上面的例子是sensitiveHeaders的默认值,这是在Spring Cloud Netflix 1.1版本新增的功能。(在1.0中,不能设置头部并且所有cookie双向流动)。
sensitiveHeaders是一个黑名单,默认不为空。因此要让Zuul发送所有头部的话,需要明确指定sensitiveHeaders为空。如下:
application.yml.
复制代码
1 zuul:
2 routes:
3 users:
4 path: /myusers/**
5 sensitiveHeaders:
6 url: https://downstream
复制代码
你也可以通过zuul.sensitiveHeader进行全局设置。如果在一个路由上设置sensitiveHeaders的话将会覆盖全局设置。
1.5 Ignored Headers(忽略头部)
除了路由敏感头部以外,你还可以设置zuul.ignoredHeaders成那些在与下游服务交互时应该剔除的值。默认,Spring Security不在classpath上时,这些值是空的。否则他们被Spring Security初始化为一些常见的“安全”头部。
这种情况下,下游的服务也可能设置这些头部,但是我们想要的是代理中的值。如果Spring Security在classpath上时,为了不剔除这些安全头部,可以设置zuul.ignoreSecurityHeaders=false。
1.6 Management Endpoints(管理端点)
如果你同时使用@EnableZuulProxy 和Spring Boot Actuator,那么将会开启两个额外的端点:
Routes
Filters
1.6.1 Routes Endpoint(路由端点)
get请求/routes:
GET /routes.
复制代码
1 {
2 /stores/**: "http://localhost:8081"
3 }
复制代码
如果想要得到路由的详细信息,在请求上添加?format=details查询字段。
GET /routes/details
复制代码
1 {
2 "/stores/**": {
3 "id": "stores",
4 "fullPath": "/stores/**",
5 "location": "http://localhost:8081",
6 "path": "/**",
7 "prefix": "/stores",
8 "retryable": false,
9 "customSensitiveHeaders": false,
10 "prefixStripped": true
11 }
12 }
复制代码
/routes的POST方法将会强制刷新路由信息。
可以通过endpoints.routes.enabled=false来禁用这个端点。
注意:路由会自动根据服务目录的改动来更新,但是POST请求/routes是一种立即强制更新的方法。
1.6.2 Filters Endpoint(过滤器端点)
/filters的GET请求将会返回过滤器类型列表。
1.7 Strangulation Patterns and Local Forwards(压缩模式和本地转发)
当迁移一个老的应用或者API时,需要慢慢把它的访问端点替换成新的实现。Zuul会是一个很有用的代理,因为你可以使用它处理所有来自客户端老的端点的流量并且重定向一些请求到新的实现上。如下:
application.yml.
复制代码
1 zuul:
2 routes:
3 first:
4 path: /first/**
5 url: http://first.example.com
6 second:
7 path: /second/**
8 url: forward:/second
9 third:
10 path: /third/**
11 url: forward:/3rd
12 legacy:
13 path: /**
14 url: http://legacy.example.com
复制代码
其中,forward:开头的url将会转发到本地。
1.8 Uploading Files through Zuul(通过Zuul上传文件)
如果你使用@EnableZuulProxy,那么可以通过代理路径来上传一些小文件,对于大文件有一个可以绕过Spring DispatcherServlet的路径“/zuul/*”,换句话说,如果zuul.routes.customers=/customers/*,那么可以
通过发送POST请求到/zuul/customers/*。servlet路径是通过zuul.servletPath外部化的。如果代理路由使用Ribbon,尤其大文件需要提高超时时间。如下:
application.yml.
复制代码
1 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
2 ribbon:
3 ConnectTimeout: 3000
4 ReadTimeout: 60000
复制代码
1.9 Query String Encoding(查询字段编码)
当处理请求时,查询字段将会被解码,这样就可以在Zuul过滤器中修改他们。然后在过滤器中再重新编码后发送请求给后端。如果使用Javascrip的encodeURIComponent()方法,那么结果可能会和原始输入不同。这在大多数情况下不会有问题,但是一些web服务器对于复杂查询字段的编码要求还是很挑剔的。
为了强制查询字符串的原始编码,可以向ZuulProperties传递一个特殊的标志,以便将查询字符串作为HttpServletRequest::getQueryString方法使用。
application.yml.
复制代码
1 zuul:
2 forceOriginalQueryStringEncoding: true
复制代码
注意:这个特殊的标志只对SimpleHostRoutingFilter有效,另外可以通过RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)来覆盖查询字符串。
1.10 Plain Embedded Zuul(纯内置Zuul)
如果使用@EnableZuulServer (而不是@EnableZuulProxy),可以启动一个Zuul服务器但是没有任何代理。任何ZuulFilter类型的bean会自动安装,但是没有任何代理过滤器会被自动添加。
这种情况下,Zuul服务器的路由依然可以通过"zuul.routes.*"来配置,但是没有服务发现和代理。因此,"serviceId" 和 "url"会被忽略掉。在下面的例子中所有"/api/**"中的路径都会被映射到Zuul的过滤器链。
application.yml.
复制代码
1 zuul:
2 routes:
3 api: /api/**
复制代码
1.11 Disable Zuul Filters(禁用Zuul过滤器)
在代理和服务器模式,spring cloud Zuul都会默认注册一些ZuulFilter。可以通过zuul...disable=true来禁用指定的过滤器。
按照惯例,包名中filters的后面就是filterType。例如,如果要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter,可以设置zuul.SendResponseFilter.post.disable=true。
1.12 Providing Hystrix Fallbacks For Routes(为路由提供Hystrix降级服务)
当Zuul的路由回路出现问题时,可以通过一个FallbackProvider类型的bean来提供降级服务。在这个bean中,需要指定路由的ID,并且提供一个ClientHttpResponse。下面的例子提供了一个相对简单的FallbackProvider的实现。
复制代码
1 class MyFallbackProvider implements FallbackProvider {
2
3 @Override
4 public String getRoute() {
5 return "customers";
6 }
7
8 @Override
9 public ClientHttpResponse fallbackResponse(String route, final Throwable cause) {
10 if (cause instanceof HystrixTimeoutException) {
11 return response(HttpStatus.GATEWAY_TIMEOUT);
12 } else {
13 return response(HttpStatus.INTERNAL_SERVER_ERROR);
14 }
15 }
16
17 private ClientHttpResponse response(final HttpStatus status) {
18 return new ClientHttpResponse() {
19 @Override
20 public HttpStatus getStatusCode() throws IOException {
21 return status;
22 }
23
24 @Override
25 public int getRawStatusCode() throws IOException {
26 return status.value();
27 }
28
29 @Override
30 public String getStatusText() throws IOException {
31 return status.getReasonPhrase();
32 }
33
34 @Override
35 public void close() {
36 }
37
38 @Override
39 public InputStream getBody() throws IOException {
40 return new ByteArrayInputStream("fallback".getBytes());
41 }
42
43 @Override
44 public HttpHeaders getHeaders() {
45 HttpHeaders headers = new HttpHeaders();
46 headers.setContentType(MediaType.APPLICATION_JSON);
47 return headers;
48 }
49 };
50 }
51 }
复制代码
下面的例子是对应上面例子的路由配置:
复制代码
1 zuul:
2 routes:
3 customers: /customers/**
复制代码
如果要对所有路由提供一个默认降级服务,可以创建一个FallbackProvider类型的bean,然后在getRoute方法中返回“*”或者null。如下:
复制代码
1 class MyFallbackProvider implements FallbackProvider {
2 @Override
3 public String getRoute() {
4 return "*";
5 }
6
7 @Override
8 public ClientHttpResponse fallbackResponse(String route, Throwable throwable) {
9 return new ClientHttpResponse() {
10 @Override
11 public HttpStatus getStatusCode() throws IOException {
12 return HttpStatus.OK;
13 }
14
15 @Override
16 public int getRawStatusCode() throws IOException {
17 return 200;
18 }
19
20 @Override
21 public String getStatusText() throws IOException {
22 return "OK";
23 }
24
25 @Override
26 public void close() {
27
28 }
29
30 @Override
31 public InputStream getBody() throws IOException {
32 return new ByteArrayInputStream("fallback".getBytes());
33 }
34
35 @Override
36 public HttpHeaders getHeaders() {
37 HttpHeaders headers = new HttpHeaders();
38 headers.setContentType(MediaType.APPLICATION_JSON);
39 return headers;
40 }
41 };
42 }
43 }
复制代码
1.13 Zuul Timeouts(Zuul的超时时间)
如果想要为通过Zuul代理的请求设置socket超时时间和读取超时时间,你有两个选项,基于配置:
1)如果Zuul使用服务发现,则配置ribbon.ReadTimeout和ribbon.SocketTimeout;
2)如果路由是通过URL指定的,那么需要配置zuul.host.connect-timeout-millis和zuul.host.socket-timeout-millis
1.14 Rewriting the Location header(重写头部Location字段)
如果Zuul在一个web应用前面,那么你需要重写Location头部当你的web应用通过HTTP状态码3XX重定向。否则,浏览器会重定向到web应用的URL而不是Zuul的URL。
可以通过配置一个LocationRewriteFilter类型的Zuul过滤器来重写Location头部到Zuul的URL。它还恢复了删除的全局前缀和特定于路由的前缀。如下:
复制代码
1 import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter;
2 ...
3
4 @Configuration
5 @EnableZuulProxy
6 public class ZuulConfig {
7 @Bean
8 public LocationRewriteFilter locationRewriteFilter() {
9 return new LocationRewriteFilter();
10 }
11 }
复制代码
注意:要非常小心使用这个过滤器,因为它会作用于所有响应码为3XX的Location头部,这可能在某些场合不适合。比如要重定向到一个外部地址。
1.15 Metrics(度量)
Zuul在Actuator metrics端点下提供metrics,当路由请求出现失败时。可以通过/actuator/metrics端点查看。metrics名称的格式为ZUUL::EXCEPTION:errorCause:statusCode。
1.16 Zuul Developer Guide(Zuul开发指南)
1.16.1 The Zuul Servlet
Zuul的实现是一个Servlet。通常情况下,Zuul是嵌入到Spring分发机制中的。Spring MVC会掌控路由。这种情况下,Zuul会缓存请求。如果有一种情况是穿过Zuul但是不要缓存(例如大文件的上传),这时可以使用一种独立于Spring分发器的外部Servlet。默认情况,这个Servlet的地址是/zuul。也可以通过zuul.servlet-path属性来修改。
1.16.2 Zuul RequestContext
Zuul使用RequestContext在不同的过滤器中传递信息。它的数据保存在特定于每个请求的ThreadLocal中.它存储的信息有:路由请求到何处,错误,HttpServletRequest 和 HttpServletResponse。
RequestContext继承ConcurrentHashMap,所以它可以存储任何信息。FilterConstants保存了那些被过滤器使用的key。
1.16.3 @EnableZuulProxy vs. @EnableZuulServer
Spring Cloud Netflix安装了一系列过滤器,安装了哪些过滤器依赖于你使用哪种注解来开启Zuul的。@EnableZuulProxy是@EnableZuulServer的超集。换句话说,@EnableZuulProxy包含了@EnableZuulServer中的过滤器。
在“proxy”模式中的额外过滤器开启了路由功能。所以如果想要一个“空白”的Zuul,就使用@EnableZuulServer。
1.16.4 @EnableZuulServer Filters
@EnableZuulServer创建一个SimpleRouteLocator(它从Spring Boot配置文件中加载路由定义)。
安装的过滤器(作为普通的spring bean):
1)Pre filters:
ServletDetectionFilter:检测请求是否是通过Spring Dispatcher,设置FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY的布尔值。
FormBodyWrapperFilter:解析表单数据,并且为下游请求重新编码这些数据。
DebugFilter:如果请求参数中设置了debug,则RequestContext.setDebugRouting()和RequestContext.setDebugRequest()都设置为true。
2)Route filters:
SendForwardFilter:使用RequestDispatcher转发请求。转发地址存储在RequestContext的FilterConstants.FORWARD_TO_KEY属性中。
3)Post filters:
SendResponseFilter:将代理请求