1. 程式人生 > 實用技巧 >【SpringCloud】Gateway新一代閘道器

【SpringCloud】Gateway新一代閘道器

Gateway新一代閘道器

概述簡介

官網

上一代zuul 1.x

https://github.com/Netflix/zuul/wiki

當前gateway

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

是什麼

概述

Cloud全家桶中有個很重要的元件就是閘道器,在1.x版本中都是採用的ZuuI閘道器;
但在2.x版本中,zuul的升級一 直跳票, SpringCloud最後自己研發了一個閘道器替代Zuul,那就是SpringCloud Gateway
一句話: gateway是 原zuul1.x版的替代

SpringCloud Gateway是Spring Cloud的一個全新專案,基於Spring 5.0+Spring Boot 2.0和Project Reactor等技術開發的閘道器,它旨在為微服務架構提供-種簡單有效的統一的API路由管理方式。

SpringCloud Gateway作為Spring Cloud生態系統中的閘道器,目標是替代Zuul,在Spring Cloud 2.0以上版本中,沒有對新版本的Zuul 2.0以上最新高效能版本進行整合,仍然還是使用的Zuul 1.x非Reactor模式的老版本。而為了提升閘道器的效能,SpringCloud Gateway是基於WebFlux框架實現的,而WebFlux框架底層則使用了高效能的Reactor模式通訊框架Netty。

Spring Cloud Gateway的目標提供統- 的路由方式且基於Filter鏈的方式提供了閘道器基本的功能,例如:安全,監控/指標,和限流。

一句話:

SpringCloud Gateway使用的是Webflux中的reactor-netty響應式程式設計元件,底層使用了Netty通訊框架

原始碼架構

能幹嘛

  • 反向代理
  • 鑑權
  • 流量控制
  • 熔斷
  • 日誌監控
  • .....

微服務架構中閘道器在哪裡

有Zuul了怎麼又出來gateway

我們為什麼選擇Gateway?

1.netflix不太靠譜,zuul2.0一直跳票,遲遲不釋出

一方面因為Zuul1.0已經進 入了維護階段,而且Gateway是 SpringCloud團隊研發的,是親兒子產品,值得信賴。而且很多功能Zuul都沒有用起來也非常的簡單便捷。

Gateway是基於非同步非阻塞模型上進行開發的,效能方面不需要擔心。雖然Netflix早就釋出了最新的Zuul 2.x,但Spring Cloud貌似沒有整合計劃。而且Netflix相關元件都宣佈進入維護期;不知前景如何?

多方面綜合考慮Gateway是很理想的閘道器選擇。

2.SpringCloud Gateway具有如下特性

Spring Cloud Gateway具有如下特性:

  • 基於Spring Framework 5, Project Reactor和Spring Boot 2.0進行構建; τ
  • 動態路由:能夠匹配任何請求屬性;
  • 可以對路由指定Predicate (斷言)和Filter (過濾器) ;
  • 整合Hystrix的斷路器功能;
  • 整合Spring Cloud服務發現功能;
  • 易於編寫的Predicate (斷言)和Filter (過濾器) ;
  • 請求限流功能;
  • 支援路徑重寫。

3.SpringCloud Gateway與Zuul的區別

Spring Cloud Gateway與Zuul的區別
在SpringCloud Finchley正式版之前,Spring Cloud推薦的閘道器是Netflix 提供的Zuul:

  1. Zuul1.x,是一個基於阻塞I/ 0的API Gateway .
  2. Zuul 1.x 基於Servlet 2. 5使用阻塞架構它不支援任何長連線(如WebSocket) Zuul的設計模式和Nginx較像,每次I/ 0操作都是從工作執行緒中選擇一個執行, 請求執行緒被阻塞到工作執行緒完成,但是差別是Nginx用C++實現,Zuul 用Java實現,而JVM本身會有第一次載入較慢的情況,使得Zuul 的效能相對較差。
  3. Zuul 2.x理念更先進,想基於Netty非阻塞和支援長連線,但SpringCloud目前還沒有整合。 Zuul 2.x的效能較Zuul 1.x有較大提升。在效能方面,根據官方提供的基準測試, Spring Cloud Gateway的RPS (每秒請求數)是Zuul的1.6倍。
  4. Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
  5. Spring Cloud Gateway還支援WebSocket,詛與Spring緊密整合擁有更好的開發體驗

Zuul1.x模型

Springcloud中所整合的Zuμ版本,採用的是Tomcat容器,使用的是傳統的Servlet IO處理模型。

學過尚矽谷web中期課程都知道一個題目,Servlet的生命週期?servlet由servlet container進行生命週期管理。
container啟動時構造servlet物件並呼叫servlet init()進行初始化;
container執行時接受請求,併為每個請求分配一個執行緒 (一般從執行緒池中獲取空閒執行緒) 然後呼叫service()。
container關閉時呼叫servlet destory()銷燬servlet;

