使用Zuul构建微服务网关
Posted shi_zi_183
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Zuul构建微服务网关相关的知识,希望对你有一定的参考价值。
使用Zuul构建微服务网关
为什么要使用微服务网关
经过前文的讲解,微服务架构已经初具雏形,但还有一些问题——不同的微服务一般会有不同的网络地址,而外部客户端(例如手机APP)可能需要调用多个服务的接口才能完成一个业务需求。
如果让客户端直接与各个微服务通信,会有以下的问题:
- 客户端多次请求不同的微服务,增加了客户端的复杂性
- 存在跨域请求,在一定场景下处理相对复杂
- 认证复杂,每个服务都需要独立认证
- 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将很难实施。
- 某些微服务可能使用了对防火墙/浏览器不友好的协议,直接访问时会有一定的困难。
以上问题可借助微服务网关解决。微服务网关是介于客户端和服务器之间的中间层,所有的外部请求都会先经过微服务网关。
微服务网关封装了应用程序的内部结构,客户端只用跟网关交互,而无须直接调用特定微服务的接口。这样,开发就可以得到简化。不仅如此使用微服务网关还有以下优点。 - 易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。
- 易于认证。可在微服务网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
- 减少了客户端与各个微服务之间的交互次数。
Zuul简介
Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用。Zuul的核心是一系列的过滤器,这些过滤器可以完成以下功能。
- 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求。
- 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图。
- 动态路由:动态地将请求路由到不同地后端集群。
- 压力测试:逐渐增加指向集群地流量,以了解性能。
- 负载分配:为每一种负载类型分配对应容量。并弃用超出限定值地请求。
- 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到内部集群。
- 多区域弹性:跨越AWS Region进行请求路由,旨在实现ELB使用地多样化,以及让系统的边缘更贴近系统的使用者。
Spring Cloud对Zuul进行了整合与增强。目前,Zuul使用的默认HTTP客户端是Apache HTTP Client,也可以使用RestClient或者okhttp3.OkHttpClient。如果想要使用RestClient,可以设置ribbon.restclient.enabled=true;想要使用okhttp3.OkHttpClient,可以设置ribbon.okhttp.enabled=true。
编写Zuul微服务网关
1)创建一个Maven工程,ArtifactId是microservice-gateway-zuul,并为项目添加以下依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)在启动类上添加注解@EnableZuulProxy,声明一个Zuul代理。该代理使用Ribbon来定位注册在Eureka Server中的微服务;同时,该代理还整合了Hystrix,从而实现了容错,所有经过Zuul的请求都会在Hystrix命令中执行。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args){
SpringApplication.run(ZuulApplication.class,args);
}
}
3)编写配置文件application.xml
server:
port: 8040
spring:
application:
name: microservice-gateway-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
测试一、路由规则
1)启动项目microservice-discovery-eureka。
2)启动项目microservice-provider-user。
3)启动项目microservice-consumer-movie-ribbon。
4)启动项目microservice-gateway-zuul。
5)访问http://localhost:8040/microservice-consumer-movie/user/1,请求会被转发到http://localhost:8010/user/1(电影微服务)。
6)访问http://localhost:8040/microservice-provider-user/1,请求会转发到http://localhost:8000/1(用户微服务)
说明在默认情况下,Zuul会代理所有注册到Eureka Server的微服务,并且Zuul的路由规则如下:
http://ZUUL_HOST:ZUUL_PORT/微服务在Eureka上的serviceId/**
会被转发到serviceId对应的微服务。
测试二、负载均衡
1)启动项目microservice-discovery-eureka
2)启动多个microservice-provider-user实例
3)启动项目microservice-gateway-zuul。此时,Eureka Server首页。
4)多次访问http://localhost:8040/microservice-provider-user/1,会发现两个用户微服务节点都打印了
说明Zuul可以使用Ribbon达到负载均衡的效果。
测试三、Hystrix容错与监控
1)启动项目microservice-discovery-eureka
2)启动项目microservice-provider-user
3)启动项目microservice-consumer-movie-ribbon
4)启动项目microservice-gateway-zuul
5)启动项目microservice-hystrix-dashboard
6)访问http://localhost:8040/microservice-consumer-movie/user/1,可获得预期结果。
7)在Hystrix Dashboard中输入http://localhost:8040/hystrix.stream,随意指定某个Title并单击Monitor Stream按钮后
说明Zuul已经整合了Hystrix
管理端点
当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露两个端点:/routes和filters。借助这些端点,可方便、直观地查看以及管理Zuul。
routes端点
/routes端点的使用非常简单
- 使用GET方法访问该端点,即可返回Zuul当前映射的路由列表。
- 使用POST方法访问该端点就会强制刷新Zuul当前映射的路由列表。尽管路由会自动刷新,Spring Cloud依然提供了强制立即刷新的方式。
- Spring Cloud Edgware对/routes端点进行了进一步的增强,我们可使用/routes?format=details查看更多与路由相关的详情设置。
由于spring-cloud-starter-netfilx-zuul已经包含了spring-boot-starter-actuator,因此之前编写的microservice-gateway-zuul已具备路由管理的能力。
测试
1)启动项目microservice-discovery-eureka
2)启动项目microservice-provider-user
3)启动项目microservice-consumer-movie-ribbon
4)修改项目microservice-gateway-zuul的配置,设置management.security.enabled: false
。
5)启动项目microservice-gateway-zuul
6)使用浏览器访问http://localhost:8040/routes
,可获得如下的结果
7)访问http://localhost:8040/routes?format=details
由结果可知,此时可看到更多与路由相关的配置,例如路由id、转发路径、是否重试等,这些对于我们调试很有用
也可使用类似方式测试Zuul路由的自动刷新与强制刷新。
filters端点
从Spring Cloud Edgware版本开始,Zuul提供了/filters端点。访问该端点即可返回Zuul中当前所有过滤器的详情,并按照类型分类。
如下是/filters端点的展示结果,从中,我们可以了解当前Zuul中,error、post、pre、route四种类型的过滤器分别有哪些,每个过滤器order(执行顺序)是多少,以及是否启用等信息。这对Zuul问题的定位很有用。
路由配置详情
前文已经编写了一个简单的Zuul网关,并让该网关代理了所有注册到Eureka Server的微服务。但在现实中可能只想让Zuul代理部分微服务,又或者需要对URL进行更加精确的控制。
1)自定义指定微服务的访问路径
配置zuul.routes.指定微服务的serviceId = 指定路径
接口
zuul:
routes:
microservice-provider-user: /user/**
完成设置后,microservice-provider-user微服务就会被映射到/user/** 路径。
2)忽略指定微服务
忽略服务非常简单,可以使用zuul.ignored-services
配置需要忽略的服务,多个服务间用逗号分隔。
zuul:
ignored-services: microservice-provider-user,microservice-consumer-movie
这样就可让Zuul忽略microservice-provider-user和microservice-consumer-movie微服务,只代理其他微服务。
3)忽略所有微服务,只路由指定微服务
很多场景下,可能只想要让Zuul代理指定的微服务,此时可以将zuul.ignored-services
设为’*’
zuul:
routes:
microservice-provider-user: /user/**
ignored-services: '*'
这样就可以让Zuul只路由microservice-provider-user微服务。
4)同时指定微服务的serviceId和对应路径。
zuul:
routes:
user_route: #该配置方式中,user-routes只是给路由一个名称,可以任意起名。
service-id: microservice-provider-user
path: /user/** #service-id对应的路径
5)同时指定path和URL
zuul:
routes:
user_route:
url: http://localhost:8000/
path: /user/**
这样可以将/user/**
映射到http://localhost:8000/**
。
需要注意的是,使用这种方式配置的路由不会作为HystrixCommand执行,同时也不能使用RIbbon来负载均衡多个URL。
6)同时指定path和URL,并且不破坏Zuul的Hystrix、Ribbon特性
zuul:
routes:
user_route:
service-id: microservice-provider-user
path: /user/**
ribbon:
eureka:
enable: false
microservice-provider-user:
ribbon:
listOfServers: localhost:8000,localhost:8001
7)使用正则表达式指定Zuul的路由匹配规则
借助PatternServiceRouteMapper,实现从微服务到映射路由的正则配置
package com.example.config;
import org.springframework.cloud.netflix.zuul.filters.discovery.PatternServiceRouteMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RouteMapperConfig {
@Bean
public PatternServiceRouteMapper serviceRouteMapper(){
//调用构造函数PatternServiceRouteMapper(String servicePattern, String routePattern)
//servicePattern指定微服务的正则
//routePattern指定路由正则
return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)","${version}/${name}");
}
}
通过这段代码即可实现将诸如microservice-provider-user-v1这个微服务,映射到/v1/microservice-provider-user/**这个路径。
8)路由前缀
zuul:
prefix: /api
strip-prefix: false
routes:
microservice-provider-user: /user/**
这样,访问Zuul的/api/user/1路径,请求将被发送到microservice-provider-user的/api/1。
zuul:
routes:
microservice-provider-user:
path: /user/**
strip-prefix: false
这样,访问Zuul的/api/user/1路径,请求将被发送到microservice-provider-user的/user/1。
9)忽略某些路径
上文讲解了如何忽略微服务,但有时还需要更细粒度的路由控制。例如,想让Zuul代理某个微服务,同时又想保护该微服务的某些敏感路径。此时,可使用ignored-Patterns,指定忽略的正则。
zuul:
ignored-patterns: /**/admin/**
routes:
microservice-provider-user: /user/**
这样就可将microservice-provider-user微服务映射到/user/**路径,但会忽略该微服务中所有包含/admin/的路径。
10)本地转发
zuul:
routes:
route-name:
path: /path-a/**
url:
forward: /path-b
这样,当访问Zuul的/path-a/**
路径,将转发到Zuul的/path-b/**
注:若无法掌握Zuul路由的规律,可将com.example包的日志级别设为DEBUG。这样,Zuul就会打印转发的具体细节,从而有助于更好地理解Zuul地路由配置。
logging:
level:
com.example: DEBUG
Zuul的安全与Header
敏感Header的设置
一般来说,可在同一个系统中的服务之间共享Header。不过应尽量防止让一些敏感的Header外泄。因此,在很多场景下,需要通过为路由指定一些了敏感Header列表。
zuul:
routes:
microservice-consumer-movie:
path: /users/**
sensitive-headers: Cookie,Set-Cookie,Authorization
url: https://downstream
这样就可为microservice-consumer-movie微服务指定敏感Header了。
也可用zuul.sensitive-headers全局指定敏感Header
zuul:
sensitive-headers: Cookie,Set-Cookie,Authorization
需要注意的是,如果使用zuul.routes.*.sensitive-headers的配置方式,会覆盖全局的配置。
忽略Header
可使用zuul.ignoredHeaders
属性丢弃一些Header
zuul:
ignored-headers: Header1,Header2
这样设置后,Header1和Header2将不会传播到其他的微服务中。
默认情况下,zuul.ignored-headers是空值,但如果Spring Security在项目的classpath中,那么zuul.ignored-headers的默认值就是Pragma,Cache-Contronl,X-Frame-Options,X-Content-Type-Option,X-XSS-Protection,Expires。所以,当Spring Security在项目的classpath中,同时又需要使用下游微服务的Spring Security的Header时,可以将zuul.ignoreSecurity-Headers设置false。
使用Zuul上传文件
对于小文件(1M以内)上传,无须任何处理,即可正常上传。对于大文件(10M以上)上传,需要为上传路径添加/zuul前缀。也可使用zuul.servlet-path自定义前缀。
也就是说,假设zuul.routes.microservice-flie-upload = /microservice-file-upload/**
,如果http://{HOST}:{PORT}/upload
是微服务microservice-file-upload的上传路径,则可使用Zuul的/zuul/microservice-file-upload/upload路径上传大文件。
如果Zuul使用了Ribbon做负载均衡,那么对于超大的文件,需要提升超时设置。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillseconds: 60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
1)创建一个Maven工程,ArtifactId是microservice-file-upload,并为项目添加以下依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2)在启动类上添加@SpringBootApplication、@EnableEurekaClient注解。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class FileUploadApplication {
public static void main(String[] args){
SpringApplication.run(FileUploadApplication.class,args);
}
}
3)编写Controller
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Controller
public class FileUploadController {
@RequestMapping(value = "/upload",method = RequestMethod.POST)
public @ResponseBody
String handleFileUpload(@RequestParam(value = "file",required = true)MultipartFile file) throws IOException {
byte[] bytes=file.getBytes();
File fileTOSave=new File(file.getOriginalFilename());
FileCopyUtils.copy(bytes,fileTOSave);
return fileTOSave.getAbsolutePath();
}
}
配置文件application.yml,在其中添加如下内容
server:
port: 8050
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
spring:
application:
name: microservice-file-upload
http:
multipart:
max-file-size: 2000Mb #Max file size,默认1M
max-request-size: 2500Mb #Max request size,默认10M
由配置可知,已经将该服务注册到Eureka Server上,并配置了文件上传大小的限制。这样一个文件上传的微服务就编写完成了。
curl -F "file=@D:/user.csv" localhost:8050/upload
测试
1)准备1个小文件(1M以下),记为small.file;1个超大文件(1G以上,2G以上),记为large.file。
2)启动microservice-discovery-eureka
3)启动microservice-file-upload
4)启动microservice-gateway-zuul
5)测试直接上传到microservice-file-upload上:使用命令curl -F "file=@D:/user.csv" localhost:8050/upload
,上传大文件,发现可以正常上传。同理,小文件也可以上传。
6)测试通过Zuul上传小文件
curl -v -H "Transfer-Encoding: chunked" -F "file=@D:/user.csv" localhost:8040/microservice-file-upload/upload
7)测试通过Zuul上传大文件,不添加/zuul前缀。
curl -v -H "Transfer-Encoding: chunked" -F "file=@D:/user.csv" localhost:8040/microservice-file-upload/upload
8)测试通过Zuul上传大文件,添加/zuul前缀
curl -v -H "Transfer-Encoding: chunked" -F "file=@D:/test.exe" localhost:8040/zuul/microservice-file-upload/upload
9)因此,不妨在application.yml中添加如下内容。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
10)关闭microservice-gateway-zuul,启动microservice-gateway-zuul-file-upload再次使用如下命令进行测试,此时已可正常上传文件。
Zuul的过滤器
过滤器类型与请求生命周期
Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了4种标准过滤器类型,这些过滤器类型对应请求的典型生命周期。
- PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- POUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发送错误时执行该过滤器。
除了默认的过滤器类型,Zuul还允许创建自定义的过滤器类型。例如,可以定制一种STATIC类型的 过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
内置过滤器详解
Spring Cloud默认为Zuul编写并启用了一些过滤器,这些过滤器有什么作用呢?我们不妨结合@EnableZuulServer、@EnableZuulProxy两个注解进行讨论。
可将@EnableZuulProxy简单理解为@EnableZuulServer的增强版。事实上,当Zuul与Eureka、Ribbon等组件配合使用时,@EnableZuulProxy是我们最常用的注解,这里使用的也是@EnableZuulServer。
@RequestContext,其用于在过滤器之间传递消息。它的数据保存在每个请求的ThreadLocal中。它用于存储请求路由到哪里、错误、HttpServletRequest、HttpServletResponse等信息。RequestContext扩展了ConcurrentHashMap,所以,理论上任何数据都可以存储在RequestContext中。
@EnableZuulServer所启用的过滤器
pre类型过滤器
1)ServletDetectionFilter:该过滤器用于查看请求是否通过Spring Dispatcher。检查后,通过FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY设置布尔值。
2)FormBodyWrapperFilter:解析表单数据,并为请求重新编码。
3)DebugFilter:顾名思义,调试用的过滤器。当设置zuul.includer-debug-header=true抑或设置zuul.debug.request=true,并在请求时加上了debug=true的参数,例如$ZUUL_HOST:ZUUL_PORT/some-path?debug=true,就会开启该过滤器。该过滤器会把RequestContext.setDebugRouting()以及RequestContext.setDebugRequest()设为true。
route类型过滤器
SendForwardFilter:该过滤器使用Servlet RequestDispatcher转发请求,转发位置存储在RequestContext的属性FilterConstants.FORWARD_TO_KEY中。这对转发到Zuul自身的端点很有用。
zuul:
routes:
abc:
path: /path-a/**
url: forward:/path-b
post类型过滤器
SendResponseFilter:将代理请求的响应写入当前响应。
error类型过滤器
SendErrorFilter:若RequestContext.getThrowable()不为null,则默认转发到/error,也可以设置error.path属性来修改默认的转发路径。
@EnableZuulProxy所启用的过滤器
如果使用注解@EnableZuulProxy,那么除上述过滤器外,Spring Cloud还会安装以下过滤器。
pre类型过滤器
PreDecorationFilter:该过滤器根据提供的RouteLocator 确定路由到的地址,以及怎样去路由。同时,该过滤器还为下游请求设置各种代理相关的header。
route类型过滤器
1)RibbonRoutingFilter:该过滤器使用Ribbon、Hystrix和可插拔的HTTP客户端发送请求。serviceId在RequestContext的属性FilterConstants.SERVICE_ID_KEY中。该过滤器可使用如下这些不同的HTTP客户端。
- Apache HttpClient:默认的HTTP客户端
- Squareup OkHttpClient v3:若需使用该客户端,需保证com.squareup.okhttp3的依赖在classpath中,并设置ribbon.okhttp.enable=true。
- Netflix RibbonHTTP Client:设置ribbon.restclient.enable=true即可启用该HTTP客户端。该客户端有一定限制,例如不支持PATCH方法,另外,它有内置的重试机制。
2)SimpleHostRoutingFilter:该过滤器通过Apache HttpClient向指定的URL发送请求。URL在RequestContext.getRouteHost()中。
编写Zuul过滤器
1)复制项目microservice-gateway-zuul,将ArtifactId修改为microservice-gateway-zuul-filter。
2)编写自定义Zuul过滤器
package com.example.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import javax.servlet.http.HttpServletRequest;
public class PreRequestLogFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PreRequestLogFilter.class);
@Override
public String filterType() {
//pre类型的过滤器
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
//在org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter之前执行
return FilterConstants.PRE_DECORATION_FILTER_ORDER -1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request=ctx.getRequest();
PreRequestLogFilter.LOGGER.info(String.format("send %s request to %s",request.getMethod(),request.getRequestURI().toString()));
return null;
}
}
由代码可知,自定义的Zuul Filter需实现以下几个方法。
- filterType:返回过滤器的类型。有pre、route、post、error等几种取值,分别对应上文的几种过滤器。
- filterOrder:返回一个int值来指定过滤器的执行顺序,不同的过滤器允许返回相同的数字。
- shouldFilter:返回一个boolean值来判断该过滤器是否要执行,true表示执行,false表示不执行。
- run:过滤器的具体逻辑。本例中让它打印了请求的HTTP方法以及请求的地址。
3)创建配置类
package com.example.config;
import com.example.filter.PreRequestLogFilter;
import org.springframework.context.annotation以上是关于使用Zuul构建微服务网关的主要内容,如果未能解决你的问题,请参考以下文章
springCloud(14):使用Zuul构建微服务网关-路由端点与路由配置详解
springCloud(16):使用Zuul构建微服务网关-容错回退与高可用