背景

这周出现了一个重复请求的问题,两个请求之间刚好间隔一分钟,但是由于缺乏日志并不能判断这个是用户点击导致的,还是Gateway出现重复请求导致的,趁着周末有时间,再深入了解一下。

由于SpringCloud Gateway是基于Spring Webflux的,大量采用了Reactor,相比传统观念上的 单刀直入,理解上会更加麻烦一点。

单纯的去看代码本身其实是很低效的一个行为,所以去我给自己定了一个小目标,了解一下 Gateway 的重试是怎么实现的。

过程

初看其实挺简单的,Google一下,发现就是配置一下 RetryGatewayFilterFactory 中的几个参数,其实现也挺简单的,判断一下Response的stateCode,service状态码,以及异常是否符合(默认存在IO异常和超时异常)。

  1. routes:
  2. - id: nacos-provider
  3. uri: lb://nacos-provider
  4. predicates:
  5. - Path=/nacos-provider/**
  6. filters:
  7. - StripPrefix=1
  8. - name: Retry
  9. args:
  10. routeId: nacos-provider
  11. retries: 3
  12. series:
  13. - SERVER_ERROR
  14. statuses:
  15. - OK
  16. methods:
  17. - GET
  18. - POST
  1. 加上好,启动项目,curl,发现并没有重试,最终原因如下,但是过程还是比较曲折的
  • 打开debug日志
  • 日志太多啦—->不得不吐槽Nacos的日志
    • 配置了好几回的logback都未生效
    • debug发现nacos jar包有一份logback日志,覆盖掉
  • 开始看日志,在 org.springframework.cloud.gateway.handler中发现如下日志

    1. [
    2. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@5dcf0772}, order = -2147483648],
    3. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@1b46392c}, order = -2147482648],
    4. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@421ead7e}, order = -1],
    5. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@6826b70f}, order = 0],
    6. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.GatewayMetricsFilter@781dac73}, order = 0],
    7. [[RewritePath /nacos-provider/?(?<remaining>.*) = '/${remaining}'], order = 1],
    8. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@c472300}, order = 10000],
    9. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter@446b64b3}, order = 10150],
    10. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerServiceInstanceCookieFilter@35ac9ebd}, order = 10151],
    11. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@4df7d9ee}, order = 2147483646],
    12. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@56c0a61e}, order = 2147483647],
    13. [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@5f6494c0}, order = 2147483647]
    14. ]
  • 显然没有 RetryGatewayFilterFactory 中的 Filter, 开始陷入为什么环节

    • 是不是RetryGatewayFilterFactory 由于自动装配没有注册上去,debug发现注册了
    • 是不是没有加载进去,debug发现加载进去了
    • 最终找到原因如下链接所示

    于是把 spring.cloud.gateway.discovery.locator.enable=true 注释掉,再次curl成功了。但是具体原因我又没找到,于是开始找原因,上边链接的解释是 enable=true 会自动加载注册中心的服务,然后建立一些默认的配置,找默认配置就得找到enable在哪里使用的,为此不得不把源代码拉下来, 网络悲催,一直下载不下来全部的配置。image.png
    顶着一片红找到了路由刷新的地方,Nacos默认每隔30秒发送一个HeartbeatEvent事件,springcloud收到事件后,又发送一个RefreshRoutesEvent事件,在 CachingRouteLocatorCachingRouteDefinitionLocator 中找到了消费的地方,由于是reactor代码,到这里就有点迷糊了,so bad
    具体原因请看这里: SpringCloud Gateway【 路由刷新】

    拓展

    为此我又拓展了一个思考题,或者说是面试题,如果我去设计一个Gateway会怎么去思考,随便写写。

  • 可用性

    • 稳定
    • 路由: 最重要的过程,收到URL,路由映射,转发到后端服务
      • header路由
      • path路由
      • query路由
      • cookie路由
      • 等等
    • 其他feature:
      • 路由刷新
      • 重试
      • websocket
      • 等等
  • 性能
    • 同步
    • 异步
  • 复杂性
    • 满足可用性和性能的基础上,代码应该尽可能的简单,但又不能丢失拓展性,让更多的同学能够参与进来,群策群力。
  • 拓展性

百亿规模API网关服务Shepherd的设计与实现