1.Zuul简介
API网关为微服务架构中的服务提供了统一的访问入口,客户端通过API网关访问相关服务。API网关的定义类似于设计模式中的门面模式,它相当于整个微服务架构中的门面,所有客户端的访问都通过它来进行路由及过滤。它实现了请求路由、负载均衡、校验过滤、服务容错、服务聚合等功能。
2.创建Spring Cloud Zuul工程
2.1在pom中添加相关依赖
<?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">
<parent>
<artifactId>spring-cloud-demo</artifactId>
<groupId>com.hxmec</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-zuul</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
</project>
2.2 增加配置文件
bootstrap.yml配置如下
server:
port: 9200
spring:
application:
name: zuul-proxy
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8888/eureka/
application.yml配置如下:
zuul:
routes: #给服务配置路由
eureka-client-provider:
path: /provider/**
eureka-client-consumer:
path: /consumer/**
2.3 创建启动类ZuulApplication
启动类代码如下:
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
2.4启动程序,进行测试
启动之前文中创建的spring-cloud-eureka-server,spring-cloud-eureka-client-provider,spring-cloud-eureka-client-consumer,以及网关Zuul工程。
测试地址如下:
3.Zuul常用功能
3.1 配置路由规则
我们可以通过修改application.yml中的配置来配置路由规则,这里我们将匹配/provider/** 的请求路由到eureka-client-provider服务上去,匹配/consumer/**的请求路由到eureka-client-consumer上去。
具体配置如下:
zuul:
#关闭默认路由配置
ignored-services: eureka-client-provider,eureka-client-consumer
routes: #给服务配置路由
eureka-client-provider:
path: /provider/**
eureka-client-consumer:
path: /consumer/**
访问http://localhost:9200/provider/demo/hello 路由到eureka-client-provider上; 访问http://localhost:9200/consumer/test/callHello 同样可以路由到了eureka-client-consumer上了。
3.2 配置访问前缀
配置如下
zuul:
#配置统一访问前缀
prefix: /zuul
routes: #给服务配置路由
eureka-client-provider:
path: /provider/**
eureka-client-consumer:
path: /consumer/**
所有的请求统一前缀需要加上/zuul 访问http://localhost:9200/zuul/provider/demo/hello 路由到eureka-client-provider上;
3.3 Header过滤及重定向添加Host
- Zuul在请求路由时,默认会过滤掉一些敏感的头信息,以下配置可以防止路由时的Cookie及Authorization的丢失:
zuul:
sensitive-headers: Cookie,Set-Cookie,Authorization #配置过滤敏感的请求头信息,设置为空就不会过滤
- Zuul在请求路由时,不会设置最初的host头信息,以下配置可以解决:
zuul:
add-host-header: true #设置为true重定向是会添加host请求头
3.4 查看路由信息
可以通过SpringBoot Actuator来查看Zuul中的路由信息。
- 在pom.xml中添加相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 增加开启查看路由的端点配置
#路由的端点
management:
endpoints:
web:
exposure:
include: 'routes'
-
通过访问http://localhost:9200/actuator/routes 查看简单路由信息
-
通过访问http://localhost:9200/actuator/routes/details 查看详细路由信息
4.过滤器
路由与过滤是Zuul的两大核心功能,路由功能负责将外部请求转发到具体的服务实例上去,是实现统一访问入口的基础,过滤功能负责对请求过程进行额外的处理,是请求校验过滤及服务聚合的基础。
4.1 过滤器类型
zuul过滤器有如下几种:
- pre:在请求被路由到目标服务前执行,比如权限校验、打印日志等功能;
- routing:在请求被路由到目标服务时执行,这是使用Apache HttpClient或Netflix Ribbon构建和发送原始HTTP请求的地方;
- post:在请求被路由到目标服务后执行,比如给目标服务的响应添加头信息,收集统计数据等功能;
- error:请求在其他阶段发生错误时执行。
4.2 过滤器的生命周期
Filter的生命周期有4个,分别是 “PRE”、“ROUTING”、“POST” 和“ERROR”,整个生命周期可以用下图来表示
Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。
- PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。
4.3 自定义过滤器
在Zuul网关中,我们需要自定义一个类来继承ZuulFilter抽象类并实现4个相应的抽象方法即可。
如下示例中,我们自定义一个过滤器AccessTokenFilter验证请求有没有accessToken参数:
@Component
@Slf4j
public class AccessTokenFilter extends ZuulFilter {
/**
* 过滤器类型 pre 表示在 请求之前进行拦截
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器的执行顺序。当请求在一个阶段的时候存在多个多个过滤器时,需要根据该方法的返回值依次执行
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 判断过滤器是否生效
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
// 获取上下文
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String accessToken = request.getParameter("accessToken");
if (StringUtils.isEmpty(accessToken)) {
//setSendZuulResponse(false)令zuul过滤该请求,不进行路由
currentContext.setSendZuulResponse(false);
//设置返回的错误码
currentContext.setResponseStatusCode(401);
currentContext.setResponseBody("AccessToken is null");
return null;
}
log.info("获取到AccessToken为{}",accessToken);
// 否则正常执行业务逻辑.....
return null;
}
}
重启zuul服务后,验证是否有效
在上面实现的过滤器代码中,我们通过继承ZuulFilter抽象类并重写了下面的四个方法来实现自定义的过滤器。这四个方法分别定义了:
- filterType():过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。
- filterOrder():过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。通过数字指定,数字越大,优先级越低。
- shouldFilter():判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来指定过滤器的有效范围。
- run():过滤器的具体逻辑。这里我们通过currentContext.setSendZuulResponse(false)令 Zuul 过滤该请求,不对其进行路由,然后通过currentContext.setResponseStatusCode(401)设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如通过currentContext.setResponseBody(body)对返回 body 内容进行编辑等。
4.4 核心过滤器
在 Zuul 中提供了一些默认的 Filter
| 类型 | 顺序 | 过滤器 | 功能 |
|---|---|---|---|
| pre | -3 | ServletDetectionFilter | 标记处理Servlet的类型 |
| pre | -2 | Servlet30WrapperFilter | 包装HttpServletRequest请求 |
| pre | -1 | FormBodyWrapperFilter | 包装请求体 |
| pre | 5 | PreDecorationFilter | 对当前请求进行预处理以便执行后续操作。 |
| pre | 1 | DebugFilter | 标记调试标志 |
| route | 10 | RibbonRoutingFilter | serviceId请求转发 |
| route | 500 | SendForwardFilter | forward请求转发 |
| route | 100 | SimpleHostRoutingFilter | url请求转发 |
| post | 900 | LocationRewriteFilter | 将Location header重写为Zuul的URL |
| post | 0 | SendErrorFilter | 处理有错误的请求响应 |
| post | 1000 | SendResponseFilter | 处理正常的请求响应 |
4.5 Ribbon和Hystrix的支持
由于Zuul已经集成了Ribbon和Hystrix,所以Zuul本身就具有负载均衡和服务容错能力,我们可以通过Ribbon和Hystrix的配置来配置Zuul中的相应功能。
- 可以使用Hystrix的配置来设置路由转发时HystrixCommand的执行超时时间:
hystrix:
command: #用于控制HystrixCommand的行为
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000 #配置HystrixCommand执行的超时时间,执行超过该时间会进行服务降级处理
- 可以使用Ribbon的配置来设置路由转发时请求连接及处理的超时时间:
ribbon: #全局配置
ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
4.6 使用 Zuul 进行限流
添加相关依赖:
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>${latest-version}</version>
</dependency>
本文中使用redis作为数据存储, 使用其他存储参考:github.com/marcosbarbe…
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring-cloud-zuul-ratelimit 是和zuul整合提供分布式限流策略的扩展,只需在yml中配置几行配置,就可使应用支持限流:
ratelimit:
enabled: true
repository: REDIS #使用redis存储,一定要大写!
policies:
eureka-client-provider: #针对上面那个服务的限流
limit: 100 #每秒多少个请求
quota: 20 #quota 单位时间内允许访问的总时间(统计每次请求的时间综合)
refreshInterval: 60 #刷新时间窗口的时间,默认值 (秒)
type:
- ORIGIN #这里一定要大写,类型说明:URL通过请求路径区分,ORIGIN通过客户端IP地址区分,USER是通过登录用户名进行区分,也包括匿名用户
ratelimit 支持的限流粒度:
- 服务粒度 (默认配置,当前服务模块的限流控制)
- 用户粒度 (详细说明,见文末总结
- ORIGIN粒度 (用户请求的origin作为粒度控制)
- 接口粒度 (请求接口的地址作为粒度控制)
- 以上粒度自由组合,又可以支持多种情况
- 如果还不够,自定义RateLimitKeyGenerator实现。
支持的存储方式:
- InMemoryRateLimiter - 使用 ConcurrentHashMap作为数据存储
- ConsulRateLimiter - 使用 Consul 作为数据存储
- RedisRateLimiter - 使用 Redis 作为数据存储
- SpringDataRateLimiter - 使用 数据库 作为数据存储