虽然在服务网关有了zuul(在这里是zuul1),其自己仍是基于servlet实现的,换言之仍是同步阻塞方式的实现。就其自己来说它的最根本弊端也是再此。而非阻塞带来的好处不言而喻,高效利用线程资源进而提升吞吐量,基于此Spring率先拿出针对于web的杀手锏,对,就是webflux。而Gateway自己就是基于webflux基础之上实现的。毕竟spring推出的技术,固然要得以推广嘛。不过就国内的软件公司而言为了稳定而选择保守,所以就这项技术的广度来讲我自己仍是在观望中。html
添加依赖:java
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
这里请注意,springcloud-gateway是基于netty运行的环境,在servlet容器环境或者把它构建为war包运行的话是不容许的,所以在项目当中没有必要添加spring-boot-starter-web。在gateway当中有三个重要的元素他们分别是:react
咱们编辑application.yaml,定义以下配置:web
spring: application: name: gateway cloud: gateway: routes: - id: before_route uri: http://www.baidu.com predicates: - Path=/baidu server: port: 8088
此时当咱们访问路径中包含/baidu的,gateway将会帮咱们转发至百度页面spring
在这里我贴上官网的一张图:mvc
在这里我想结合源代码来讲明其流程,这里面有个关键的类,叫RoutePredicateHandlerMapping,咱们能够发现这个类有以下特色:app
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping { // ....省略部分代码 @Override protected Mono<?> getHandlerInternal(ServerWebExchange exchange) { // don't handle requests on management port if set and different than server port if (this.managementPortType == DIFFERENT && this.managementPort != null && exchange.getRequest().getURI().getPort() == this.managementPort) { return Mono.empty(); } exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName()); return lookupRoute(exchange) // .log("route-predicate-handler-mapping", Level.FINER) //name this .flatMap((Function<Route, Mono<?>>) r -> { exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); if (logger.isDebugEnabled()) { logger.debug( "Mapping [" + getExchangeDesc(exchange) + "] to " + r); } exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); return Mono.just(webHandler); }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); if (logger.isTraceEnabled()) { logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]"); } }))); } //...省略部分代码 }
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()); return r.getPredicate().apply(exchange); }) // instead of immediately stopping main flux due to error, log and // swallow it .doOnError(e -> logger.error( "Error applying predicate for route: " + route.getId(), e)) .onErrorResume(e -> Mono.empty())) // .defaultIfEmpty() put a static Route not found // or .switchIfEmpty() // .switchIfEmpty(Mono.<Route>empty().log("noroute")) .next() // TODO: error handling .map(route -> { if (logger.isDebugEnabled()) { logger.debug("Route matched: " + route.getId()); } validateRoute(route, exchange); return route; }); /* * TODO: trace logging if (logger.isTraceEnabled()) { * logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); } */ }
@Bean public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties, List<GatewayFilterFactory> GatewayFilters, List<RoutePredicateFactory> predicates, RouteDefinitionLocator routeDefinitionLocator, @Qualifier("webFluxConversionService") ConversionService conversionService) { return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters, properties, conversionService); }
public class FilteringWebHandler implements WebHandler { //.....省略其它代码 @Override public Mono<Void> handle(ServerWebExchange exchange) { //拿到当前的route Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); //获取全部的gatewayFilter List<GatewayFilter> gatewayFilters = route.getFilters(); //获取全局过滤器 List<GatewayFilter> combined = new ArrayList<>(this.globalFilters); combined.addAll(gatewayFilters); // TODO: needed or cached? AnnotationAwareOrderComparator.sort(combined); if (logger.isDebugEnabled()) { logger.debug("Sorted gatewayFilterFactories: " + combined); } //交给默认的过滤器链执行全部的过滤操做 return new DefaultGatewayFilterChain(combined).filter(exchange); } //....省略其它代码 }
在这里能够看到它的实际处理方式是委派给过滤器链进行处理请求操做的负载均衡
Spring Cloud Gateway包含许多内置的Predicate Factory。全部的Predicate都匹配HTTP请求的不一样属性。若是配置类多个Predicate, 那么必须知足全部的predicate才能够,官网上列举的内置的Predicate,我在这里不作过多的说明,请你们参考:地址,predicate的实现能够在org.springframework.cloud.gateway.handler.predicate
的包下找到。ide
先改一下application.yaml中的配置:函数
spring: application: name: gateway cloud: gateway: routes: - id: before_route uri: http://www.baidu.com predicates: - Number=1
默认命名规则:名称RoutePredicateFactory,在这里咱们能够看到以下代码规则用以解析Predicate的名称,该代码在NameUtils当中:
public static String normalizeRoutePredicateName( Class<? extends RoutePredicateFactory> clazz) { return removeGarbage(clazz.getSimpleName() .replace(RoutePredicateFactory.class.getSimpleName(), "")); }
那么在这里咱们就按照如上规则创建对应的NumberRoutePredicateFactory,代码以下:
@Component public class NumberRoutePredicateFactory extends AbstractRoutePredicateFactory<NumberRoutePredicateFactory.Config> { public NumberRoutePredicateFactory() { super(Config.class); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("number"); } @Override public ShortcutType shortcutType() { return ShortcutType.GATHER_LIST; } @Override public Predicate<ServerWebExchange> apply(Config config) { return new GatewayPredicate() { @Override public boolean test(ServerWebExchange serverWebExchange) { String number = serverWebExchange.getRequest().getQueryParams().getFirst("number"); return config.number == Integer.parseInt(number); } }; } public static class Config { private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } } }
Gateway中的filter能够分为(GlobalFilter)全局过滤器与普经过滤器,过滤器能够在路由到代理服务的先后改变请求与响应。在这里我会列举两个常见的filter给你们用做参考:
与zuul相似,Gateway也能够做为服务端的负载均衡,那么负载均衡的处理关键就是与Ribbon集成,那么Gateway是利用GlobalFilter进行实现的,它的实现类是LoadBalancerClientFilter:
public class LoadBalancerClientFilter implements GlobalFilter, Ordered { protected final LoadBalancerClient loadBalancer; private LoadBalancerProperties properties; //.... @Override @SuppressWarnings("Duplicates") public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // preserve the original url addOriginalRequestUrl(exchange, url); log.trace("LoadBalancerClientFilter url before: " + url); //选择一个服务实例 final ServiceInstance instance = choose(exchange); if (instance == null) { throw NotFoundException.create(properties.isUse404(), "Unable to find instance for " + url.getHost()); } URI uri = exchange.getRequest().getURI(); // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default, // if the loadbalancer doesn't provide one. //判断协议类型 String overrideScheme = instance.isSecure() ? "https" : "http"; if (schemePrefix != null) { overrideScheme = url.getScheme(); } //重构uri地址 URI requestUrl = loadBalancer.reconstructURI( new DelegatingServiceInstance(instance, overrideScheme), uri); //... return chain.filter(exchange); } }
在这里咱们能够看到这里它是基于Spring-Cloud-Commons规范里的LoadBalanceClient包装实现的。
Gateway一样也能够和Hystrix进行集成,这里面的关键类是HystrixGatewayFilterFactory,这里面的关键是RouteHystrixCommand该类继承了HystrixObservableCommand:
@Override protected Observable<Void> construct() { // 执行过滤器链 return RxReactiveStreams.toObservable(this.chain.filter(exchange));//1 } @Override protected Observable<Void> resumeWithFallback() { if (this.fallbackUri == null) { return super.resumeWithFallback(); } // TODO: copied from RouteToRequestUrlFilter URI uri = exchange.getRequest().getURI(); // TODO: assume always? boolean encoded = containsEncodedParts(uri); URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null) .uri(this.fallbackUri).scheme(null).build(encoded).toUri();//2 exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); addExceptionDetails(); ServerHttpRequest request = this.exchange.getRequest().mutate() .uri(requestUrl).build(); ServerWebExchange mutated = exchange.mutate().request(request).build(); return RxReactiveStreams.toObservable(getDispatcherHandler().handle(mutated));//3 }
服务发现对于Gateway来讲也是个很是重要的内容,Gateway在这里定义了一个核心接口叫作:RouteDefinitionLocator,这个接口用于获取Route的定义,服务发现的机制实现了该接口:
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator { @Override public Flux<RouteDefinition> getRouteDefinitions() { //....省略部分代码 return Flux.fromIterable(discoveryClient.getServices())//获取全部服务 .map(discoveryClient::getInstances) //映射转换全部服务实例 .filter(instances -> !instances.isEmpty()) //过滤出不为空的服务实例 .map(instances -> instances.get(0)).filter(includePredicate)//根据properites里的include表达式过滤实例 .map(instance -> { /* 构建Route的定义 */ String serviceId = instance.getServiceId(); RouteDefinition routeDefinition = new RouteDefinition(); routeDefinition.setId(this.routeIdPrefix + serviceId); String uri = urlExpr.getValue(evalCtxt, instance, String.class); routeDefinition.setUri(URI.create(uri)); final ServiceInstance instanceForEval = new DelegatingServiceInstance( instance, properties); //添加Predicate 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); } //添加filter 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; }); } }
由此咱们能够知道,这里面利用DiscoveryClient获取全部的服务实例并将每一个实例构建为一个Route,不过在此以前,在自动装配的类GatewayDiscoveryClientAutoConfiguration里已经配置了默认的Predicate与Filter,它会预先帮咱们配置默认的Predicate与Filter:
public static List<PredicateDefinition> initPredicates() { ArrayList<PredicateDefinition> definitions = new ArrayList<>(); // TODO: add a predicate that matches the url at /serviceId? // add a predicate that matches the url at /serviceId/** PredicateDefinition predicate = new PredicateDefinition(); predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class)); predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'"); definitions.add(predicate); return definitions; } public static List<FilterDefinition> initFilters() { ArrayList<FilterDefinition> definitions = new ArrayList<>(); // add a filter that removes /serviceId by default FilterDefinition filter = new FilterDefinition(); filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class)); String regex = "'/' + serviceId + '/(?<remaining>.*)'"; String replacement = "'/${remaining}'"; filter.addArg(REGEXP_KEY, regex); filter.addArg(REPLACEMENT_KEY, replacement); definitions.add(filter); return definitions; }
这里面主要会根据ServiceId构建为 Path=/serviceId/**的Predicate和路由至对应服务前把ServiceId去掉的filter
根据上述说明,我仅仅选取了两个比较典型意义的Predicate与Filter代码进行说明,因为官网上没有说明自定义Predicate,我在这里索性写了个简单的例子,那么自定义Filter的例子能够参考官网地址:
这里须要吐槽一下官方 何时能把TODO补充完整的呢?
Gateway是基于Webflux实现的,它经过扩展HandlerMapping与WebHandler来处理用户的请求,先经过Predicate定位到Router而后在通过FilterChain的过滤处理,最后定位到下层服务。同时官方给咱们提供了许多Prdicate与Filter,好比说限流的。从这点来讲它的功能比zuul还强大呢,zuul里有的服务发现,断路保护等,Gateway分别经过GlobalFilter与Filter来实现。
最后至于Gateway能普及到什么样的程度,亦或者能不能最终成为统一的网关标准,这个我也不能再这里有所保证,那么就交给时间来证实吧。