前言
这篇文章我将使用 Spring Cloud 和 Netflix Zuul 来实现一个服务网关,服务网关充当着服务客户端和被调用的服务之间的中介, 服务客户端仅与服务网关管理的单个URL进行交互。有了服务网关, 服务客户端永远不会直接调用单个服务的URL, 而是将所有调用都放到服务网关上。如下图:
由于服务网关位于客户端到各个服务的所有调用之间, 因此它还充当服务调用的中央策略执行点(PEP)。使用集中式PEP意味着横切服务关注点可以在一个地方实现, 无需在每个服务中重复实现。举例来说, 可以在服务网关中实现的横切关注点包括以下几个。
- 静态路由 - 服务网关将所有的服务调用放置在单个 URL 和 API 路由的后面。
- 动态路由 - 服务网关可以检查传入的服务请求, 根据来自传入请求的数据和服务调用者的身份来智能路由。
- 验证和授权 - 由于所有服务调用都经过服务网关进行路由, 所以服务网关是检查服务调用者是否已经进行了验证的绝佳场所。
- 数据收集和日志记录 - 当服务调用通过服务网关时, 可以使用服务网关来收集数据和日志信息。
具体实现
Spring Cloud 集成了 Netflix Zuul, Zuul 是一个服务网关, 它可以很容易的就和 Spring Cloud 集成, 他提供了以下功能:
- 将服务映射到一个URL上 - 使用服务网关构建一个单一的入口点, 所有客户端服务调用都将经过这个入口点。
- 对通过网关的请求进行过滤 - 开发人员可以使用过滤器来对请求进行过滤。
1. 配置
新建 Spring Boot 工程, 并引入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
</dependencies>
为项目的引导类添加 Zuul 注解, Zuul 提供类两种注解 @EnableZuulServer 与 @EnableZuulProxy, 前者不会加载任何 Zuul 反向代理过滤器, 也不会使用 Eureka 进行服务发现, 如果需要与 Eureka 之外的其它服务发现引擎集成时可以使用该注解, 这里我使用后者。
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
对 Zuul 参数进行配置, 这里我禁用所有服务发现的路由, 使用静态的URL手动映射路由, 并为所有URL加上 '/api' 的前缀, 如下图所示:
application.yml
server:
port: 7019
zuul:
# 禁用所有服务发现路由
ignored-services: '*'
# 前缀
prefix: /api
routes:
clienta: /clienta/**
clientb: /clientb/**
eureka:
instance:
# 注册服务的 IP 而不是名称
prefer-ip-address: true
client:
# 注册自身到 eureka
register-with-eureka: true
# 在本地缓存注册表 每隔 30s 自动刷新服务
fetch-registry: true
service-url:
# 配置注册中心地址
defaultZone: http://localhost:7016/eureka/
至此 Zuul 的基础配置就结束了, 访问对应的网关路由就可以调用指定的服务了。
1. 过滤器
Zuul 允许开发人员使用网关内的过滤器构建自定义逻辑。过滤器可用与实现每个服务请求在执行时都会经过的业务逻辑链。
Zuul 支持以下 3 种类型的过滤器:
- 前置过滤器 - 在 Zuul 将实际请求发送到目的地之前被调用。
- 后置过滤器 - 在目标服务被调用并将响应发送回客户端后被调用。
- 路由过滤器 - 用于在调用目标服务之前拦截调用。
下图详细介绍了各个过滤器的作用和位置:
图片来自 Spring Microservices In Action 一书
a. 前置过滤器
TrackingFilter
@Component
@Slf4j
@AllArgsConstructor
public class TrackingFilter extends ZuulFilter {
private static final int FILTER_ORDER = 1;
private static final boolean SHOULD_FILTER = true;
/**
* 告诉 Zuul 该过滤器的类型 (前置 | 路由 | 后置)
*
* @return {@link String}
* @createTime 2021/3/8 22:31
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 定义不同类型过滤器的执行顺序
*
* @return {@link int}
*/
@Override
public int filterOrder() {
return FILTER_ORDER;
}
/**
* 判断该过滤器是否要执行
*
* @return {@link boolean}
*/
@Override
public boolean shouldFilter() {
return SHOULD_FILTER;
}
/**
* 过滤器逻辑代码
*
* @return {@link Object}
* @author nza
* @createTime 2021/3/8 22:40
*/
@Override
public Object run() throws ZuulException {
// 获取当前请求上下文
RequestContext ctx = RequestContext.getCurrentContext();
log.info("TrackingFilter 处理请求: {}", ctx.getRequest().getRequestURI());
return null;
}
}
b. 后置过滤器
ResponseFilter
@Component
@Slf4j
@AllArgsConstructor
public class ResponseFilter extends ZuulFilter {
private static final int FILTER_ORDER = 1;
private static final boolean SHOULD_FILTER = true;
/**
* 后置过滤器
*
* @return {@link String} 过滤器类型
*/
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}
/**
* 定义不同类型过滤器的执行顺序
*
* @return {@link int}
*/
@Override
public int filterOrder() {
return FILTER_ORDER;
}
/**
* 判断该过滤器是否要执行
*
* @return {@link boolean}
*/
@Override
public boolean shouldFilter() {
return SHOULD_FILTER;
}
/**
* 拦截器业务逻辑
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
log.info("ResponseFilter 处理请求响应: {}", ctx.getRequest().getRequestURI());
return null;
}
}
路由过滤器的实现方式大同小异, 这里就不再赘述。
小结
Spring Cloud 使得构建服务网关十分的简单, 只需要简单的进行配置搭建起一个服务网关。Zuul 默认与 Eureka 进行集成, 可以自动的通过注册的服务名来映射路由。Zuul 提供了三种过滤器, 即前置过滤器, 后置过滤器和路由过滤器。关于 Zuul 的更多配置信息, 可以访问官网查看。