微服務網關Gateway實踐總結

2022.09.05
微服務網關Gateway實踐總結

Gateway網關作為微服務架構系統中最先接收請求的一層,可以定義許多策略來保護系統的安全,比如高並發接口的限流,第三方授權驗證,遭到惡意攻擊時的IP攔截等等,盡量將非法請求在網關中攔截掉,從而保證系統的安全與穩定。

一、Gateway簡介

微服務架構中,網關服務通常提供動態路由,以及流量控制與請求識別等核心能力,在之前的篇幅中有說過Zuul組件的使用流程,但是當下Gateway組件是更常規的選擇,下面就圍繞Gateway的實踐做詳細分析;

圖片

從架構模式上看,網關不管採用什麼技術組件,都是在客戶端與業務服務中間提供一層攔截與校驗的能力,但是相比較Zuul來說,Gateway提供了更強大的功能和卓越的性能;

基于实践的场景来看,在功能上网关更侧重请求方的合法校验,流量管控,以及IP级别的拦截,从架构层面看,通常需要提供灵活的路由机制,比如灰度,负载均衡的策略等,并基于消息机制,进行系统级的安全通知等;

圖片

下面围绕客户端、网关层、门面服务的三个节点,分析Gateway的使用细节,即客户端向网关发出请求,经过网关路由到门面服务处理;

二、动态路由

1.基础概念

路由:作为网关中最核心的能力,从源码结构上看,包括ID、请求URI、断言集合、过滤集合等组成;

public class RouteDefinition {
 private String id;
 private URI uri;
 private List<PredicateDefinition> predicates = new ArrayList<>();
 private List<FilterDefinition> filters = new ArrayList<>();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

断言+过滤:通常在断言中定义请求的匹配规则,在过滤中定义请求的处理动作,结构上看都是名称加参数集合,并且支持快捷的方式配置;

public class PredicateDefinition {
 private String name;
 private Map<String, String> args = new LinkedHashMap<>();
}

public class FilterDefinition {
 private String name;
 private Map<String, String> args = new LinkedHashMap<>();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

2.配置路由

以配置的方式,添加facade​服务路由,以路径匹配的方式,如果请求路径错误则断言失败,StripPrefix设置为1,即在过滤中去掉第一个/facade参数;

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
        - id: facade
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/facade/**
          filters:
            - StripPrefix=1
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

执行原理如下:

圖片

这里是以配置文件的方式,设置facade服务的路由策略,其中指定了路径方式,在Gateway文档中提供了多种路由样例,比如:Header、Cookie、Method、Query、Host等断言方式;

3.编码方式

基于编码的方式管理路由策略,在Gateway文档同样提供了多种参考样例,如果路由服务少并且固定,配置的方式可以解决,如果路由服务很多,并且需要动态添加,那基于库表方式更适合;

@Configuration
public class GateConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("facade",r -> r.path("/facade/**").filters(f -> f.stripPrefix(1))
                .uri("http://127.0.0.1:8082")).build();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

4.库表加载

在常规的应用中,从库表中读取路由策略是比较常见的方式,定义路由工厂类并实现RouteDefinitionRepository​接口,涉及加载、添加、删除三个核心方法,然后基于服务类从库中读取数据转换为RouteDefinition对象即可;

圖片

@Component
public class DefRouteFactory implements RouteDefinitionRepository {
    @Resource
    private ConfigRouteService routeService ;
    // 加载
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(routeService.getRouteDefinitions());
    }
    // 添加
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> { routeService.saveRouter(routeDefinition);
            return Mono.empty();
        });
    }
    // 删除
    @Override
    public Mono<Void> delete(Mono<String> idMono) {
        return idMono.flatMap(routeId -> { routeService.removeRouter(routeId);
            return Mono.empty();
        });
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

在源码仓库中采用的就是库表管理的方式,代码逻辑的更多细节可以移步Git参考,此处不再过多粘贴;

三、自定义路由策略

自定义断言,继承AbstractRoutePredicateFactory​类,注意命名以RoutePredicateFactory​结尾,重写apply方法,即可执行特定的匹配规则;

@Component
public class DefCheckRoutePredicateFactory extends AbstractRoutePredicateFactory<DefCheckRoutePredicateFactory.Config> {
    public DefCheckRoutePredicateFactory() {
        super(Config.class);
    }
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                log.info("DefCheckRoutePredicateFactory:" + config.getName());
                return StrUtil.equals("butte",config.getName());
            }
        };
    }
    @Data
    public static class Config { private String name; }
    @Override
    public List<String> shortcutFieldOrder() { return Collections.singletonList("name"); }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

自定义过滤,继承AbstractNameValueGatewayFilterFactory​类,注意命名以GatewayFilterFactory​结尾,重写apply方法,即可执行特定的过滤规则;

@Component
public class DefHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
    @Override
    public GatewayFilter apply(AbstractNameValueGatewayFilterFactory.NameValueConfig config) {
        return (exchange, chain) -> {
            log.info("DefHeaderGatewayFilterFactory:"+ config.getName() + "-" + config.getValue());
            return chain.filter(exchange);
        };
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

配置加载方式,此处断言与过滤即快捷的配置方式,所以在命名上要遵守Gateway的约定;

spring:
  cloud:
    gateway:
      routes:
        - id: facade
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/facade/**
            - DefCheck=butte
          filters:
            - StripPrefix=1
            - DefHeader=cicada,smile
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

通常来说,在应用级的系统中都需要进行断言和过滤的策略自定义,以提供业务或者架构层面的支撑,完成更加细致的规则校验,尤其在相同服务多版本并行时,可以更好的管理路由策略,从而避免分支之间的影响;

四、全局过滤器

在路由中采用的过滤是GatewayFilter​,实际Gateway中还提供了GlobalFilter全局过滤器,虽然从结构上看十分相似,但是其职责是有本质区别的;

全局过滤器1:打印请求ID

@Component
@Order(1)
public class DefOneGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("request-id:{}",exchange.getRequest().getId()) ;
        return chain.filter(exchange);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

全局过滤器2:打印请求URI

@Component
@Order(2)
public class DefTwoGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("request-uri:{}",exchange.getRequest().getURI()) ;
        return chain.filter(exchange);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

Gateway网关作为微服务架构系统中最先接收请求的一层,可以定义许多策略来保护系统的安全,比如高并发接口的限流,第三方授权验证,遭到恶意攻击时的IP拦截等等,尽量将非法请求在网关中拦截掉,从而保证系统的安全与稳定。

五、参考源码

应用仓库:https://gitee.com/cicadasmile/butte-flyer-parent

组件封装:https://gitee.com/cicadasmile/butte-frame-parent