1. 程式人生 > >SpringCloudAlibaba-服務閘道器Gateway

SpringCloudAlibaba-服務閘道器Gateway

一:閘道器簡介

在微服務架構中,一個系統會被拆分為很多個微服務。那麼作為客戶端要如何去呼叫這麼多的微服務呢?如果沒有閘道器的存在,我們只能在客戶端記錄每個微服務的地址,然後分別去呼叫。這樣的話會產生很多問題,例如:

  • 客戶端多次請求不同的微服務,增加客戶端程式碼或配置編寫的複雜性
  • 認證複雜,每個微服務都有獨立認證
  • 存在跨域請求,在一定場景下處理相對複雜

為解決上面的問題所以引入了閘道器的概念:所謂的API閘道器,就是指系統的統一入口,它封裝了應用程式的內部結構,為客戶端提供統一服務,一些與業務本身功能無關的公共邏輯可以在這裡實現,諸如認證、鑑權、監控、路由轉發等。

比較流行的閘道器對比

  • Nginx+lua

  使用nginx的反向代理和負載均衡可實現對api伺服器的負載均衡及高可用lua是一種指令碼語言,可以來編寫一些簡單的邏輯, nginx支援lua指令碼

  • Kong

  基於Nginx+Lua開發,效能高,穩定,有多個可用的外掛(限流、鑑權等等)可以開箱即用。 問題:只支援Http協議;二次開發,自由擴充套件困難;提供管理API,缺乏更易用的管控、配置方式。

  • Zuul

  Netflix開源的閘道器,功能豐富,使用JAVA開發,易於二次開發 問題:缺乏管控,無法動態配置;依賴元件較多;處理Http請求依賴的是Web容器,效能不如Nginx

  • Spring Cloud Gateway

  Spring公司為了替換Zuul而開發的閘道器服務,將在下面具體介紹。

二:Gateway簡介

Spring Cloud Gateway是Spring公司基於Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的閘道器,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。它的目標是替代Netflix Zuul,其不僅提供統一的路由方式,並且基於 Filter 鏈的方式提供了閘道器基本的功能,例如:安全,監控和限流。

  • 優點:
  1. 效能強勁:是第一代閘道器Zuul的1.6倍
  2. 功能強大:內建了很多實用的功能,例如轉發、監控、限流等
  3. 設計優雅,容易擴充套件
  • 缺點:
  1. 其實現依賴Netty與WebFlux,不是傳統的Servlet程式設計模型,學習成本高
  2. 不能將其部署在Tomcat、Jetty等Servlet容器裡,只能打成jar包執行
  3. 需要Spring Boot 2.0及以上的版本,才支援

三:Gateway核心架構

3.1:基本概念

路由(Route) 是 gateway 中最基本的元件之一,表示一個具體的路由資訊載體。主要定義了下面的幾個資訊:

  •  id:路由標識、區別於其他route
  • uri:路由指向的目的地uri,即客戶端請求最終被轉發到的微服務
  • order:用於多個route之間的排序,數值越小排序越靠前,匹配優先順序越高
  • predicate:斷言的作用是進行條件判斷,只有斷言都返回真,才會真正的執行路由 
  • filter:過濾器用於修改請求和響應資訊

3.2:執行流程

  1. Gateway Client向Gateway Server傳送請求
  2. 請求首先會被HttpWebHandlerAdapter進行提取組裝成閘道器上下文
  3. 然後閘道器的上下文會傳遞到DispatcherHandler,它負責將請求分發給RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping負責路由查詢,並根據路由斷言判斷路由是否可用
  5. 如果過斷言成功,由FilteringWebHandler建立過濾器鏈並呼叫
  6. 請求會一次經過PreFilter--微服務--PostFilter的方法,最終返回響應

四:Gateway快速入門

4.1:建立一個api-gateway模組,匯入相關依賴

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-alibaba</artifactId>
        <groupId>com.chenpt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>api-gateway</artifactId>

    <dependencies>
        <!--nacos客戶端-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--gateway閘道器-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
        </dependency>
    </dependencies>

</project>
完整版pom.xml

4.2:建立啟動類

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {

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

4.3:新增配置檔案

server:
  port: 7000
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:                       # 路由陣列[路由 就是指定當請求滿足什麼條件的時候轉到哪個微服務]
       - id: product_route          # 當前路由的標識, 要求唯一
         uri: lb://service-product  # lb指的是從nacos中按照名稱獲取微服務,並遵循負載均衡策略
         predicates:                # 斷言(就是路由轉發要滿足的條件)
          - Path=/shop-pro/**       # 當請求路徑滿足Path指定的規則時,才進行路由轉發
         filters:                   # 過濾器,請求在傳遞過程中可以通過過濾器對其進行一定的修改
          - StripPrefix=1           # 轉發之前去掉1層路徑
       - id: order_route
         uri: lb://service-order
         predicates:
          - Path=/shop-order/**
         filters:
          - StripPrefix=1
application.yml

4.4:測試

 

 

 五:閘道器限流

採用前面學過的Sentinel元件來實現閘道器的限流

5.1:匯入依賴

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>         

5.2:編寫配置類

基於Sentinel 的Gateway限流是通過其提供的Filter來完成的,使用時只需注入對應的SentinelGatewayFilter例項以及 SentinelGatewayBlockExceptionHandler 例項即可。

@Configuration
public class GatewayConfig {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfig(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolvers;
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    // 初始化一個限流的過濾器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    // 配置初始化的限流引數
    @PostConstruct
    public void initGatewayRules() {
//        Set<GatewayFlowRule> rules = new HashSet<>();
//        rules.add(
//                new GatewayFlowRule("product_route") //資源名稱,對應路由id
//                        .setCount(1) // 限流閾值
//                        .setIntervalSec(1) // 統計時間視窗,單位是秒,預設是 1 秒
//        );
//        GatewayRuleManager.loadRules(rules);


        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(new GatewayFlowRule("product_route").setCount(1).setIntervalSec(1));
        rules.add(new GatewayFlowRule("order_route").setCount(1).setIntervalSec(1));
        GatewayRuleManager.loadRules(rules);
    }

    // 配置限流的異常處理器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }

    // 自定義限流異常頁面
    @PostConstruct
    public void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map map = new HashMap<>();
                map.put("code", 0);
                map.put("message", "介面被限流了");
                return ServerResponse.status(HttpStatus.OK).
                        contentType(MediaType.APPLICATION_JSON_UTF8).
                        body(BodyInserters.fromObject(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }

}
GatewayConfig

5.3:測試

&n