上述模式的缺點:
servlet是一個簡單的網路IO模型,當請求進入servlet container時, servlet container就會為其繫結一個執行緒, 在併發不高的場景下這種模型是適用的。但是一旦高併發(此如抽風用jemeter壓),執行緒數量就會上漲,而執行緒資源代價是昂貴的(. 上線文切換,記憶體消耗大)嚴重影響請求的處理時間。
在一些簡單業務場景下,不希望為每個request分配一個執行緒,只需要1個或幾個執行緒就能應對極大併發的請求,這種業務場景下servlet模型沒有優勢

所以Zuul 1.X是基於servlet之上的一個阻塞式處理模型,即spring實現了 處理所有request請求的一個servlet (DispatcherServlet) 並由該servlet阻塞式處理處理。所以Springcloud Zuul無法擺脫servlet模型的弊端

Gateway模型

WebFlux是什麼

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#spring-webflux

說明:
傳統的Web框架,此如說: struts2, springmvc等都是 基於Servlet API與Servlet容器基礎之上執行的。
但是
在Servlet3.1之後有了非同步非阻塞的支援。而WebFlux是一 個典型非阻塞非同步的框架, 它的核心是基於Reactor的相關API實現的。相對於傳統的web框架來說,它可以執行在諸如Netty, Undertow及 支援Servlet3.1的容器上。非阻塞式+函數語言程式設計(Spring5必須讓你使用java8)

Spring WebFlux是Spring 5.0引入的新的響應式框架,區別於Spring MVC,它不需要依賴Servlet API,它是完全非同步非阻塞的,並且基於Reactor來實現響應式流規範。

三大核心概念

Route(路由)

路由是構建閘道器的基本模組,它由ID,目標URI,一系列的斷言和過濾器組成,如斷言為true則匹配該路由

Predicate(斷言)

參考的是Java8的java.util.function.Predicate 開發人員可以匹配HTTP請求中的所有內容(例如請求頭或請求引數),如果請求與斷言相匹配則進行路由

Filter(過濾)

指的是Spring框架中GatewayFilter的例項,使用過濾器,可以在請求被路由前或者之後對請求進行修改.

總結


web請求,通過一些匹配條件, 定位到真正的服務節點。並在這個轉發過程的前後,進行-些精細化控制。
predicate就是我們的匹配條件:而filter. 就可以理解為一個無所不能的攔截器。有了這兩個元素。再加上目標uri.就可以實現一個具體的路由了

Gateway工作流程

官網總結


客戶端向Spring Cloud Gateway發出請求。然後在Gateway Handler Mapping中找到與請求相匹配的路由,將其傳送到Gateway Web Handler。

