Spring Cloud Gateway 入門

認識 Spring Cloud Gatewayhtml

Spring Cloud Gateway 是一款基於 Spring 5,Project Reactor 以及 Spring Boot 2 構建的 API 網關,是 Spring Cloud 微服務生態的主要組件之一。Spring Cloud Gateway 主要負責接口請求的路由分發,而且支持對請求的安全驗證,流量監控和流量控制等擴展操做。另外值得一提的點是,Spring Cloud Gateway 默認採用了非阻塞 I/O 模型實現請求路由的分發。對於處理一些I/O 耗時長的請求上,相比其餘同樣用 Java 編寫採用的同步阻塞I/O 模型的網關性能更高,處理的併發數也更高,避免了由於 I/O 阻塞(網絡調用,數據庫操做等)致使線程空閒下來,仍能繼續處理響應其餘請求。java

Spring Cloud Gateway 適用場景git

做爲 API 網關,Spring Cloud Gateway 所提供的功能也很強大,集成了對負載均衡,動態路由,訪問控制,限流熔斷,埋點監控等功能的支持。若是現有的微服務體系是以 Java 生態甚至 Spring 生態爲基礎的,那麼就十分適合使用 Spring Cloud Gateway 做爲 API 應用網關了,讓聚合管理多個微服務 API,對外進行統一的輸出。github

同時秉承 Spring 家族的傳統,Spring Cloud Gateway 也旨在提供一個簡單,且高效的方式來進行 API 路由和請求關注點的擴展,對於已經熟悉 Spring 或者 Spring Boot 的開發者來講,Spring Cloud Gateway 學習成本並不高,利用底層框架所帶的註解驅動和自動化配置等特性,使用和擴展起來難度都不算高。spring

快速上手 Spring Cloud Gateway數據庫

利用 Spring Cloud Gateway 能快速搭建一個 API 網關,但在這以前,先介紹一下使用 Spring Cloud Gateway 框架所涉及的一些專用概念,來加深對 Spring Cloud Gateway 的認識,方便後面的使用。緩存

  • 路由:是 Spring Cloud Gateway 中基礎的組件,一般由一個 id 標識,目標 URI,以及一系列斷言(Predicate)和過濾器組成。安全

  • 斷言(Predicate):是 Java 8 函數庫的 Predicate 對象,具體類型爲 Predicate<ServerWebExchange> ,用於匹配 HTTP 請求上數據信息,如請求頭信息,請求體信息。若是對於某個請求的斷言爲 true,那麼它所關聯的路由就算匹配成功,而後將請求給這個路由處理。網絡

  • 過濾器:用於某一個路由的請求或者響應進行修改的組件,在 Spring Cloud Gateway 都要實現 GatewayFilter 接口,而且須要由基於 GatewayFilterFactory 具體實現類構造。併發

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

認識上面三個概念以後,再看上圖所示,就能清楚看出 Spring Cloud Gateway 對客戶端請求的處理過程了,這幫助咱們用好 Spring Cloud Gateway 幫助很大。

  • 客戶端請求首先被 GatewayHandlerMapping 獲取,而後根據斷言匹配找到對應的路由

  • 找到路由後,走完所關聯的一組請求過濾器的處理方法,請求到目標 URI 所對應的服務程序,得到服務響應。

  • 網關收到響應後,經過關聯的響應過濾器的處理方法後,一樣由 GatewayHandlerMapping 返回響應給客戶端。

額外須要注意的是 Spring Cloud Gateway 的過濾器是有序執行的,統一以 order 值的大小決定執行順序,值越小優先級越高,就越先執行。

如何實現 API 聚合

認識 Spring Cloud Gateway 總體處理請求過程以後,咱們如今就快速構建一個基於 Spring Cloud Gateway 的 API 網關,看看在實際應用中還須要注意的哪些地方,須要注意的是本文所使用的 Spring Cloud Gateway 屬於最新的里程碑版本 2.2.3,對應 Spring Boot 版本爲 2.3.1, 而且 Spring Cloud 版本爲 Hoxton.SR6 。利用 Spring Initializr ,選擇對應的版本和依賴後快速新建一個項目 spring-cloud-gateway-quick-start ,而且爲了實現請求的路由,表現網關的效果,再分別新建用戶服務應用 demo-userservice 和訂單服務應用 demo-orderservice ,各自提供一個可調用 API 接口。

