微服務閘道器Gateway實踐總結

2022-09-01 09:02:54

有多少請求,被閘道器截胡;

一、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<>();
}

斷言+過濾:通常在斷言中定義請求的匹配規則,在過濾中定義請求的處理動作,結構上看都是名稱加引數集合,並且支援快捷的方式設定;

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<>();
}

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

執行原理如下:

這裡是以組態檔的方式,設定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();
    }
}

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();
        });
    }
}

在原始碼倉庫中採用的就是庫表管理的方式,程式碼邏輯的更多細節可以移步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"); }
}
  • 自定義過濾,繼承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);
        };
    }
}
  • 設定載入方式,此處斷言與過濾即快捷的設定方式,所以在命名上要遵守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

通常來說,在應用級的系統中都需要進行斷言和過濾的策略自定義,以提供業務或者架構層面的支撐,完成更加細緻的規則校驗,尤其在相同服務多版本並行時,可以更好的管理路由策略,從而避免分支之間的影響;

四、全域性過濾器

在路由中採用的過濾是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);
    }
}
  • 全域性過濾器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);
    }
}

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

五、參考原始碼

應用倉庫:
https://gitee.com/cicadasmile/butte-flyer-parent

元件封裝:
https://gitee.com/cicadasmile/butte-frame-parent