路由器和过滤器:Zuul
路由是微服务架构的组成部分。 例如,/ 可以映射到您的Web应用程序,/api /users映射到用户服务,/api/ shop映射到购物服务。 Zuul是Netflix基于JVM的路由器和服务器端负载均衡器。
Netflix使用Zuul进行以下操作:
配置属性zuul.max.host.connections已被两个新属性替换,zuul.host.maxTotalConnections和zuul.host.maxPerRouteConnections,分别默认为200和20。
所有路由的默认Hystrix隔离模式(ExecutionIsolationStrategy)是SEMAPHORE(信号量)。zuul.ribbonIsolationStrategy 如果首选此隔离模式,则可以更改为THREAD(线程)。
如何引入Zuul
<!--引入zuul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
嵌入式Zuul反向代理
Spring Cloud已经创建了一个嵌入式Zuul代理,以方便开发一个非常常见的用例,其中UI应用程序希望代理对一个或多个后端服务的调用。该特性对于用户界面代理所需的后端服务非常有用,避免了对所有后端独立管理CORS和身份验证问题的需要。
要启用它,可以使用@EnableZuulProxy注释Spring Boot主类上,并将本地调用转发给相应的服务。根据约定,ID为“users”的服务将从位于/users的代理接收请求(去掉前缀)。 代理使用功能区来定位要通过发现转发的实例,并且所有请求都在hystrix命令中执行,因此故障将显示在Hystrix指标中,并且一旦电路打开,代理将不会尝试联系服务。
默认Zuul对所有服务都启用代理,要跳过默认代理方式,请将zuul.ignored-services配置不需要代理那些服务。
application.yml
zuul:
ignoredServices: '*'
routes:
users: /myusers/**
在上面配置中,除了“users”外,所有服务都被忽略。[忽略所有微服务,只路由指定微服务]
要扩充或更改代理路由,您可以添加如下所示的外部配置:
application.yml.
zuul.routes.指定微服务的ServiceId=指定路径 (path和serviceId的组合简洁配置)
zuul:
routes:
users: /myusers/**
这意味着对“/ myusers”的http调用被转发到“users”服务(例如“/myusers/101”被转发到"users"服务的“/101”路径上)。
要对路径进行更细粒度的控制,可以单独指定路径和serviceId:
application.yml.
zuul:
routes:
users:
path: /myusers/**
serviceId: users_service
注意:上面users只是给路由起一个名称,可以随便起
这意味着对“ /myusers”的http调用将转发到“users_service”服务。 但必须指定调用路径,因此“/myusers/ *”只匹配一个级别,但“/myusers / **”按层次匹配。
可以不指定serviceId,直接指定具体的路径
application.yml.
zuul:
routes:
users:
path: /myusers/**
url: http://example.com/users_service
上面配置 url 为指定的url ,path我url对应的路径。
注意:这些简单的url-routes不会作为HystrixCommand执行,也不会使用Ribbon对多个URL进行负载均衡。 要做到这一点,你
可以使用静态服务器列表指定serviceId:。
zuul:
routes:
echo:
path: /myusers/**
serviceId: myusers-service
stripPrefix: true
hystrix:
command:
myusers-service:
execution:
isolation:
thread:
timeoutInMilliseconds: ...
myusers-service:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
listOfServers: http://example1.com,http://example2.com
ConnectTimeout: 1000
ReadTimeout: 3000
MaxTotalHttpConnections: 500
MaxConnectionsPerHost: 100
另一种方法是指定服务路由并为serviceId配置Ribbon客户端(这需要禁用Eureka对Ribbon的支持,默认情况下Ribbon会根据服务发现机制来获取配置服务对应的实例清单,但是这里并没有整合类似Eureka之类的服务治理框架,如果加入了服务治理框架就不需要禁用ribbon也不需要配置 <rooter>.ribbon.listOfServers)【以下也是最常用的】
zuul:
routes:
users:
path: /myusers/**
serviceId: users
ribbon:
eureka:
enabled: false
users:
ribbon:
listOfServers: localhost:8000,localhost:80001 //也可以为具体的域名
users为随便起的路由名
上述配置即指定了path与serviceId,又不会破坏zuul的hystrix与ribbon的特性,即 使用path和url的映射关系来配置路由规则的时候,路由转发请求不会采用HystrixCommand来包装,所有请求没有线程隔离和断路器保护,并且不会采用负载均衡。因此我们在使用zuul的时候尽量使用path和serviceId组合来配置,这样不仅保证API网关的健壮性和稳定性,也能用到Ribbon的负载均衡。
您可以使用regexmapper在serviceId和路由之间提供约定。 它使用名为groups的正则表达式从serviceId中提取变量并将它们注入路由模式。
在SpringBoot启动类中添加如下配置:
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}
这意味着serviceId为“myusers-v1”将映射到路由“/v1/ myusers/ **”。 接受任何正则表达式,但所有命名组必须同时出现在servicePattern【(?<name>^.+)-(?<version>v.+$)】正则表达式为服务名配置模式)和routePattern【${version}/${name}为路由映射模式】中。 如果servicePattern与serviceId不匹配,则使用默认行为(即完整服务名作为前缀的路径表达式)。 在上面的示例中,serviceId“myusers”将映射到路由“/ myusers / **”(未检测到版本)此功能默认情况下处于禁用状态,仅适用于已发现的服务。
要为所有映射添加前缀,请将zuul.prefix设置为值,例如/ api。 在默认转发请求之前,会从请求中删除代理前缀(使用zuul.stripPrefix = false关闭此行为)。 您还可以关闭从各个路由中剥离特定于服务的前缀,例如
zuul:
routes:
users:
path: /myusers/**
stripPrefix: false
注意:zuul.stripPrefix仅适用于zuul.prefix中设置的前缀。 它对给定路径的路径中定义的前缀没有任何影响。
在上面配置,对“/ myusers / 101”的请求将转发到“/ myusers / 101”上的“users”服务。
zuul.routes配置的属性信息实际上绑定到ZuulProperties类型的对象。 如果查看该对象的属性,您将看到它还具有“可重试”标志。 将该标志设置为“true”以使Ribbon客户端自动重试失败的请求(如果需要,可以使用Ribbon客户端配置修改重试操作的参数)。
默认情况下,X-Forwarded-Host标头会添加到转发的请求中。 要关闭它,请设置zuul.addProxyHeaders = false。 默认情况下,前缀路径被剥离,对后端的请求会选择一个标题“X-Forwarded-Prefix”。
如果需要更细粒度的忽略,则可以指定要忽略的特定模式。 这些模式在路径定位过程开始时进行评估,这意味着前缀应包含在模式中以保证匹配。 忽略的模式跨越所有服务并取代任何其他路由规范。
application.yml.
zuul:
ignoredPatterns: /**/admin/**
routes:
users: /myusers/**
这意味着所有诸如“/ myusers /101”之类的请求将被转发到“users”服务上的“/ 101”。 但包括“/ admin /”在内的路径请求将被忽略。
Zuul Http客户端
zuul使用的默认HTTP客户端现在由Apache HTTP Client支持,而不是由不推荐使用的Ribbon RestClient支持。 要使用RestClient或使用okhttp3.OkHttpClient设置ribbon.restclient.enabled = true或ribbon.okhttp.enabled = true。 如果要自定义Apache HTTP客户端或OK HTTP客户端,请提供ClosableHttpClient或OkHttpClient类型的bean。
Cookies和敏感 Headers
在同一系统中的服务之间共享请求头是可以的,但您可能不希望敏感的请求头向下游泄漏到外部服务器。 您可以在路由配置中指定忽略的请求头列表。 Cookie起着特殊的作用,因为它们在浏览器中具有明确定义的语义,并且它们始终被视为敏感。 如果您的代理的消费者是浏览器,那么下游服务的cookie也会给用户带来问题,因为它们都会混乱(所有下游服务看起来都来自同一个地方)。
如果您对服务的设计非常小心,例如,如果只有一个下游服务设置了cookie,那么您可以让它们从后端一直流到调用者。 此外,如果您的代理设置了cookie并且您的所有后端服务都是同一系统的一部分,那么简单地共享它们就很自然(例如使用Spring Session将它们链接到某个共享状态)。 除此之外,由下游服务设置的任何cookie都可能对调用者不是很有用,因此建议您(至少)将“Set Cookie”和“Cookie”设置为非敏感的标头 您域名的一部分。 即使对于属于您域的路由,在允许cookie在它们与代理之间流动之前,请仔细考虑它的含义。
可以通过路由指定一些列敏感的请求头:
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
url: https://downstream
上述sensitiveHeaders配置是sensitiveHeaders的默认值,因此除非您希望它不同,否则无需进行设置。注: 这是Spring Cloud Netflix 1.1中的新功能(在1.0中,用户无法控制标题,所有Cookie都在两个方向上流动)。
sensitiveHeaders是黑名单(即就是配置的将不会向下传递),默认不为空,因此要使Zuul发送所有标题(“忽略”标题除外),您必须将其明确设置为空列表。 如果要将cookie或授权标头传递给后端,则必须执行此操作。
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders:
url: https://downstream
也可以通过设置zuul.sensitiveHeaders来全局设置敏感标头(不建议这样做)。 如果在路由上设置了sensitiveHeaders,则会覆盖全局sensitiveHeaders设置。
忽略Headers
可以使用zuul.ignoredHeaders属性丢弃一些Header.
zuul.ignoredHeaders:Header1,Header2 这样将不会传播到其他微服务中。
默认情况下zuul.ignoredHeaders是控制,但是spring-security在项目的calsspath那么zuul.ignoredHeaders的默认值就是Pragma,Cache-Control,X-Frame-Options,X-Content-Type-Options,X-XSS-Protection,Expires.所以,当Spring security在项目的classpath中,同时又需要使用下游微服务的spring security的请求头,可以你将zuul.ignoreSecurityHeaders设置为false。 这可能很有用如果您在Spring Security中禁用了HTTP安全响应标头,并希望获得下游服务提供的值。
管理端点
如果您将@EnableZuulProxy与Spring Boot Actuator一起使用,您将启用(默认情况下)两个额外的端点:
- Routes
- Filters
routes端点
到/routes的路由端点的GET将返回映射路由的列表:
GET /routes.
{
/stores/**: "http://localhost:8081"
}
可以通过将?format = details查询字符串添加到/ routes来请求其他路由详细信息。 这将产生以下输出:
GET /routes?format=details
{
"/stores/**": {
"id": "stores",
"fullPath": "/stores/**",
"location": "http://localhost:8081",
"path": "/**",
"prefix": "/stores",
"retryable": false,
"customSensitiveHeaders": false,
"prefixStripped": true
}
}
POST将强制刷新现有路由。 您可以禁用此端点通过将endpoints.routes.enabled设置为false。
Filters 端点
对于过滤器端点/fliters的GET将按类型返回Zuul过滤器的映射。 对于地图中的每种过滤器类型,您将找到该类型的所有过滤器及其详细信息的列表。
使用Zuul进行文件上传
如果您使用@EnableZuulProxy,您可以使用代理路径上传文件,只要文件很小,它就可以正常工作。 对于大型文件,有一个替代路径绕过“/zuul/*”中的Spring DispatcherServlet(以避免多部分处理)。即 如果zuul.routes.customers = /customers/ **那么您可以将大文件POST到“/zuul/customers/ *”。 servlet路径通过zuul.servletPath外部化。 如果代理路由引导您完成功能区负载均衡器,则极大文件也需要提升超时设置,如进行如下设置:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
查询字符串编码
处理传入请求时,将对查询参数进行解码,以便它们可用于Zuul过滤器中的可能修改。 然后在路由过滤器中构建后端请求时重新编码它们。 如果使用Javascript的encodeURIComponent() 方法编码,结果可能与原始输入不同。 虽然这在大多数情况下不会引起任何问题,但某些Web服务器可能会因复杂查询字符串的编码而变得挑剔。
要强制查询字符串的原始编码,可以将特殊标志传递给ZuulProperties,以便使用HttpServletRequest :: getQueryString方法获取查询字符串:
zuul:
forceOriginalQueryStringEncoding: true
普通嵌入式Zuul
如果使用@EnableZuulServer(而不是@EnableZuulProxy),您也可以在没有代理的情况下运行Zuul服务器,或者选择性地切换代理平台的某些部分。 您添加到ZuulFilter类型的应用程序的任何bean都将自动安装,与@EnableZuulProxy一样,但不会自动添加任何代理过滤器。
在这种情况下,仍然通过配置“zuul.routes.*”来指定进入Zuul服务器的路由,但是没有服务发现且没有代理,因此忽略“serviceId”和“url”设置。 例如:
application.yml.
zuul:
routes:
api: /api/**
将“/api/ **”中的所有路径映射到Zuul过滤器链。
禁用Zuul过滤器
Spring Cloud默认zuul编写并启用了一些列过滤器,在代理和服务器模式下都默认启用了这些ZuulFilter bean。 请参阅zuul filters包以获取已启用的可能过滤器。 如果要禁用一个,只需设置zuul.<SimpleClassName>.<filterType> .disable = true。 按照惯例,过滤器后的包是Zuul过滤器类型。 例如,要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter,请设置zuul.SendResponseFilter.post.disable = true。
为路由提供Hystrix降级
当Zuul中给定路径的电路短路时,您可以通过创建ZuulFallbackProvider类型的bean来提供回退响应。 在此bean中,您需要指定回退所针对的路由ID,并提供ClientHttpResponse作为回退返回。 以下是非常简单的ZuulFallbackProvider实现。
class MyFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
//表明是为那个微服务提供的回退
return "customers";
}
@Override
public ClientHttpResponse fallbackResponse() {
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() {
}
@Override
public InputStream getBody() throws IOException {
// // 定义返回提示
return new ByteArrayInputStream("fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
以下为路由配置:
以下是path和serviceId的简介配置zuul.routes.<serviceId>=<path>
zuul:
routes:
customers: /customers/**
如果您希望为所有路由提供默认回退,则可以创建ZuulFallbackProvider类型的bean并使getRoute方法返回*或null。
class MyFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse() {
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() {
}
@Override
public InputStream getBody() throws IOException {
// 定义返回提示
return new ByteArrayInputStream("fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
如果您想根据失败原因选择响应,请使用FallbackProvider,它将取代未来版本中的ZuulFallbackProvder。
class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(final Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return fallbackResponse();
}
}
@Override
public ClientHttpResponse fallbackResponse() {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
}
@Override
public int getRawStatusCode() throws IOException {
return status.value();
}
@Override
public String getStatusText() throws IOException {
return status.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
Zuul超时
服务发现配置
如果Zuul正在使用服务发现(即将Zuul注册到注册中心),则需要关注两个超时,Hystrix超时(因为默认情况下所有路由都包含在Hystrix命令中)和Ribbon超时。 Hystrix超时需要考虑ribbon读取和连接超时将为该服务发生的重试总次数。 默认情况下,Spring Cloud Zuul会尽力为您计算Hystrix超时,除非您明确指定Hystrix超时。
Hystrix超时使用以下公式计算:
(ribbon.ConnectTimeout + ribbon.ReadTimeout) * (ribbon.MaxAutoRetries + 1) * (ribbon.MaxAutoRetriesNextServer + 1)
例如,如果在应用程序属性中设置以下属性:
application.yml
ribbon:
ReadTimeout:100
ConnectTimeout:500
MaxAutoRetries:1
MaxAutoRetriesNextServer:1
对于以上配置Hystrix超时(对于这种情况下的所有路由)将设置为2400ms。
您可以使用service.ribbon.*属性为各个路由配置Hystrix超时。
如果您选择不配置上述属性,则将使用默认值,因此默认的Hystrix超时将设置为4000毫秒。
如果设置hystrix.command.commandKey.execution.isolation.thread.timeoutInMilliseconds(该参数用来设置API网关中路由转发请求的HystrixCommand执行超时单位毫秒,当路由转发请求的命令执行时间超过该设置的时间Hystrix会将执该命令标记为超时并抛出异常,Zuul会对该异常进行处理并返回Json格式的超时信息),其中commandKey是路由ID,或者设置hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds比这些值将用于全局配置。 如果您设置了这些属性中的任何一个,则您有责任确保这些属性它考虑了功能区连接和读取超时以及可能发生的任何重试。
URL配置
如果您通过指定URL配置Zuul路由,则需要使用zuul.host.connect-timeout-millis和zuul.host.socket-timeout-millis进行超时配置。