API 網關
API 網關出現的緣由是微服務架構的出現,不一樣的微服務通常會有不一樣的網絡地址,而外部客戶端可能須要調用多個服務的接口才能完成一個業務需求,若是讓客戶端直接與各個微服務通訊,會有如下的問題:html
- 客戶端會屢次請求不一樣的微服務,增長了客戶端的複雜性。
- 存在跨域請求,在必定場景下處理相對複雜。
- 認證複雜,每一個服務都須要獨立認證。
- 難以重構,隨着項目的迭代,可能須要從新劃分微服務。例如,可能將多個服務合併成一個或者將一個服務拆分紅多個。若是客戶端直接與微服務通訊,那麼重構將會很難實施。
- 某些微服務可能使用了防火牆 / 瀏覽器不友好的協議,直接訪問會有必定的困難。
以上這些問題能夠藉助 API 網關解決。API 網關是介於客戶端和服務器端之間的中間層,全部的外部請求都會先通過 API 網關這一層。也就是說,API 的實現方面更多的考慮業務邏輯,而安全、性能、監控能夠交由 API 網關來作,這樣既提升業務靈活性又不缺安全性,典型的架構圖如圖所示:前端
使用 API 網關後的優勢以下:java
- 易於監控。能夠在網關收集監控數據並將其推送到外部系統進行分析。
- 易於認證。能夠在網關上進行認證,而後再將請求轉發到後端的微服務,而無須在每一個微服務中進行認證。
- 減小了客戶端與各個微服務之間的交互次數。
API 網關選型
業界的狀況:web
我前面的文章<Netflix網關zuul(1.x和2.x)全解析>已經介紹了zuul1 和zuul2,如今就嘗試從實例入手介紹一下spring cloud gatewayspring
首先咱們一步步實現一個最簡單的網關例子apache
步驟1:在http://start.spring.io網站上建立一個spring-cloud-gateway-example項目,依賴spring-cloud-gateway,以下圖所示後端
此時生產了一個spring-cloud-gateway-example的空項目包,pom.xml文件以下api
<?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"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>spring-cloud-gateway-example</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud-gateway-example</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories> </project>
2.建立一個Route實例的配置類GatewayRoutes跨域
package com.example.springcloudgatewayexample; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GatewayRoutes { @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes() .route(r -> r.path("/java/**") .filters( f -> f.stripPrefix(1) ) .uri("http://localhost:8090/helloWorld") ) .build(); } }
固然,也能夠不適用配置類,使用配置文件,以下圖所示瀏覽器
spring: cloud: gateway: routes: - predicates: - Path=/java/** filters: - StripPrefix=1 uri: "http://localhost:8090/helloWorld"
不過,爲了調試方便,咱們使用配置類方式。
此時項目已經完成,足夠簡單吧。
3.啓動此項目
>>因api網關須要轉發到一個服務上,本文爲http://localhost:8090/helloWorld,那須要先啓動我上文<spring boot整合spring5-webflux從0開始的實戰及源碼解析>,你也能夠建立一個普通的web項目,啓動端口設置爲8090,而後啓動。
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.1.3.RELEASE) 2019-02-21 09:29:07.450 INFO 11704 --- [ main] c.e.demo.Spring5WebfluxApplication : Starting Spring5WebfluxApplication on DESKTOP-405G2C8 with PID 11704 (E:\workspaceForCloud\spring5-webflux\target\classes started by dell in E:\workspaceForCloud\spring5-webflux) 2019-02-21 09:29:07.455 INFO 11704 --- [ main] c.e.demo.Spring5WebfluxApplication : No active profile set, falling back to default profiles: default 2019-02-21 09:29:09.409 INFO 11704 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8090 2019-02-21 09:29:09.413 INFO 11704 --- [ main] c.e.demo.Spring5WebfluxApplication : Started Spring5WebfluxApplication in 2.304 seconds (JVM running for 7.311)
>>以spring boot方式啓動spring-cloud-gateway-example項目,日誌以下
2019-02-21 10:34:33.435 INFO 8580 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$1e059320] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.1.3.RELEASE) 2019-02-21 10:34:33.767 INFO 8580 --- [ main] e.s.SpringCloudGatewayExampleApplication : No active profile set, falling back to default profiles: default 2019-02-21 10:34:34.219 INFO 8580 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=d98183ec-3e46-38ba-ba4c-e976a1017dce 2019-02-21 10:34:34.243 INFO 8580 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$1e059320] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2019-02-21 10:34:44.367 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [After] 2019-02-21 10:34:44.367 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Before] 2019-02-21 10:34:44.367 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Between] 2019-02-21 10:34:44.367 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Cookie] 2019-02-21 10:34:44.367 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Header] 2019-02-21 10:34:44.368 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Host] 2019-02-21 10:34:44.368 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Method] 2019-02-21 10:34:44.368 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Path] 2019-02-21 10:34:44.368 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Query] 2019-02-21 10:34:44.368 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [ReadBodyPredicateFactory] 2019-02-21 10:34:44.368 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [RemoteAddr] 2019-02-21 10:34:44.368 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [Weight] 2019-02-21 10:34:44.368 INFO 8580 --- [ main] o.s.c.g.r.RouteDefinitionRouteLocator : Loaded RoutePredicateFactory [CloudFoundryRouteService] 2019-02-21 10:34:44.920 INFO 8580 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080 2019-02-21 10:34:44.923 INFO 8580 --- [ main] e.s.SpringCloudGatewayExampleApplication : Started SpringCloudGatewayExampleApplication in 12.329 seconds (JVM running for 13.126)
4.測試,瀏覽器訪問http://localhost:8080/java/helloWorld
返回hello world !
5.從上面的代碼和配置及實例中,咱們能夠看出spring cloud gateway處理request請求的流程以下所示:
即在最前端,啓動一個netty server(默認端口爲8080)接受請求,而後經過Routes(每一個Route由Predicate(等同於HandlerMapping)和Filter(等同於HandlerAdapter))處理後經過Netty Client發給響應的微服務。
那麼在gateway自己最重要的應該是Route(Netty Server和Client已經封裝好了),它由RouteLocatorBuilder構建,內部包含Predicate和Filter,
private Route(String id, URI uri, int order, AsyncPredicate<ServerWebExchange> predicate, List<GatewayFilter> gatewayFilters) { this.id = id; this.uri = uri; this.order = order; this.predicate = predicate; this.gatewayFilters = gatewayFilters; }
那麼咱們就來探討一下這兩個組件吧
5.1.Predicate
Predicte由PredicateSpec來構建,主要實現有:
以path爲例
/** * A predicate that checks if the path of the request matches the given pattern * @param patterns the pattern to check the path against. * The pattern is a {@link org.springframework.util.PathMatcher} pattern * @return a {@link BooleanSpec} to be used to add logical operators */ public BooleanSpec path(String... patterns) { return asyncPredicate(getBean(PathRoutePredicateFactory.class) .applyAsync(c -> c.setPatterns(Arrays.asList(patterns)))); }
PathRoutePredicateFactory中執行
@Override public Predicate<ServerWebExchange> apply(Config config) { final ArrayList<PathPattern> pathPatterns = new ArrayList<>(); synchronized (this.pathPatternParser) { pathPatternParser.setMatchOptionalTrailingSeparator( config.isMatchOptionalTrailingSeparator()); config.getPatterns().forEach(pattern -> { PathPattern pathPattern = this.pathPatternParser.parse(pattern); pathPatterns.add(pathPattern); }); } return exchange -> { PathContainer path = parsePath(exchange.getRequest().getURI().getPath()); Optional<PathPattern> optionalPathPattern = pathPatterns.stream() .filter(pattern -> pattern.matches(path)).findFirst(); if (optionalPathPattern.isPresent()) { PathPattern pathPattern = optionalPathPattern.get(); traceMatch("Pattern", pathPattern.getPatternString(), path, true); PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path); putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables()); return true; } else { traceMatch("Pattern", config.getPatterns(), path, false); return false; } }; }
5.2.Filter
Filter分兩種,一種GatewayFilter,一種GlobalFilter
5.2.1 GatewayFilter
GatewayFilter由GatewayFilterSpec構建,GatewayFilter的構建器
5.2.2 GlobalFilter
5.3 GlobalFilter和GatewayFilter的聯繫
FilteringWebHandler.GatewayFilterAdapter代理了GlobalFilter
6.總結
本文從一個spring-cloud-gateway實例入手,深刻淺出的介紹了spring-cloud-gateway的組件,並從源碼角度給出了實現的原理。
spring-cloud-gateway在最前端,啓動一個netty server(默認端口爲8080)接受請求,而後經過Routes(每一個Route由Predicate(等同於HandlerMapping)和Filter(等同於HandlerAdapter))處理後經過Netty Client發給響應的微服務。
Predicate和Filter的各個實現定義了spring-cloud-gateway擁有的功能。
參考資料:
【1】https://www.infoq.cn/article/comparing-api-gateway-performances
【2】https://dzone.com/articles/spring-cloud-gateway-configuring-a-simple-route