• spring cloud gateway核心流程


     

    一、      网关种类

    流量型网关和业务型网关,也是自己的一个理解,流量型网关可以通常看成是nginx,kong这种更加专注于高性能进行流量分发,业务相对简单,但是对于“复杂”型业务网关,尤其系统实现使用的是java,那么使用openresty这种无疑是加大了研发成本,而且不利于调试和定位问题,毕竟需要通过规定统一接口来进行交互。

    二、      网关产生背景

    1. 客户端会多次请求不同的微服务,增加了客户端的复杂性。
    2. 认证复杂,每个服务都需要独立认证。
    3. 项目的迭代,可能需要重新划分微服务,这样客户端调用的逻辑会有调整,导致级联客户端的调整,给整个系统带来很大的麻烦,牵一发而动全身
    4. 提取通用业务到网关可以使具体的服务更专注于业务本身,通用业务如:统一身份认证,统一的数据转换处理,定向转发,以及负载均衡策略,限流,访问日志。

    三、      网关技术

    目前市场比较的成形网关有zuul(1.x,2.x)| spring cloud gateway | nginx | kong|other

    能够支撑网关的核心技术点就是能够搞定高tps即可!

        说到这里核心的关注点来了,高tps的支撑就是要快速的去处理请求,快速的接收到请求,不能因为服务器资源的问题而拒绝请求,或造成在底层操作系统级别的排队阻塞。

        这个问题让我们想到了nodejs,而nodejs正是基于事件分发机制的reactor模型的实现,是异步非阻塞的。

        相比于传统的阻塞IO,异步非阻塞接受请求只需要一条线程即可。是的,这个线程只进行请求的接收,收到后会保存请求到一个指定的位置,然后会由一个looper来进行请求的获取,

       交给请求处理器去处理,这里的请求处理器可以理解成一个线程池的机制。

    四、      Spring家族的网关

    Spring cloud gateway(下面简称gateway) 是spring cloud在进一步放弃了zuul 1.x后的新作。也是spring自己的东西,相比于外部依赖更加的可控些。

    SpringBoot 2.2.2. RELEASE,SpringCloud Hoxton.SR1

     

    Release Train

    Boot Version

    Hoxton

    2.2.x

    Greenwich

    2.1.x

    Finchley

    2.0.x

    Edgware

    1.5.x

    Dalston

    1.5.x

    1.  
    2.  

    五、      Gateway启动时的自动配置

    学习一个项目,或者一个技术的关键点在于,了解这个项目的运转过程,了解项目的结构,别一下进入到细节中,也不要仅仅停留于最简单的demo中。

    l  GatewayAutoConfiguration  网关基础配置类,当中承载着核心的配置逻辑

    l  GatewayClassPathWarningAutoConfiguration  网关类加载配置类,就是用于校验是否加载的时webFlux依赖,而不是普通的web依赖。

    l  GatewayLoadBalancerClientAutoConfiguration  网关客户端负载均衡配置类

    l  GatewayRedisAutoConfiguration   网关限流器配置类

    我们在启动spring boot的时候基本都会使用@EnableAutoConfiguration注解,那么当你引入gateway项目的时候上面的三个配置类就会被加载。

    首先看GatewayAutoConfiguration  中的NettyConfiguration,这个类为初始化netty通信的一系列流程,分别注册了3个bean。这里只是抛砖引玉,里面详细的配置做了哪些事情,可以自己找下gateway代码对号入座。

           GatewayAutoConfiguration 作为基础配置,内部又注册了这些bean

    a)   NettyConfiguration

    b)   GlobalFilter

    c)   FilteringWebHandler

    d)   GatewayProperties

    e)   PrefixPathGatewayFilterFactory

    f)   RoutePredicateFactory

    g)   RouteDefinitionLocator

    h)   RouteLocator

    i)   RoutePredicateHandlerMapping

    j)   GatewayWebfluxEndpoint

    六、      Gateway核心概念

    • Routepredicategatewayfilter,globalfilter
    1. Route可以看成是一个请求服务器资源的对象,里面包含着请求信息,下面我会列出属性和关键方法,当然里面包含Predicate以及filter。

    那么在使用的时候需要给route对象中指定属性,uri,path参数,那么predicate用来检查是否合规。

    Route对象属性:

    属性

    含义

    private final String id;

    路由编号

    private final URI uri;

    即将路由向的 URI

    private final int order;

    路由顺序

    Predicate <ServerWebExchange> predicate;

    校验访问信息否合规,调用了包装对象的test方法,因为Predicate本身也有test方法,在gateway中又做了扩展,接口名称为:GatewayPredicate,还要注意,该字段为数组,可add操作bool表达式。

    List<GatewayFilter> gatewayFilters;

    过滤器链

    以上的属性是通过读取配置文件得来的,或者使用Routes.locator()进行对象链式创建。目前这个阶段可以理解成一个请求信息收集。

    1. Predicate,在gateway-core包的handle中,以一个时间的Predicate来举例,AfterRoutePredicateFactory用来校验在某一时间点后生效的断言。

    Predicate也很好解释,java8中的Predicate函数式关键字实质也是一个判断条件,满足条件即放行。而Route其内部是包含了关于ServerWebExchange的Boolean表达式。

    在请求到了gateway,是通过DispatchHandle来进行处理的,它会去匹配HandlerMapping,gateway实现了一个RoutePredicateHandlerMapping;在这个类中的核心

    方法是getHandlerInternal,这个方法中去判断当前断言是否通过,核心方法是调用每个路由断言的test方法,代码如下:

    .filter(route -> route.getPredicate().test(exchange))

       

    如果路由断言条件没有通过,则lookupRoute(ServerwebExchange)方法,返回空的集合。后面的逻辑不会在执行。

       

       

    如果我们想自己定义一个predicate,按照官方的做法继承AbstractRoutePredicateFactory即可,不过目前原始提供的已经比较丰富,或许不用我们扩展!如果需要扩展应该想下我们的方案是否出现在了正确位置

       那么如果能通过predicate,就会调用Mono.just(webHandler)方法继续后面的FilteringWebHandler,而这个类中会持有全局的过滤链。

    1. gateway中的网关还有一个重要的成员就是filter,分为gatewayFilter和globalFilter两种,下面详细解释下过滤器的相关问题,在此之前先打个感叹号!

            

      


    1. 请求接入filter

    先说下请求接入的整个过程吧。这里就会涉及到gateway本身提供的全局过滤器。如有针对路由的过滤器也会根据order方法返回值顺序,与globalFilter进行统一排序。

       

    请求实际走过handle的顺序

             i.      HttpWebHandleAdapter;

           ii.      DispatcherHandle;负责转发到具体的请求处理器;

          iii.      RoutePredicatehandlerMapping;匹配处理器后进行route的断言,成功则取执行过滤链,否则直接response;

           iv.      FilteringWebHandle;这个handle中初始化了9个spring全局的globaFilter (有一个是自定义的)

       

    DefaultGatewayFilterChain 用来处理filter过滤链的关键类,该类持有了filter链;请求在与路由匹配时,FilteringWebHandler组件创建的时候会将所有的 GlobalFilter 构建一个GatewayFilterAdapter,而该对象仅持有GlobalFilter接口方法,在转换成OrderGatewayFilter这样也持有了getOrder方法,根据getOrder方法的返回值顺序组成ArrayList。

       

    在FilteringWebHandler这个类中很关键,如果你有自定义的globalfilter那么就会加入到这个ArrayList中,首次入过滤链是通过WebClientWriteResponseFilter这个过滤器,因为这个过滤器中包含了请求和响应的全状态。整个过滤链都是在这个过滤器中进行的,代码如下:

       

        Lambda表达式中是处理响应阶段的,而chain的filter方法就是在循环ArrayList进行filter的执行;如果你在自定义filter中放行,并继续执行下面的filter那么会在代码中调用chain的filter,如果确定结束,那么需要返回一个Mono对象,由Mono对象去执行then方法,取进行响应内容的操作,最后writeWith到客户端。

    系统提供的重要的全局过滤器:

    • RemoveCachedBodyFilter order为-2147483648

    清除exchange的attributes中cachedRequestBody值。这个key的名称来自exchangeUtile中CACHED_REQUEST_BODY_ATTR = "cachedRequestBody";

    • AdaptCachedBodyGlobalFilter order为-2147483648+1000

    作用是从exchange的attributes中获取cachedRequestBody属性值作为request的body,注意使用此功能首先必须预设cachedRequestBody属性至attributes中。

    • NettyWriteResponseFilter order为-1

    NettyWriteResponseFilter将结果数据流写入ServerHttpResponse中发生在NettyRouting获取到远程调用的结果数据流之后,当NettyRouting拿到结果数据流之后会将其写入当前请求exchange的attributes中。

    • ForwardPathFilter order-0

    处理uri为forword开头的服务地址,形如:forword://xxxxxx.com,否则也忽略。

    • RouteToRequestUrlFilter order为10000

    过滤器RouteToRequestUrlFilter是必须的全局过滤器,主要任务是将原始的url请求根据route中配置的uri,将请求的具体资源信息组合到一起,形成一个真正往后端服务的请求,将真实的请求url路径,配置到exchange中attribute的Map中,key为“包全名.ServerWebExchangeUtils.gatewayRequestUrl”,直接发送到下一个过滤器,如果为lb://模式则会通过LoadBalancerClientFilter进行处理。

    • LoadBalancerClientFilter(我们可以在此处做自定义负载均衡)

    LoadBalancerClientFilter负责服务真实ip的映射,主要针对对个服务节点的情况进行负载均衡,默认采用的netflix-ribbon作为负载均衡器,首先如果scheme不是服务节点映射的话直接过滤,获取服务节点,choose函数是真实负载均衡发生的函数,获取一个本次选出的服务server instance(如果是单节点则无负载计算),然后将服务的真实ip+port替换掉path中的lb://{serviceId}前缀。实际就是拿到一个能够真实请求的地址。那么这个过滤器如果不是lb://servername

    则该过滤器也直接忽略

    • WebsocketRoutingFilter 

    过滤器实现了gateway对于websocket的支持,内部通过websocketClient实现将一个http请求协议换转成websocket,如果uri不是ws开头的这种则不起作用,

    ws://xxxx.com或者wss://zxxxxxx.cn

       

    • NettyRouting   order为2147483647

    NettyRouting获取到远程调用的结果数据流会将其写入当前请求exchange的attributes中,发送回DispatchHandle,又webflux处理。

    • ForwardRoutingFilter  order为2147483647

    最终将exchange交还给Webhandler做http请求处理,已经准备返回数据给客户端(如果是forward则会发送到gateway本地的控制器处理)。

       

    七、      关于网关做统一认证的问题

    • 读取requestBody

    gateway用于统一请求信息校验。我们可以校验header中的信息,通过exchange来获得,如果是一个post请求我们有时也需要校验请求体body的合法性。

    每个filter中都持有exchange对象,获取header的时候使用exchange.getRequest().getHeaders();

    那么现在如果想获取requestBody呢。你会看到网上不天盖地的各种文章,针对各种版本进行处理。拿出一种方式来举个反例:

    照猫画虎:exchange.getBody()获取出来的是Flux<DataBuffer>对象,我们知道fulx使用订阅方法可以取出body,但是如果请求体过大使用sub方法没法取出。

    因为sub方法只能取出发过来的第一份元素。 见了网上的各种hack方式,如果我们仅仅需要校验一个requestBody内容,则只需要在builder.routes()构建每个具体的route对象时对predicate进行readBody的设置,这里需要传入一个参数,是body的传入类型。

       

    这样在gateway启动后,一个请求过来就会去匹配我们事先定义好的route对象。嗯,我们来看下route方法的第二个参数,Function<PredicateSeqc>类型。

       

    我们找到这个类,这里如果使用了readBody则会调用ReadBodyPredicateFactory的applyAsync方法,该方法为读取body的核心操作。

       

    进入applyAsync方法后我们会看到关键的对请求信息做put.attribute的操作,key为一个工具类中的常量,进入方法首先判断是否有缓存,然后我们想到了FilteringWebHandle中的第一个全局过滤器RemoveCacheBodyFilter,所以我们在这里一定能够进入到else。那就是使用exchangeUtil中的cacheBody方法,最后将body获取并存储ccHashMap。

       

    到这里我们看到了一个整体的reqbody的缓存流程,接下来可以在任意的filter中取出使用。

    写到这里我有个小想法,还是想把这个predicate的readBody用filter顶替掉,因为在断言表达式成功了以后就可以进行缓存了,我们需要body可以随时拉出来校验,这个方法不会花费较长时间。不然每个路由都需要配置一次,实在是麻烦!

    后来看了下gateway平台提供了一个modifiyRequestBody的全局Filter。经过改造(去除了一些修改请求的操作,仅仅是将原来的请求body订阅出来,缓存起来,然后构建一个新的exchange对象),order优先级以-2147483647+10排序时机执行,选择这个时机执行因为它处于removeCacheBody和AdapCachaeBody两个过滤器之间。即使有人使用了predicate的readBody(String.class,b-> true)方式,那么在AdapCachaeBodyGlobaleFilter全局过滤器中我们仍然遵循默认的gateway原则去执行,map中缓存的key都是spring gateway项目提供的,所以没有冲突。

       

      https://www.cnblogs.com/zzq-include/p/12944680.html

      这里敲下黑板!!!!!!,可以后面关注下这个readBody方法。

      

    • 修改requestBody

    修改requestBody,由于上面的readBody的提示,我们自然而然的就想到了应该也有一个类似方法来控制,在这里我们需要注意下readBody是以predicate的形式出现的,而modifyBody是以过滤器的身份出现的,非全局过滤器。

       

    如果我们需要全局对每一个请求Body都可能有监控修改的需求,建议按照modifyRequestBodyFilterFactory的内容,自己定义一个全局过滤器这样也免去了配置的麻烦。

    八、      还没想好

  • 相关阅读:
    Block编程
    自己写Web服务器(续)
    C# 2.0对现有语法的改进
    使用CDN
    优化网站设计(一):减少请求数
    MongoDB Shell的使用
    memcache 和appfabric
    go语言中几个有趣的特性以及对go的看法
    bpm流程平台
    Socket编程 (异步通讯) (Tcp,Udp)Part2
  • 原文地址:https://www.cnblogs.com/zzq-include/p/12957460.html
Copyright © 2020-2023  润新知