Gateway( 网关),顾名思义,是出现在系统边界上的一个面向API或应用服务的、串行集中式的强管控服务,这里我们讨论的边界可以基于企业IT系统的边界,当然,可以理解为企业级应用防火墙,其目标主要起到隔离外部访问与内部系统交互的作用。在微服务概念的流行之前,网关就已经诞生了,在面向SOA体系中已经成熟,然而,随着微服务体系的快速发展,更进一步将Gateway推向更高的浪口。与其说网关催生了微服务体系,不如说微服务体系拥抱了网关。
随着微服务架构概念的提出,API 网关成为了微服务架构的一个标配组件,无时无刻在我们的应用系统架构中起着举足轻重的作用,首先,我们来了解下基于Spring Cloud微服务体系网关的架构图,具体如下所示:
当我们谈论起网关,这玩意到底有什么用?具备哪些功能?具体我们可以参考以下模型图,具体:
如上图所示:作为网关,其该具备的最基本的四大功能为:统一接入,流量管控,协议适配转发以及安全防护等。
基于上述,我们分享了网关的历史以及其具备的基本功能,现在我们了解下在不同语言体系环境下所采用的网关技术,目前市面上或者业务场景落地解决方案中,常见的开源网关大致上按照语言分类有如下几类,具体如下图所示:
若按照使用范围、成熟度以及落地场景等来划分,目前主流网关技术应用涉及以下4 种:OpenResty、Kong、Zuul/Zuul 2、Spring Cloud Gateway,此外,随着Go语言在微服务领域的快速崛起以及应用,fagongzi API 网关最近也获得不少关注。因作者当前公司主要技术栈为Java,故本文重点以Spring Cloud Gateway网关为主,解析其基本原理以及在业务中的应用。
在Spring 的早期框架中,我们基本上用的是第一代Zuul,随着Spring 5的出现,Spring Cloud 开始完善其生态,引入多种不同的组件以支撑其在微服务体系领域中的地位。因此,Spring Cloud Gateway应运而生。
Spring Cloud Gateway 是Spring Cloud 生态全新项目,其基于 Spring 5.0、Spring Boot2.0 和 Project Reactor 等技术开发的网关组件,旨在为微服务架构提供简单、有效和统一的 API 路由管理方式,同时提供安全性、监控/度量和限流,Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul。于是,大家肯定在想,这哥们疯了,Zuul都2代了,还搞Gateway ?有意思吗?答案:当然。主要基于以下几点:
- 提供更简单、高效的 API 网关。
- Zuul 1.x 采用 Thread per connection 方式处理请求(每个请求一个线程进行处理),
一旦服务响应慢,线程会被阻塞不释放,存在性能瓶颈。
- 虽然 Zuul 2.x 是适合高并发的版本,但是在 Zuul 2.x 开源前 Spring 团队启动了
Gateway。
现在,我们来看下Spring Cloud Gateway的基本配置,具体如下所示,
pom.xml中的Maven依赖,具体可参考:
代码语言:javascript复制<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
application.yml 配置,具体可参考:
代码语言:javascript复制spring:
cloud:
gateway:
routes:
- id: ${serviceId}
uri: lb://${serviceName} # http://localhost:8080/
predicates:
- Path= /api/**
filters:
- StripPrefix=1
- id - 路由唯一 ID。
- uri - 目标服务地址,支持普通 URL 和 lb://${服务名称}(表示从注册中心获取服务的
地址)。
- predicates - 路由条件,匹配请求 URL 判断是否执行该路由。
- filters - 过滤规则,包括 pre 和 post 过滤等相关规则。
除此之外,我们还可以通过Java Bean来进行配置,具体如下所示:
代码语言:javascript复制@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route2", r -> r.path("/user/getByUsername")
.uri("http://localhost:8201/user/getByUsername"))
.build();
}
}
现在,我们继续了解Spring Cloud Gateway 的基本原理,我们先看下其架构图,具体如下所示:
基于上述拓扑,我们可以看到:Spring Cloud Gateway 依赖 Spring Boot 和 Spring Webflux 提供的 Netty runtime,启动时 Netty Server 监听指定端口,接受客户端请求。请求处理过程如下图,几个重要组成部分:
1、路由(Route),网关的基本组件,由 ID、目标 URI、Predicate 集合和 Filter 集合组成。
2、Predicate,Java 8 引入的函数式接口,提供断言(assert)功能,可以匹配 HTTP 请求中的任何内容,如果 Predicate 集合判断结果是 true,表示请求会由该 Route 进行转发。
3、Filter,为请求提供前置(pre)和后置(post)过滤。
现在,我们来了解下其工作流原理,基本的处理流程及架构图如下:
基于上述处理图,我们可以得知:
1、Gateway接受客户端请求;
2、网关处理程序映射确定请求与路由匹配,匹配成功则将其发送到网关Web处理程序;
3、Web处理程序处理程序通过特定于请求的过滤器链运行请求:请求经过 Filter 过滤器链,执行 pre 处理逻辑,如修改请求头信息等;发出代理请求,请求被转发至下游服务并返回响应。
4、响应经过 Filter 过滤器链,执行 post 处理逻辑。
5、向客户端响应应答。
再此,我们看下Spring Cloud Gateway源码实现,以便能够更清晰地去熟悉其内部具体实现细节,具体如下所示:
网关初始化,启动注解:@GatewayAutoConfiguration(spring-cloud-gateway
-core#org.springframework.cloud.gateway.config),源码如下:
代码语言:javascript复制@Service
public class XXXGatewayFilterFactory extends AbstractGatewayFilterFactory<XXXConfig> {
@Override
public GatewayFilter apply(final XXXConfig config) {
return (((exchange, chain) -> {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// ...
}));
}));
}
@Data
public static class XXXConfig {
private String name;
}
}
Spring Cloud Gateway 基于 Spring WebFlux 实现,@GatewayClassPathWarningAutoConfiguration 注解用于用于检查项目是否正确导入 spring-boot-starter-webflux 依赖,而不是错误导入 spring-boot-starter-web 依赖。
@GatewayLoadBalancerClientAutoConfiguration 初始化 LoadBalancerClientFilter 实现负载均衡。
@GatewayAutoConfiguration 中实现多个核心 Bean 的初始化。Gateway 的配置参数参考 GatewayProperties.class。其基本组件简要源码实现如下:
Route,作为Gateway 中最基本的组件之一,表示一个具体的路由信息载体。源码如下所示:
代码语言:javascript复制public class Route implements Ordered {
private final String id;
private final URI uri; // 路由指向的目的地 uri
private final int order; // 多个 Route 之间的排序,数值越小排序越靠前
private final AsyncPredicate<ServerWebExchange> predicate; // 匹配 Route 的条件
private final List<GatewayFilter> gatewayFilters; // 应用于 Route 的过滤器
}
Predicate 组件用于匹配请求和 Route,其总共定义3 种逻辑操作方法:and/or/negate。源码如下所示:
代码语言:javascript复制public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
default AsyncPredicate<T> and(AsyncPredicate<? super T> other) {
// 两个 Predicate 同时满足
}
default AsyncPredicate<T> negate() {
// 对 Predicate 匹配结果取反
}
default AsyncPredicate<T> or(AsyncPredicate<? super T> other) {
// 两个 Predicate 只需满足其一
}
}
Filter 组件功能主要用于请求代理之前或之后,最终通过 filter chain 形成链式进行调用,每个filter处理完pre filter逻辑后委派给 filter chain,其再委派给下一个filter。具体源码如下:
代码语言:javascript复制public interface GatewayFilter extends ShortcutConfigurable {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
public interface GatewayFilterChain {
Mono<Void> filter(ServerWebExchange exchange);
}
接下来,我们再看下RouteLocator,其主要用于获取 Route,通过 RouteDefinitionLocator 获取到 RouteDefinition,然后转换成Route,源码如下所示:
代码语言:javascript复制public interface RouteLocator {
Flux<Route> getRoutes();
}
public class RouteDefinitionRouteLocator implements RouteLocator,
BeanFactoryAware, ApplicationEventPublisherAware {
private final RouteDefinitionLocator routeDefinitionLocator;
// RoutePredicateFactory 列表
private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap();
// GatewayFilterFactory 列表
private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap();
public Flux<Route> getRoutes() {
return this.routeDefinitionLocator.getRouteDefinitions()
.map(this::convertToRoute)
.map((route) -> {
if (this.logger.isDebugEnabled()) {
this.logger.debug("RouteDefinition matched: " route.getId());
}
return route;
});
}
private Route convertToRoute(RouteDefinition routeDefinition) {
AsyncPredicate<ServerWebExchange> predicate = this.combinePredicates(routeDefinition);
List<GatewayFilter> gatewayFilters = this.getFilters(routeDefinition);
return ((AsyncBuilder)Route
.async(routeDefinition)
.asyncPredicate(predicate)
.replaceFilters(gatewayFilters))
.build();
}
}
路由匹配部分,Spring WebFlux 的访问入口 org.springframework.web.reactive.DispatcherHandler(对应 MVC 中的 DispatcherServlet),具体源码可参考如下:
代码语言:javascript复制public class DispatcherHandler implements WebHandler, ApplicationContextAware {
private List<HandlerMapping> handlerMappings;
private List<HandlerAdapter> handlerAdapters;
public Mono<Void> handle(ServerWebExchange exchange) {
// 顺序使用 handlerMappings 获得对应的 WebHandler,invoke 执行
return Flux.fromIterable(this.handlerMappings)
// RoutePredicateHandlerMapping
.concatMap((mapping) -> { return mapping.getHandler(exchange); })
.next()
.switchIfEmpty(this.createNotFoundError())
// SimpleHandlerAdapter
.flatMap((handler) -> { return this.invokeHandler(exchange, handler); })
.flatMap((result) -> { return this.handleResult(exchange, result); });
}
}
RoutePredicateHandlerMapping 匹配路由,源码如下:
代码语言:javascript复制public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
private final FilteringWebHandler webHandler;
private final RouteLocator routeLocator;
// mapping.getHandler(exchange); 会调用至此
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
return this.lookupRoute(exchange) // 匹配 Route
.flatMap((r) -> {
// ...
return Mono.just(this.webHandler);
})
.switchIfEmpty(/**未匹配到 Route**/);
}
}
最后,我们在了解下Filter chain,SimpleHandlerAdapter 循环执行 WebHandler,我们以 FilteringWebHandler 为例,创建 GatewayFilterChain 处理请求,具体源码如下:
代码语言:javascript复制public class SimpleHandlerAdapter implements HandlerAdapter {
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
WebHandler webHandler = (WebHandler)handler;
Mono<Void> mono = webHandler.handle(exchange);
return mono.then(Mono.empty());
}
}
public class FilteringWebHandler implements WebHandler {
public Mono<Void> handle(ServerWebExchange exchange) {
Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
List<GatewayFilter> gatewayFilters = route.getFilters();
List<GatewayFilter> combined = new ArrayList(this.globalFilters);
// 合并 GlobalFilter 和 GatewayFilter,整体排序
combined.addAll(gatewayFilters);
AnnotationAwareOrderComparator.sort(combined);
// 创建 GatewayFilterChain 处理请求
return (new FilteringWebHandler.DefaultGatewayFilterChain(combined))
.filter(exchange);
}
}
至此,Spring Cloud Gateway基本解析到此为止,大家有什么问题或者建议,欢迎随时留言沟通。