用戶服務暴露 8071 端口,提供 /user/get 接口:

// demo-userservice  項目
@RestController
@RequestMapping("/user")
public class UserServiceController {
    @RequestMapping("/get")
    public User get() {
        return User.mock();
    }
}

相似,訂單服務暴露 8061 端口,提供 /order/get 接口:

// demo-orderservice 項目
@RestController
@RequestMapping("/order")
public class OrderServiceController {
    @RequestMapping("/get")
    public Order get() {
        return Order.mock();
    }
}

接下來要經過 Spring Cloud Gateway 將兩個服務接口聚合在 spring-cloud-gateway-quick-start 項目中,首先來看下利用 Spring Cloud Gateway API 方式的實現:

@SpringBootApplication
public class DemogatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemogatewayApplication.class, args);
    }

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes().route("user-service", r -> r.path("/user/*").uri("http://localhost:8071"))
                .route("order-service", r -> r.path("/order/*").uri("http://localhost:8061"))
                .build();
    }
}

接下來要經過 Spring Cloud Gateway 將兩個服務接口聚合在 spring-cloud-gateway-quick-start 項目中,首先來看下利用 Spring Cloud Gateway API 方式的實現:

上述代碼就已經實現 API 路由的功能,是否是很快速,同時啓動 spring-cloud-gateway-quick-start 和其餘服務應用,就能夠統一經過網關應用訪問用戶服務和訂單服務了:

one@192 ~ % curl http://localhost:8080/user/get
{"id":4720186416534735290,"token":"86b6118d-7dc6-4d30-a5f3-3d5fc6348f9a"}

one@192 ~ % curl http://localhost:8080/order/get
{"id":5832646761962425508,"title":"My Order"}

回到 API 實現的代碼, DemogatewayApplication#customRouteLocator 方法中定義了兩個 id 分別爲 user-service 和 order-service 的路由,而且設置了匹配請求的斷言,以及真正目標請求地址。這裏路由的斷言採用了路徑匹配的規則,只要原始請求地址符合對應的規則就算匹配到此路由,但 Spring Cloud Gate 還支持豐富的斷言規則,如主機匹配,請求體字段匹配,請求數據匹配等等,足以知足定製路由斷言的規則了。

因爲使用 API 就是硬編碼方式將路由規則定義在程序裏了,這樣作擴展性不好,也很差維護。因而更推薦另一種實現方式:配置化。來看下要實現相同功能,在 application.properties 裏該如何配置:

