-
关键原理解析
基本原理
Spring Cloud Route核心可以概括为Gateway过滤器框架和Route定义和解析两块内容。
DefaultFilter、GatewayFilter、GlobalFilter
三种过滤器的区别及执行顺序
SpringCloud Gateway中的提到的过滤器包括三种类型:
DefaultFilter:默认过滤器,接口GatewayFilter的实现类,配置后所有路由都执行的过滤器。
GatewayFilter: 网关过滤器,接口GatewayFilter的实现类,特定路由配置的过滤器。
GlobalFilter:全局过滤器,接口GlobalFilter的实现类。全局过滤器,无需配置。
三种过滤器在网关路由处理过程中其实都是统一加载在同一个过滤器链中,按照Order对三种过滤器进行排序,按照order值从小到大的顺序执行,如果order值相同,则按照加载顺序DefaultFilter->GatewayFilter->GlobalFilter。
DefaultFilter和GatewayFilter 在请求过来路由查找过程中加载,GlobalFilter则在FilteringWebHandler处理请求时加载。
DefaultFilter和gatewayFilter的转载过程
装载过程在RouteDefinitionRouteLocator中实现,该类负责根据路由定义加载路由信息。
private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) { List<GatewayFilter> filters = new ArrayList(); if (!this.gatewayProperties.getDefaultFilters().isEmpty()) { filters.addAll(this.loadGatewayFilters("defaultFilters", this.gatewayProperties.getDefaultFilters())); } if (!routeDefinition.getFilters().isEmpty()) { filters.addAll(this.loadGatewayFilters(routeDefinition.getId(), routeDefinition.getFilters())); } AnnotationAwareOrderComparator.sort(filters); return filters; } //loadGatewayFilters函数中将gatewayFilter进行Order赋值 for(int i = 0; i < filters.size(); ++i) { GatewayFilter gatewayFilter = (GatewayFilter)filters.get(i); if (gatewayFilter instanceof Ordered) { ordered.add(gatewayFilter); } else { ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1)); } } |
GlobalFilter加载过程
GlobalFilter由FilteringWebHandler在初始化时加载所有对象, 在FilteringWebHandler处理请求时将GlobalFilter和该请求相关的所有Gateway一起进行排序执行。
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); combined.addAll(gatewayFilters); //gatewayFilter和globalFilter加入统一集合进行排序 AnnotationAwareOrderComparator.sort(combined); if (logger.isDebugEnabled()) { logger.debug("Sorted gatewayFilterFactories: " + combined); } //调用过滤器链处理请求 return (new FilteringWebHandler.DefaultGatewayFilterChain(combined)).filter(exchange); } |
WebFilter
org.springframework.web.server.WebFilter 是Spring-web框架定义的过滤器, 是在进入webHandler之前进行调用, 如果在网关项目中实现相关接口将在网关定义的三种Filter之前执行
Filter和@WebFilter
Javax.sevlet.Filter接口和tomcat的@WebFilter注解都是实现Servlet容器的Filter接口,这类型接口作用再Servlet上, 而Spring-web 则是建立在DispatcherServlet之上。Filter是最原始,最早执行的Filter。
路由的获取和解析过程
路由解析
Spring Web请求处理从Dispatcher入口后的两个核心组件是HandlerMapping和HandlerAdapter,其中HandlerMapping负责映射请求URL到对应的HandlerAdapter进行处理。
对于SpringCloud Gateway来说这两个组件的具体实现类是:RouterFunctionMapping和
FilteringWebHandler. RouterFunctionMapping路由解析相关源码如下:
@Override protected Mono<?> getHandlerInternal(ServerWebExchange exchange) { …… //查找路由 return lookupRoute(exchange) // .log("route-predicate-handler-mapping", Level.FINER) //name this .flatMap((Function<Route, Mono<?>>) r -> { exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); //将路由设置到ServerWebExchange的属性数据中 exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); //返回FilteringWebHandler return Mono.just(webHandler); }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { …… }))); } |
protected Mono<Route> lookupRoute(ServerWebExchange exchange) { return this.routeLocator.getRoutes() // individually filter routes so that filterWhen error delaying is not a // problem .concatMap(route -> Mono.just(route).filterWhen(r -> { // add the current route we are testing exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId()); //通过Predicate进行匹配 return r.getPredicate().apply(exchange); }) …… } |
最终由Route定义中的Predicate来对请求进行匹配。 相关的Route Predicate Factories包括路径断言,Cookie断言等各种断言。(可参考官方文档)
路由的加载
路由的加载的核心组件是包括RouteLocator和RouteDefinitionLocator,可以使用Gateway中自带的实现来满足微服务、手工配置、动态配置等应用,也可以通过扩展或操作相关组件实现自己的动态路由定义。下面介绍路由相关的核心定义和源码。
路由信息Route
Route对象是在Gateway处理流程中体现一个具体的路由配置。主要属性如下:
Id:唯一标识
Uri:目的URL
order;排序ORDER
predicate;路由断言配置,用于路由匹配
gatewayFilters:路由过滤器配置
metadata:路由元数据
其中Predicate、gatewayFilter、metadata也是gateway的相关核心概念。
RouteLocator
RouteLocator: Route定位器,getRoutes获取所有的路由。Gateway RouteLocator的默认实现是RouteDefinitionRouteLocator,顾名思义是从RouteDefinition(路由定义)中解析出路由信息,依赖RouteDifinitionLocator类来获取所有路由定义。
public Flux<Route> getRoutes() { //依赖RouteDefinitionLcator获取路由定义。 return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute).map((route) -> { …… return route; }); } |
RouteDefinitionLocator
RouteDefinitionLocator: 路由定义加载器,负责加载网关的RouteDefinition。有以下几种路由定义加载器实现,根据配置和实际需要进行使用。
DiscoveryClientRouteDefinitionLocator
当spring.cloud.gateway.discovery.locator.enabled = true(默认为关闭)时,springcloud gateway通过DiscoveryClientRouteDefinitionLocator 获取所有服务产生基于服务注册中心的路由配置。
关键源码如下:
//通过DiscoveryClient获取的所有服务实例遍历转换为路由定义 return serviceInstances.filter(instances -> !instances.isEmpty()).flatMap(Flux::fromIterable) .filter(includePredicate).collectMap(ServiceInstance::getServiceId) // remove duplicates .flatMapMany(map -> Flux.fromIterable(map.values())).map(instance -> { //转换为路由定义 RouteDefinition routeDefinition = buildRouteDefinition(urlExpr, instance); final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties); //加载Discovery属性中配置的Predicate,从源码中可以看到配置的Predicate和Filter是所有服务 //路由共同使用的 for (PredicateDefinition original : this.properties.getPredicates()) { PredicateDefinition predicate = new PredicateDefinition(); predicate.setName(original.getName()); for (Map.Entry<String, String> entry : original.getArgs().entrySet()) { String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry); predicate.addArg(entry.getKey(), value); } routeDefinition.getPredicates().add(predicate); } //加载配置的过滤器定义 for (FilterDefinition original : this.properties.getFilters()) { FilterDefinition filter = new FilterDefinition(); filter.setName(original.getName()); for (Map.Entry<String, String> entry : original.getArgs().entrySet()) { String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry); filter.addArg(entry.getKey(), value); } routeDefinition.getFilters().add(filter); } return routeDefinition; }); |
spring.cloud.gateway.discovery.locator路径下可以配置过滤器和Predicated:
该配置是针对所有动态服务共有的,相关配置类是(DiscoveryLocatorProperties)
RedisRouteDefinitionRepository/ InMemoryRouteDefinitionRepository
当spring.cloud.gateway.redis-route-definition-repository.enabled=true是实例化RedisRouteDefinitionRepository,当系统没有定义RouteDefinition接口时,实例化InMemoryRouteDefinitionRepository。两个实例实现了RouteDefinitionRepository接口,提供了RouteDefinition的管理能力的示例,可以直接使用或参考实现自身定义的RouteDefinitionRoute实现对RouteDefinition的管理。
InMemoryRouteDefinitionRepository使用synchronizedMap来保存RouteDefinition:
private final Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());
RefreshRoutesEvent
利用ApplicationEvent机制,提供在网关中传递路由刷新的事件,Gateway框架中CorsGatewayFilterApplicationListener和RouteDefinitionMetrics对该事件进行了监听。前者对新的路由信息新增全局的cors配置,后者则是对路由定义的一个统计测量
注:ApplicationEvent机制的使用设计到两个接口:ApplicationListener<RefreshRoutesEvent>监听事件接口,ApplicationEventPublisherAware事件发布器注入接口。 通过ApplicationEventPublisherAware获取到ApplicationEventPublisher发发布的相关事件会通知到对应事件的Listener。
动态路由配置和配置文件静态配置的关系
- RouteDefinitionLocator的Primary Bean是CompositeRouteDefinitionLocator
@Bean @Primary public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) { return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators)); } |
构建CompositeRouteDefinitionLocator的RouteDefinitionLocator列表可能包括
- PropertiesRouteDefinitionLocator: 读取配置文件生成的路由定义
- DiscoveryClientRouteDefinitionLocator:
当 spring.cloud.gateway.discovery.locator.enabled = true 时从服务注册中心获取的服务路由定义。
- RedisRouteDefinitionRepository:当spring.cloud.gateway.redis-route-definition-repository.enabled = true时。可以通过RedisRouteDefinitionRepository接口操作存储在Redis中的Route定义
- InMemoryRouteDefinitionRepository: 内存存储的Route定义。 默认配置中是不会创建该Bean。 但定制开发中可以使用该类进行开发。
CompositeRouteDefinitionLocator汇集所有的RouteDefinitionLocator加载的RouteDefinition。
网关功能实现解析
了解了Gateway的路由定义及工作原理之后,Gateway提供的所有功能都是基于过滤器框架上实现的。
路由跳转
路由跳转是Gateway的最基本功能,同样是通过过滤器框架来实现,涉及到的过滤器如下,一下的过滤器都是GlobalFilter,无需用户配置:
Filter | order | 说明 |
RouteToRequestUrlFilter(GlobalFilter) | 10000 | 路由解析,根据路由定义,将路由信息解析成目的地址 |
ReactiveLoadBalancerClientFilter | 10500 | 负载均衡,使用了spring-cloud-commons中的spring-cloud-balancer模块 |
WebsocketRoutingFilter | 2147483646 |
|
ForwardRoutingFilter | 2147483647 | |
NettyRoutingFilter | 2147483647 |
负载均衡
最新的网关使用spring-cloud-balancer模块来进行负载均衡,这里先看一下2.0.2版本中的LoadBalancerClientFilter实现,该实现使用RibbonLoadBalancer实现的一个循环使用所有可用实例的负载均衡器。
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //获取RouteToRequestFilter中解析度目标地址 URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR); //非负载均衡则直接略过 if (url == null || !"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix)) { return chain.filter(exchange); } else {
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url); log.trace("LoadBalancerClientFilter url before: " + url); //负载均衡实现方法choose ServiceInstance instance = this.choose(exchange); if (instance == null) { throw new NotFoundException("Unable to find instance for " + url.getHost()); } else { URI uri = exchange.getRequest().getURI(); String overrideScheme = null; if (schemePrefix != null) { overrideScheme = url.getScheme(); } URI requestUrl = this.loadBalancer.reconstructURI( new LoadBalancerClientFilter.DelegatingServiceInstance(instance, overrideScheme), uri); log.trace("LoadBalancerClientFilter url chosen: " + requestUrl); exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl); return chain.filter(exchange); } } } |
protected ServiceInstance choose(ServerWebExchange exchange) { return this.loadBalancer.choose( ((URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR)).getHost()); } |
最终由以下两个关键接口进行实际的服务器加载和选择: com.netflix.loadbalancer.IRule: 负责从可用服务中选中服务 com.netflix.loadbalancer.ServerList:加载所有可用服务 ServerList中Eureka集成的实现在:spring-cloud-netflix-eureka-client包中的最终的DiscoveryEnabledNIWSServerList类,最终调用EurekaClient和Eureka集群同步可用服务。 IRule的默认实现是:com.netflix.loadbalancer. RoundRobinRule 核心逻辑: int nextServerIndex = this.incrementAndGetModulo(serverCount); server = (Server)allServers.get(nextServerIndex); |
限流
由GatewayFilter:RequestRateLimiterGatewayFilterFactory实现。
核心接口:
org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
org.springframework.cloud.gateway.filter.ratelimit. RateLimiter
限流实现:
org.springframework.cloud.gateway.filter.ratelimit. RedisRateLimiter
List<String> keys = getKeys(id); List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "", Instant.now().getEpochSecond() + "", "1"); Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs); return flux.onErrorResume((throwable) -> { return Flux.just(Arrays.asList(1L, -1L)); }).reduce(new ArrayList(), (longs, l) -> { longs.addAll(l); return longs; }).map((results) -> { boolean allowed = (Long)results.get(0) == 1L; Long tokensLeft = (Long)results.get(1); Response response = new Response(allowed, this.getHeaders(routeConfig, tokensLeft)); return response; }); |
核心原理有Redis Lua脚本来进行限流算法。Redis Lua实现Redis事务性。算法逻辑如下: 由tokenkey保存当前token剩余数量, timestamp key可以保存上次刷新时间, 通过时间窗内限流配置,计算可用tokens,当有剩余token时减去本次请求占用的token数量并更新的Redis中
KeyResolver:定制限流标识,从ServerExchange相关信息确定这次限流的唯一标识。每一个标识可以认为是一个唯一的限流器。
Google guava RateLimiter
有关限流功能Google guava提供的限流工具RateLimiter是一个基于令牌桶算法实现的限流器,常用于控制网站的QPS。与Semaphore不同,Semaphore控制的是某一时刻的访问量,RateLimiter控制的是某一时间间隔的访问量。
SmoothBursty和SmoothWarmingUp是定义在SmoothRateLimiter里的两个静态内部类,是SmoothRateLimiter的真正实现类。
RateLimiter的一个重要设计原则——透支未来令牌
如果说令牌池中的令牌数量为x,某个请求需要获取的令牌数量是y,只要x>0,即使y>x,该请求也能立即获取令牌成功。但是当前请求会对下一个请求产生影响,即会透支未来的令牌,使得下一个请求需要等待额外的时间。
2. 实际项目中网关需要承担的任务
1. 集成企业统一登录平台:获取用户信息
2. 用户及权限校验:如角色权限、各类黑白名单。
3. 通用请求过滤处理功能: 如日记、数据脱敏、metric统计等。
4.生成及校验JWT Token
其中第1、2点是基本每个项目都必须实现的,其他相关通用请求处理功能均可以再网关组件实现。
3. 补充 SpringCloud Gateway基本介绍
本章内容主要整理自官方文档,为SpringCloud的基本概念介绍和配置示例,如熟悉可忽略。
基本概念
路由(route)
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
断言(predicates)
Java8中的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
过滤器(Filter)
SpringCloud Gateway中的filter分为Gateway FilIer和Global Filter。Filter可以对请求和响应进行处理。
Route Predicate
Predicate对应于路由配置中的断言, 示例如下:
路径断言
指定请求路径匹配列表。
predicates:
- Path=/red/{segment},/blue/{segment}
请求参数断言
指定一个参数和可选的正则(没有的话只判断是否存在)
predicates:
- Query=green
GatewayFilter 工厂
GatewayFilter工厂对应于配置在路由上的过滤器。如下面例子:
添加请求头The AddRequestHeader GatewayFilter Factory
请求中增加请求头
filters:
- AddRequestHeader=X-Request-red, blue
predicates:
- Path=/red/{segment}
filters:
- AddRequestHeader=X-Request-Red, Blue-{segment}
定义默认过滤器
spring.cloud.gateway.default-filters
spring: cloud: gateway: default-filters: - AddResponseHeader=X-Response-Default-Red, Default-Blue - PrefixPath=/httpbin |
全局过滤器 GlobalFilter
实现:GlobalFilter, Ordered 接口。全局过滤器和gateway filter最终都会汇集到一个过滤器链上按照ordered接口返回的顺序执行,区别在GlobalFilter无需配置。所有请求路由都会经过改类型过滤器。
比如:
负载均衡ReactiveLoadBalancerClientFilter
对于uri schema为lb://会进行负载均衡。
routes:
- id: myRoute
uri: lb://service
HttpHeadersFilter
HttpHeadersFilter 接口由NettyRoutingFilter调用,接口返回的header将添加到响应头中。
路由元数据
可以使用元数据为每个路由配置附加数据,在处理过程中使用。
spring: cloud: gateway: routes: - id: route_with_metadata uri: https://example.org metadata: optionName: "OptionValue" compositeObject: name: "value" iAmNumber: 1 |
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); // get all metadata properties route.getMetadata(); // get a single metadata property route.getMetadata(someKey); |
Http超时配置
全局
connect-timeout必须以毫秒为单位指定。
response-timeout必须指定为 java.time.Duration
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s
每个路由
通过元数据配置。
metadata:
response-timeout: 200
connect-timeout: 200
Reactor Netty 访问日志
要启用 Reactor Netty 访问日志,请设置
-Dreactor.netty.http.server.accessLogEnabled=true.
<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
<appender-ref ref="async"/>
</logger>
CORS配置
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://docs.spring.io"
allowedMethods:
- GET