在《微服务网关——需求篇 》中,我们讨论了微服务网关的需求;而在《微服务网关——设计篇 》中,我们讨论了微服务网关的设计。本文将对微服务网关进行实现。
对于网关的开发,完全自研的难度比较大,特别是IO的处理。考虑到目前市面上有比较多的成熟框架,可以基于成熟的开源框架,进行二次开发。
对于Java来说,目前Spring提供了SpringCloud Gateway(下面简称SG),SG基于SpringWebFlux,底层默认使用的是Netty框架,支持高并发请求,同时提供了一些默认的组件,支持路由、限流等功能。可以基于SG进行二次开发。
如果对网关有特别多的自定义需求,可以直接基于Netty来进行更彻底的二次开发,适配自身系统的个性化需求。
本文将基于SG来讨论网关的实现。
基于SpringCloud Gateway的网关设计
这里针对前面的需求,梳理如何基于SpringCloud Gateway来实现。关于SpringCloud Gateway本身的设计和实现,会新开一篇专门讨论。
SpringCloud Gateway整体架构
SpringCloud Gateway基于SpringWebFlux,整体架构如下图所示:
SG定义了几个概念:
- 路由(Route):路由是网关的基本构成单元。它由一个ID、一个目标URL、一组谓词以及一组过滤器组成。当谓词判定为true时,表示请求与对应路由匹配
- 谓词(Predicate):Java8函数式谓词。输入参数是Spring框架封装的ServerWebExchange对象。开发人员可以基于此对象来匹配HTTP请求的任意内容,比如请求头或请求参数
- 过滤器(Filter):由特定工厂类构造的一组Spring框架提供的GatewayFilter对象。过滤器可以在请求或响应被处理前/后对其进行修改。
SG处理请求的大致流程如下:
GatewayHandlerMapping判定对应的请求是否匹配某个路由。如果匹配到某个路由,则将请求交给GatewayWebHandler处理。Handler调用一个Filter链来处理这个请求,具体执行流程如下:
- 首先,会执行「pre」过滤器的逻辑
- 然后执行请求处理逻辑
- 最后再执行「post」过滤器的逻辑
SG提供了GatewayFilter和GlobalFilter两种类型的过滤器,从名字可以看出GlobalFilter是对全局生效的,而GatewayFilter是对特定请求生效的。
注意:这里的GlobalFilter是针对所有匹配了GatewayHandlerMapping的请求生效,而不是对所有进入网关的请求生效。
假设,你在网关中编写了一个Controller,但是路由配置中并没有匹配该Controller的路径,那么针对该Controller的请求并不会触发任何GlobalFilter。
SG针对一个请求的完整流程如下图所示:
可以看到,SG的扩展是基于一个个的Filter来实现的。前面提到的大部分需求也完全可以基于Filter去实现,包括但不限于路由、负载均衡、认证授权、过载保护、缓存、服务重试、日志记录等。
这里仅以限流为例,来说明SG的扩展逻辑。其它实现请自行阅读源码,或关注后续内容。
限流实现
SG提供了RequestRateLimiterGatewayFilterFactory过滤器支持限流,同时也支持基于histrix的过载保护,直接集成使用即可,具体请见histrix文档。
这里以RequestRateLimiterGatewayFilterFactory为例来分析SG如何基于拦截器来实现j限流的。SG中的限流是针对每个路由来单独定义的,配置内容如下:
上面的配置,配置了一个路由:
- id为test_route
- 路由地址到lb://test。前面的lb://表示支持负载均衡,后面的test是服务名称
- predicates是谓词,表示当请求以/test开头时,此路由生效
- 同时配置了一个限流拦截器,包括名称和参数。基于此配置通过RequestRateLimiterGatewayFilterFactory来构建对应的Filter。其中的参数是用来配置请求速率的,即每秒允许10个请求,允许短时间(这里是1秒)涌入20个请求。
上面的配置会被构建为一个RouteLocator实例,该类根据配置构建Route、Predicates、Filter等实例。对于Filter来说,
- 在创建RouteLocator实例时,已创建的GatewayFilterFactory实例列表会被作为参数传入
- list被转换为map,key为去除了GatewayFilterFactory后缀名的Filter类名(与配置文件中的Filter的配置匹配),value是对应的GatewayFilterFactory实例
- 在构建Route实例时,会根据配置文件中的Filter的配置获取Filter实例设置进Route实例中,提供给后续流程使用
RequestRateLimiterGatewayFilterFactory核心代码如下所示:
- 1处,获取配置参数。这里的KeyResolver是对获取请求的key逻辑的抽象。比如其中的一个实现是PrincipalNameKeyResolver,它从ServerWebExchange中获取Principal对象,并将Principal.getName()的返回值作为当前请求的key,如果值相同,则认定为同一个请求。
- 2处,构建匿名GatewayFilter
- 3处,基于限流器来处理限流逻辑
官方提供的分布式限流方案是基于redis实现。
核心逻辑在RedisRateLimiter的isAllowed()方法中:
- 1处,获取参数(上面已经解释过了)
- 2处,构建lua参数,执行lua脚本
- 3处,根据返回值判定是否允许访问
lua脚本就不贴出来了,使用的是令牌桶算法。
自定义拦截器
从上面的梳理,可以看出,要编写一个自定义的GatewayFilter的流程如下:
- 编写一个FilterFactory类继承AbstractGatewayFilterFactory,这个类实现了GatewayFilterFactory接口,该接口提供了一个名叫name的默认方法,用于提供Factory的name,作为获取对应实例的名称。逻辑就是将对应FilterFactory类名后的「GatewayFilterFactory」去掉,例如上面的RequestRateLimiterGatewayFilterFactory,对应的配置名称就是RequestRateLimiter。
- 在覆写的apply方法中构建对应的Filter,可以是直接返回匿名GatewayFilter,或返回对应Filter的实例
- 如果返回的是Filter的实例,则需要编写一个实现了GatewayFilter的Filter类,实现其filter方法
- 然后将FilterFactory、实现的Filter(如果有的话)配置给Spring管理
- 最后,就可以在配置文件中针对需要的路由配置对应的Filter了
聚合服务
SG基于SpringWebFlux,支持编写异步RESTful接口,同时提供WebClient异步REST客户端来实现聚合服务的编写。不过由于是编码的形式,所以需要发布网关。对于集群部署的情况下,在可用性要求没有特别严格的情况下,此方式可以接受。
如果聚合服务较多且发布频繁,可以独立出聚合服务层。即基于SpringWebFlux构建微服务,使用WebClient来整合独立微服务的逻辑,网关路由至对应的服务即可。
关于WebClient的使用方法请自行搜索,这里就不赘述了。
非功能性需求
SG本身就基本符合前面提到的非功能性需求
- 基于SpringWebFlux的非阻塞IO模型,支持高并发。
- 基于分布式配置服务进行动态配置
- 基于GatewayFilter和GlobalFilter进行扩展
- 可以接入PinPoint的链路监控以及基于ELK的日志监控平台
主要注意保证无状态设计!
总结
本文及之前的「微服务网关——需求篇」和「 微服务网关——设计篇 」对微服务网关进行了梳理。
后面计划专栏基于SpringCloud来对整个微服务进行梳理,欢迎关注!