spring.cloud.gateway.routes[0].id=order-service
spring.cloud.gateway.routes[0].uri=http://localhost:8061
spring.cloud.gateway.routes[0].predicates[0].name=Path
spring.cloud.gateway.routes[0].predicates[0].args[pattern]=/order/*
spring.cloud.gateway.routes[1].id=user-service
spring.cloud.gateway.routes[1].uri=http://localhost:8071
spring.cloud.gateway.routes[1].predicates[0].name=Path
spring.cloud.gateway.routes[1].predicates[0].args[pattern]=/user/*

使用上面的配置,重啓網關應用,一樣能完成以前 API 方式的效果,因爲路由規則轉移到了配置文件中,就大大方便對 API 的管理,爲實現動態路由也提供了可能。固然須要實現動態路由,除了路由配置,還須要進行額外的擴展實現路由規則的動態刷新,涉及 Spring Cloud Gateway 更高級的用法,本文就再也不詳細贅述了,能夠等後續進階使用和分析的文章或者參考網上其餘實現資料。

如何自定義過濾器

爲了能對 API 的請求或者響應處理,Spring Cloud Gateway 提供過濾器組件來實現這一功能,而且內置了不少功能強大。另外過濾器分兩類,全局過濾器和網關過濾器,對於全局過濾器,全部匹配到路由的請求處理時都會通過全局過濾器處理;而網關過濾器只有顯示在指定路由上時纔會起到左右。

Spring Cloud Gateway 默認的全局過濾器有 8個:

  • ForwardRoutingFilter

  • LoadBalancerClientFilter(棄用)

  • ReactiveLoadBalancerClientFilter

  • WebClientHttpRoutingFilter

  • NettyWriteResponseFilter

  • RouteToRequestUrlFilter

  • WebsocketRoutingFilter

  • GatewayMetricsFilter

而網關過濾器就更多了,而且由對應工廠類來構造,好比用於熔斷的 HystrixGatewayFilterFactory ,用於限流的 RequestRateLimiterGatewayFilterFactory,用於修改請求數據的 ModifyRequestBodyGatewayFilterFactory 等等,固然也支持開發者進行定義本身的過濾器。

首先來看下如何自定義一個全局過濾器,代碼實現比較簡單:

@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
    private Logger log = LoggerFactory.getLogger(MyAuthFilterFactory.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("執行自定過濾器");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

這樣就能夠爲全部路由添加一個全局的過濾器了。不一樣於全局過濾器的定義,網關過濾器必須在指定路由上進行申明才能生效,參考官方內置的網關攔截器,自定義一個用於受權的簡易網關攔截器工廠以下:

@Component
public class MyAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<MyAuthGatewayFilterFactory.Config> {
    private Logger logger = LoggerFactory.getLogger(MyAuthGatewayFilterFactory.class);

    public MyAuthGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();
                MultiValueMap<String, String> queryParams = request.getQueryParams();
                String from = queryParams.getFirst(config.getAuthKey());
                ServerHttpResponse response = exchange.getResponse();
                logger.warn("校驗受權開始");
                if (config.getAuthValue().equals(from)) {
                    logger.warn("校驗受權成功");
                    return chain.filter(exchange);
                } else {
                    logger.warn("校驗受權失敗");
                    response.setStatusCode(HttpStatus.OK);
                    response.getHeaders().setContentType(MediaType.valueOf("text/html;charset=utf-8"));
                    DataBuffer wrap = response.bufferFactory().wrap(config.getAuthFailMsg().getBytes(Charset.forName("UTF-8")));
                    return response.writeWith(Flux.just(wrap));
                }
            }
        };
    }

    public static class Config {
        private String authKey = "from";
        private String authValue = "system";
        private String authFailMsg = "受權失敗";

        public String getAuthKey() {
            return authKey;
        }

        public void setAuthKey(String authKey) {
            this.authKey = authKey;
        }

        public String getAuthValue() {
            return authValue;
        }

        public void setAuthValue(String authValue) {
            this.authValue = authValue;
        }

        public String getAuthFailMsg() {
            return authFailMsg;
        }

        public void setAuthFailMsg(String authFailMsg) {
            this.authFailMsg = authFailMsg;
        }
    }
}

若是要在 user-service 路由下使用,須要在 application.properties 配置文件添加以下配置:

spring.cloud.gateway.routes[1].filters[0].name=MyAuth

這裏的名稱就須要跟 MyAuthGatewayFilterFactory 類的 MyAuth 保持一致,Spring Cloud Gateway 會自動拼接上 AuthGatewayFilterFactory 去查找對應的網關過濾器,沒有找到就會致使啓動失敗,拋出異常:

java.lang.IllegalArgumentException: Unable to find GatewayFilterFactory with name MyAuth2

配置完對網關應用進行重啓,這是使用原來的方式去請求用戶服務,已經沒法正常訪問,只會返回校驗受權失敗的信息,必須以 http://localhost:8080/user/get?from=system 方式訪問才能成功獲取到數據,說明定義的受權攔截器已經起了做用。

這裏咱們就將全局攔截器和網關攔截器都實現了自定義,一般狀況咱們都會在網關攔截器上進行擴展定製,也結合內置的過濾器使用。最後將完整的實現代碼上傳到 Github :https://github.com/wrcj12138aaa/spring-cloud-gateway-quick-start ,感興趣的朋友也能夠參考下。

 

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

往期推薦

日誌系統新貴Loki,確實比笨重的ELK輕

接到一個需求,想在頁面上加一個連接有多難?

MySQL中,當 update 修改數據與原數據相同時會再次執行嗎?

Spring Boot 2.x基礎教程:使用EhCache緩存集羣

嚐鮮剛發佈的 SpringFox 3.0.0,之前造的輪子能夠不用了...

開源:推薦一個不錯的離線IP地址定位庫

相關文章
相關標籤/搜索