Handler再通過指定的過濾器鏈來將請求傳送到我們實際的服務執行業務邏輯,然後返回。
過濾器之間用虛線分開是因為過濾器可能會在傳送代理請求之前( "pre” )或之後( "post" )執行業務邏輯。

Filter在"pre" 型別的過濾器可以做引數校驗、許可權校驗、流量監控、日誌輸出、協議轉換等,
在"post"型別的過濾器中可以做響應內容、響應頭的修改,日誌的輸出,流量監控等有著非常重要的作用。

核心邏輯

路由轉發+執行過濾器鏈

入門配置

新建Module

cloud-gateway-gateway9527

POM

<dependencies>
    <dependency><!-- 引用自己定義的api通用包,可以使用Payment支付Entity -->
        <groupId>com.eiletxie.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>
    <!--gateway-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--eureka client-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--熱部署-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

YML

server:
  port: 9527

spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

業務類

主啟動類

@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {

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

9527閘道器如何做路由對映呢???

cloud-provider-payment8001看看controller的訪問地址

  • get
  • lb

我們目前不想暴露8001埠,希望在8001外面套一層9527

YML新增閘道器配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 開啟從註冊中心動態建立路由的功能,利用微服務名稱j進行路由
      routes:
        - id: payment_route # 路由的id,沒有規定規則但要求唯一,建議配合服務名
         #匹配後提供服務的路由地址
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/** # 斷言,路徑相匹配的進行路由
        - id: payment_route2
          uri: http://localhost:8001
          predicates:
            Path=/payment/lb/** #斷言,路徑相匹配的進行路由

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

測試

啟動7001

啟動8001
cloud-provider-payment8001

啟動9527

訪問說明

  • 新增閘道器前
    http://localhost:8001/payment/get/31
  • 新增閘道器後
    http://localhost:9527/payment/get/31

YML配置說明

Gateway閘道器路由有兩種配置方式:

在配置檔案yaml中配置

見前面的步驟

程式碼中注入RouteLocator的Bean

官網案例

百度國內新聞網站,需要外網

https://news.baidu.com/guonei

自己寫一個
百度新聞
業務需求

通過9527閘道器訪問到外網的百度新聞網址

編碼

cloud-gateway-gateway9527

業務實現

package com.eiletxie.springcloud.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author EiletXie
 * @Since 2020/3/12 15:01
 */
@Configuration
public class GatewayConfig {

    /**
     * 配置了一個id為route-name的路由規則
     * 當訪問地址 http://localhost:9527/guonei時會自動轉發到地址: http://news.baidu.com/guonei
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        RouteLocatorBuilder.Builder routes = builder.routes();
        routes.route("path_route_eiletxie",
                r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }

    @Bean
    public RouteLocator customRouteLocator2(RouteLocatorBuilder builder) {
        RouteLocatorBuilder.Builder routes = builder.routes();
        routes.route("path_route_eiletxie2",
                r -> r.path("/guoji")
                        .uri("http://news.baidu.com/guoji")).build();
        return routes.build();
    }
}

config

通過服務名實現動態

預設情況下Gatway會根據註冊中心註冊的服務列表,以註冊中心上微服務名為路徑建立動態路由進行轉發,從而實現動態路由的功能

啟動:

一個eureka7001+兩個服務提供者8001/8002

POM

<dependency>
    <groupId>org.springframework.cloud</groupId>
     <artifactId≥spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

YML

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 開啟從註冊中心動態建立路由的功能,利用微服務名稱進行路由
      routes:
        - id: payment_route # 路由的id,沒有規定規則但要求唯一,建議配合服務名
          #匹配後提供服務的路由地址
          #uri: http://localhost:8001
          uri: lb://cloud-payment-service
          predicates:
            - Path=/payment/get/** # 斷言,路徑相匹配的進行路由

        - id: payment_route2
          #uri: http://localhost:8001
          uri: lb://cloud-payment-service
          predicates:
            Path=/payment/lb/** #斷言,路徑相匹配的進行路由

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

需要注意的是uri的協議lb,表示啟用Gateway的負載均衡功能.

lb://serverName是spring cloud#gatway在微服務中自動為我們建立的負載均衡uri

測試

http://localhost:9527/payment/lb
8001/8002兩個埠切換

Predicate

是什麼

啟動我們的gateway9527

Route Predicate Factories這個是什麼東東


Spring Cloud Gateway將路由匹配作為Spring WebFlux HandlerMapping基礎架構的一部分。
Spring Cloud Gateway包括許多內建的Route PredicateI廠。所有這些Predicate都與HTTP請求的不同屬性匹配。 多個Route Predicate工廠可以進行組合

Spring Cloud Gateway建立Route物件時,使用 RoutePredicateFactory建立Predicate 物件,Predicate 物件可以賦值給Route。Spring Cloud Gateway包含許多內建的Route Predicate Factories.

所有這些謂詞都匹配HTTP請求的不同屬性。多種謂詞工廠可以組合,並通過邏輯and。

常用的Route Predicate

1.After Route Predicate


2.Before Route Predicate

3.Between Route Predicate

不帶cookies訪問

帶上cookies訪問

加入curl返回中文亂碼
https://blog.csdn.net/leedee/article/details/82685636

5.Header Route Predicate


6.Host Route Predicate

7.Method Route Predicate

8.Path Route Predicate

9.Query Route Predicate

YML

10.RemoteAddr Route Predicate

11.Weight Route Predicate

小總結

ALL

說白了,Predicate就是為了實現一組匹配規則,讓請求過來找到對應的Route進行處理

Filter的使用

是什麼

Spring Cloud Gateway的filter

生命週期,Only Two

  • pre
  • post

種類,Only Two

GatewayFilter

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories

GlobalFilter

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#global-filters

常用的GatewayFilter

AddRequestParameter

YML

省略

自定義過濾器

自定義全域性GlobalFilter

兩個主要介面介紹

implments GlobalFilter,OrderId

能幹嘛

  • 全域性日誌記錄
  • 統一閘道器鑑權
  • .....

案例程式碼

package com.atguigu.springcloud.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Date;

/**
 * 全域性自定義過濾器
 *
 * @author zzyy
 * @version 1.0
 * @create 2020/03/06
 */
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("come in global filter: {}", new Date());

        ServerHttpRequest request = exchange.getRequest();
        String uname = request.getQueryParams().getFirst("uname");
        if (uname == null) {
            log.info("使用者名稱為null,非法使用者");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        // 放行
        return chain.filter(exchange);
    }

    /**
     * 過濾器載入的順序 越小,優先級別越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

測試

啟動

  • 正確:http://localhost:9527/payment/lb?uname=z3
  • 錯誤:http://localhost:9527/payment/lb