一、为什么使用服务网关
不使用服务网关的时候,客户端直接与各个微服务通信,如图
这样会带来以下问题
- 客户端会多次请求不同的微服务,增加了客户端的复杂性。
- 存在跨域请求,在一定场景下处理相对复杂。
- 认证复杂,每个服务都需要独立认证。
- 难以重构,随着项目的迭代,可能需要重新划分微服务。
- 某些微服务可能使用了防火墙/浏览器不友好的协议,直接访问会有一定的困难。
微服务网关是介于客户端与服务器端之间的中间层,所有的外部请求都会先经过微服务网关。使用微服务网关可以解决以上的问题。
如上图,微服务网关封装了应用程序的内部结构,客户端只需跟网关交互,而无需直接调用特定微服务的接口。除此之外,还有以下优点
- 易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。
- 易于认证。可在微服务网关上进行认证,然后再将请求转发到后端的微服务,而无需在每个微服务中进行认证。
- 减少了客户端与每个微服务之间的交互次数。
二、Zuul简介
Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用。Zuul的核心是一系列的过滤器,这些过滤器可以完成以下功能
- 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求。
- 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图。
- 动态路由:动态地将请求路由到不同的后端集群。
- 压力测试:逐渐增加指向集群的流量,以了解性能。
- 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求。
- 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到内部集群。
- 多区域弹性:跨越AWS Region进行请求路由,旨在实现ELB(Elastic Load Balancing)使用的多样化,以及让系统的边缘更贴近系统的使用者。
Spring Cloud对Zuul进行了整合与增强。
三、编写Zuul微服务网关
1)新建maven项目microservice-gateway-zuul。pom.xml引入Eureka和Zuul的依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zhh</groupId>
<artifactId>microservice-gateway-zuul</artifactId>
<version>1.0</version>
<!-- Spring Boot依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- Spring Cloud管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- spring-boot-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- zuul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否则各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)在启动类上添加注解@EnableZuulProxy,声明一个Zuul代理。该代理使用Ribbon来定位注册在Eureka Server中的微服务;同时,该代理还整合了Hystrix,从而实现了容错,所有经过Zuul的请求都会在Hystrix命令中执行。
@SpringBootApplication
@EnableZuulProxy
public class MicroserviceGatewayZuulApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceGatewayZuulApplication.class, args);
}
}
3)编写配置文件application.yml。
server:
port: 8084
spring:
application:
name: gateway-zuul
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8080/eureka/
从配置可知,仅是添加了Zuul的依赖,并将Zuul注册到Eureka Server上。
4)首先启动Eureka Server项目和若干微服务项目,之后启动这个项目。
访问http://ZUUL_HOST:ZUUL_PORT/微服务在Eureka上的serviceId/**,就会被转发到serviceId对应的微服务。
除了动态路由功能之外,还有负载均衡、容错监控的功能。
四、Zuul的路由端点
当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露一个路由管理端点(2.0之后暂时不知道是什么)。
借助这个端点,可以方便、直观地查看以及管理Zuul的路由。
GET访问为查询,POST访问为强制立即刷新(尽管路由会自动刷新)。
五、路由配置详解
1、自定义指定微服务的访问路径
zuul:
routes:
consumer-order: /o/**
这样的话,consumer-order微服务就会被映射到/o/**路径。
2、忽略指定微服务
多个用逗号分隔。
zuul:
ignored-services: consumer-order
这样可让zuul忽略consumer-order微服务,只代理其它微服务。
3、忽略所有微服务,只路由指定微服务
zuul:
ignored-services: '*' # 使用'*'可忽略所有微服务
routes:
consumer-order: /o/**
这样就只路由consumer-order微服务,忽略其它微服务。
4、同时指定微服务的serviceId和对应路径
zuul:
routes:
order: # 只是给路由的一个名称,可以任意起名
serviceId: consumer-order
path: /o/** # serviceId对应的路径
5、同时指定path和URL
zuul:
routes:
order:
url: http://localhost:8082/ # 指定的url
path: /o/** # url对应的路径
这样就可将/o/**映射到http://localhost:8082/**。
还有其它的,比如使用正则表达式指定Zuul的路由匹配规则、路由前缀、忽略某些路径等,这里就不介绍了。有兴趣的小伙伴可以自行去了解。
六、Zuul的安全与Header
1、sensitiveHeaders:会过滤客户端请求中的和该配置项匹配的headers
比如
zuul:
sensitiveHeaders: X-ABC
如果客户端在发请求是带了X-ABC,那么X-ABC不会传递给下游服务
2、ignoredHeaders:会过滤服务之间通信附带的headers
比如
zuul:
ignoredHeaders: X-ABC
如果客户端在发请求是带了X-ABC,那么X-ABC依然会传递给下游服务。但是如果下游服务再转发就会被过滤
还有一种情况就是客户端带了X-ABC,在ZUUL的Filter中又addZuulRequestHeader("X-ABC", "new"),
那么客户端的X-ABC将会被覆盖,此时不需要sensitiveHeaders。如果设置了sensitiveHeaders: X-ABC,那么Filter中设置的X-ABC依然不会被过滤。
七、使用Zuul上传文件
上传文件含有中文名,需要为上传路径添加/zuul前缀(当前版本貌似已经修复,不需要添加/zuul前缀也可以)。
对于小文件(1M以内)上传无需任何处理。对于大文件(10M以上)上传,需要为上传路径添加 /zuul 前缀,也可以使用zuul.servlet-path自定义前缀。
假设 zuul.routes.file-upload = /file-upload/**,如果http://{HOST}:{PORT}/uoload是微服务file-upload上传路径,则可以使用Zuul的/zuul/file-upload/upload路径上传大文件。
如果Zuul使用Ribbon做负载均衡,那么对于超大文件(例如500M)需要提升超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
ribbon.ConnectTimeout=3000
ribbon.ReadTimeout= 60000
添加/zuul前缀是指需要使用ZuulServlet来实现文件上传。
八、Zuul的过滤器
过滤器是Zuul的核心组件。
1、过滤器类型与请求生命周期
通过看com.netflix.zuul.ZuulFilter类中filterType方法的注释,我们知道Zuul中定义了4种标准过滤器类型
这些过滤器类型对应于请求的典型生命周期
- pre:在请求被路由之前调用。可以实现身份认证、在集群中选择请求的微服务、记录调试信息等。
- route:将请求路由到微服务。用于构建发送给微服务的请求,并使用Apache HttpClient或Netflix Ribbon请求微服务。
- post:在路由到微服务以后执行。可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- error:在其它阶段发生错误时执行该过滤器。
此外,还有一个static类型的过滤器StaticResponseFilter。
2、编写Zuul过滤器
1)编写自定义Zuul过滤器,让其记录请求日志。
重写四个方法。
/**
* 记录请求日志过滤器
* @author z_hh
* @time 2019年2月19日
*/
public class PreRequestLogFilter extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/**
* 返回一个boolean值来判断该过滤器是否要执行。
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 执行的具体逻辑。
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
LOG.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());
return null;
}
/**
* 过滤器类型。有pre、route、post、error等集中取值。
*/
@Override
public String filterType() {
return "pre";
}
/**
* 指定执行的顺序,允许不用过滤器返回相同的数字。
*/
@Override
public int filterOrder() {
return 1;
}
}
2)使用Java Bean的方式将过滤器注入到Spring IOC容器。
@Configuration
public class BeanConfiguration {
@Bean
public PreRequestLogFilter preRequestLogFilter() {
return new PreRequestLogFilter();
}
}
3)启动,通过网关访问微服务,看到控制台打印出如下信息
说明自定义的Zuul过滤器执行了。
3、禁用过滤器
配置文件设置zuul.<SimpleClassName>.<filterType>.disable=true即可。例如禁用刚才编写的自定义Zuul过滤器,则为zuul.PreRequestLogFilter.pre.disable=true。
九、Zuul的容错与回退
把微服务关掉,然后通过网关访问,会看到如下页面
Zuul的Hystrix监控的粒度是微服务,而不是某个API;同时也说明,所有经过Zuul的请求,都会被Hystrix保护起来。
下面为Zuul添加一个回退功能
1)编写Zuul的回退类。
实现FallbackProvider接口,重写getRoute和fallbackResponse两个方法。
@Component
public class OrderFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
// 表明是为哪个微服务提供回退
return "consumer-order";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpHeaders getHeaders() {
// headers设定
HttpHeaders headers = new HttpHeaders();
MediaType mediaType = new MediaType("application", "json", Charset.forName("UTF-8"));
headers.setContentType(mediaType);
return headers;
}
@Override
public InputStream getBody() throws IOException {
// 响应体
return new ByteArrayInputStream("订单微服务不可用,请稍后再试!".getBytes());
}
@Override
public String getStatusText() throws IOException {
// 状态文本
return this.getStatusCode().getReasonPhrase();
}
@Override
public HttpStatus getStatusCode() throws IOException {
// fallback时的状态码
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
// 数字类型的状态码
return HttpStatus.OK.value();
}
@Override
public void close() {
// TODO Auto-generated method stub
}
};
}
}
2)重启项目,通过网关访问一个未启动的微服务,将会看到返回如下信息
十、Zuul的高可用
两种方式
1、Zuul客户端也注册到Eureka Server上
2、借助额外的负载均衡器(Nginx、HAProxy、F5等)实现
十一、使用Zuul聚合微服务
许多场景下,外部请求需要查询Zuul后端的多个微服务。如果让客户端直接请求各个微服务(即使使用Zuul进行转发),那么网络开销、消耗时长、复杂度等可能都无法令我们满意。对于这种情形,可使用Zuul聚合微服务请求——客户端只需发送一个请求给Zuul,由Zuul请求各个微服务,并组织好数据给客户端。这样不仅可以简化客户端的开发,还可以提高效率。
比如使用响应式编程,RxJava、Webflux等。