• Spring - Spring Cloud Gateway网关实战及原理解析


    API 网关

    API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:

    • 客户端会多次请求不同的微服务,增加了客户端的复杂性。
    • 存在跨域请求,在一定场景下处理相对复杂。
    • 认证复杂,每个服务都需要独立认证。
    • 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
    • 某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难。

    以上这些问题可以借助 API 网关解决。API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 API 网关来做,这样既提高业务灵活性又不缺安全性,典型的架构图如图所示:

    使用 API 网关后的优点如下:

    • 易于监控。可以在网关收集监控数据并将其推送到外部系统进行分析。
    • 易于认证。可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
    • 减少了客户端与各个微服务之间的交互次数。

    API 网关选型

    业界的情况:

    我前面的文章<Netflix网关zuul(1.x和2.x)全解析>已经介绍了zuul1 和zuul2,现在就尝试从实例入手介绍一下spring cloud gateway

    首先我们一步步实现一个最简单的网关例子

    步骤1:在http://start.spring.io网站上创建一个spring-cloud-gateway-example项目,依赖spring-cloud-gateway,如下图所示

    此时生产了一个spring-cloud-gateway-example的空项目包,pom.xml文件如下

    
    
        4.0.0
        
            org.springframework.boot
            spring-boot-starter-parent
            2.1.3.RELEASE
             
        
        com.example
        spring-cloud-gateway-example
        0.0.1-SNAPSHOT
        spring-cloud-gateway-example
        Demo project for Spring Boot
    
        
            1.8
            Greenwich.RELEASE
        
    
        
            
                org.springframework.cloud
                spring-cloud-starter-gateway
            
    
            
                org.springframework.boot
                spring-boot-starter-test
                test
            
        
    
        
            
                
                    org.springframework.cloud
                    spring-cloud-dependencies
                    ${spring-cloud.version}
                    pom
                    import
                
            
        
    
        
            
                
                    org.springframework.boot
                    spring-boot-maven-plugin
                
            
        
    
        
            
                spring-milestones
                Spring Milestones
                https://repo.spring.io/milestone
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    当然,也可以不适用配置类,使用配置文件,如下图所示

    spring:
      cloud:
        gateway:
          routes: 
            - predicates:
                - Path=/java/**
              filters:
                - StripPrefix=1
              uri: "http://localhost:8090/helloWorld"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    不过,为了调试方便,我们使用配置类方式。

    此时项目已经完成,足够简单吧。

    3.启动此项目

    >>因api网关需要转发到一个服务上,本文为http://localhost:8090/helloWorld,那需要先启动我上文<spring boot整合spring5-webflux从0开始的实战及源码解析>,你也可以创建一个普通的web项目,启动端口设置为8090,然后启动。

    . ____ _ __ _ _
    /\ / ___'_ __ _ _(_)_ __ __ _    
    
    • 1
    • 2

    ( ( )___ | '_ | '| | ’ / ` |
    / )| |)| | | | | || (| | ) ) ) )
    ’ |
    | .__|| ||| |, | / / / /
    =========|
    |==============|
    /=///_/
    :: 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:workspaceForCloudspring5-webflux	argetclasses started by dell in E:workspaceForCloudspring5-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)
    
    • 1
    • 2
    • 3
    • 4

    >>以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)
    
      .   ____          _            __ _ _
     /\ / ___'_ __ _ _(_)_ __  __ _    
    
    • 1
    • 2
    • 3
    • 4

    ( ( )___ | '_ | '| | ’ / ` |
    / )| |)| | | | | || (| | ) ) ) )
    ’ |
    | .__|| ||| |, | / / / /
    =========|
    |==============|
    /=///_/
    :: 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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 predicate, List gatewayFilters) {
            this.id = id;
            this.uri = uri;
            this.order = order;
            this.predicate = predicate;
            this.gatewayFilters = gatewayFilters;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    那么我们就来探讨一下这两个组件吧

    5.1.Predicate

    Predicte由PredicateSpec来构建,主要实现有:

    /**
     * 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))));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    PathRoutePredicateFactory中执行

    @Override
        public Predicate apply(Config config) {
            final ArrayList 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 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;
                }
            };
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    5.2.Filter

    Filter分两种,一种GatewayFilter,一种GlobalFilter

    5.2.1 GatewayFilter

    GatewayFilter由GatewayFilterSpec构建,GatewayFilter的构建器

    5.2.2GlobalFilter

    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

  • 相关阅读:
    leetcode:152. 乘积最大子数组
    2022年全球市场液相导热油总体规模、主要生产商、主要地区、产品和应用细分研究报告
    IS 2022 | 字节AI Lab联合南科大提出:利用伪标注数据提升端到端S2ST
    Vue项目搭建基础流程
    如何在 Ubuntu 上启用自动更新
    Ubuntu系统下MySQL开启远程连接
    DSP技术及应用——学习笔记一(量化效应)
    IO Watch:用 Arduino UNO 制造的可编程手表
    NNDL 知识点梳理
    在Qt creator中使用多光标
  • 原文地址:https://blog.csdn.net/m0_67402236/article/details/126496582