SpringCloud升級之路2020.0.x版-41. SpringCloudGateway 基本流程講解(1)


本系列代碼地址:https://github.com/JoJoTec/spring-cloud-parent

接下來,將進入我們升級之路的又一大模塊,即網關模塊。網關模塊我們廢棄了已經進入維護狀態的 zuul,選用了 Spring Cloud Gateway 作為內部網關。為何選擇 Spring Cloud Gateway 而不是 nginx 還有 Kong 的原因是:

  1. 項目組對於 Java 更加熟悉,並且對於 Project Reactor 異步編程也比較熟悉,這個比較重要
  2. 需要在網關中使用我們之前實現的基於請求的有狀態重試的壓力敏感的負載均衡器
  3. 需要在網關中實現重試
  4. 需要在網關中實現實例路徑斷路
  5. 需要在網關中進行業務統一加解密
  6. 需要在網關中實現 BFF(Backends For Frontends)接口,即根據客戶端請求,將某幾個不同接口的請求一次性組合返回
  7. 需要在網關中使用 Redis 記錄一些與 Token 相關的值

因此,我們使用了 Spring Cloud Gateway 作為內部網關,接下來,我們就來依次實現上面說的這些功能。同時在本次升級使用過程中, Spring Cloud Gateway 也有一些坑,例如:

  1. 結合使用 spring-cloud-sleuth 會有鏈路信息追蹤,但是某些情況鏈路信息會丟失
  2. 對於三方 Reactor 封裝的異步 API (例如前面提到的操作 Redis 使用的 spring-data-redis)理解不到位導致關鍵線程被占用

但是首先,我們需要簡單理解下 Spring Cloud Gateway 究竟包括哪些組件以及整個調用流程是什么樣子的。由於 Spring Cloud Gateway 基於 Spring-Boot 和 Spring-Webflux 實現,所以我們會從外層 WebFilter 開始說明,然后分析如何走到 Spring Cloud Gateway 的封裝邏輯,以及 Spring Cloud Gateway 包含的組件,請求是如何轉發出去,回來后又經過了哪些處理,這些我們都會逐一分析。

創建一個簡單的 API 網關

為了詳細分析流程,我們先來創建一個簡單的網關,用於快速上手並分析。

首先創建依賴:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>spring-cloud-parent</artifactId>
		<groupId>com.github.jojotech</groupId>
		<version>2020.0.3-SNAPSHOT</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>

	<artifactId>spring-cloud-api-gateway</artifactId>

	<dependencies>
		<dependency>
			<groupId>com.github.jojotech</groupId>
			<artifactId>spring-cloud-webflux</artifactId>
			<version>${project.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
		</dependency>
	</dependencies>
</project>

parent 指向了我們項目的 spring-cloud-parent,同時加入了上一節實現的 spring-cloud-webflux 依賴,同時還需要加入 spring-cloud-starter-gateway,由於在我們的 spring-cloud-parent 已經指定了 spring-cloud-parent 的版本依賴管理,所以這里不需要指定 spring-cloud-starter-gateway 的版本

然后,我們開始編寫配置文件:

application.yml

server:
  ##端口為 8181
  port: 8181
spring:
  application:
    # 微服務名稱是 apiGateway
    name: apiGateway
  cloud:
    gateway:
      httpclient:
        # 網關轉發到其他微服務的 HTTP 連接超時為 500ms
        connect-timeout: 500
        # 網關轉發到其他微服務的 HTTP 響應超時為 500ms
        response-timeout: 60000
      routes:
        # 編寫轉發規則
        - id: first_route
          # 轉發到微服務 test-service
          uri: lb://test-service
          # 包含哪些路徑
          predicates:
            - Path=/test-ss/**
          #  轉發到的微服務訪問路徑,去掉路徑中的第一塊,即去掉 /test-ss
          filters:
            - StripPrefix=1
    loadbalancer:
      # 指定 zone,因為我們之前在負載均衡中加入了只有同一個 zone 的實例才能互相訪問的邏輯
      zone: test
      ribbon:
        # 關閉ribbon
        enabled: false
      cache:
        # 本地微服務實例列表緩存時間
        ttl: 5
        # 緩存大小,你的微服務調用多少個其他微服務,大小就設置為多少,默認256
        capacity: 256
    discovery:
      client:
        simple:
          # 使用 spring-common 中的簡單 DiscoveryClient 服務發現客戶端,就是將微服務實例寫死在配置文件中
          instances:
            # 指定微服務 test-service 的實例列表
            test-service:
              - host: httpbin.org
                port: 80
                metadata:
                  # 指定該實例的 zone,因為我們之前在負載均衡中加入了只有同一個 zone 的實例才能互相訪問的邏輯 
                  zone: test
eureka:
  client:
    # 關掉 eureka
    enabled: false

最后編寫啟動入口類:

package com.github.jojotech.spring.cloud.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.github.jojotech.spring.cloud.apigateway")
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

啟動,訪問路徑: http://127.0.0.1:8181/test-ss/anything,可以看到請求被發送到 httpbin.org 的 anything 路徑中,這個接口會返回請求中的所有信息。

這樣,我們就實現了一個簡單的網關。接下來我們來詳細分析其工作流程和源碼。

異步環境下請求處理的核心 - Spring Boot + Spring WebFlux 的 WebHandler

我們創建的簡易網關,外層的服務容器其實就是基於 Netty 和 Project Reactor 的容器,我們跳過這些,直接進入 Spring Boot 相關的處理邏輯。我們只需要知道,請求和其對應的響應,會被外層的容器封裝成為 ServerHttpRequest requestServerHttpResponse response(都在 org.springframework.http.server.reactive 這個包下)。

然后,會交由 WebHandler 進行處理。WebHandler 的實現,其實是一種責任鏈裝飾模式,如下圖所示。每一層的 WebHandler 會將 requestresponse 進行對應自己責任的裝飾,然后交給內層的 WebHandler 處理。

image

HttpWebHandlerAdapter - 將請求封裝成 ServerWebExchange

WebHandler 的接口定義是:

public interface WebHandler {
	Mono<Void> handle(ServerWebExchange exchange);
}

但是最外層傳進來的參數是 requestresponse,需要將他們封裝成 ServerWebExchange,這個工作就是在 HttpWebHandlerAdapter 中做的。HttpWebHandlerAdapter 其實主要任務就是將各種參數封裝成 ServerWebExchange(除了和本次請求相關的 requestresponse,還有會話管理器 SessionManager,編碼解碼器配置,國際化配置還有 ApplicationContext 用於擴展)。

除了這些,處理 Forwarded 還有 X-Forwarded* 相關的 Header 的配置邏輯,也在這里進行。然后將封裝好的 ServerWebExchange 交給內層的 WebHandlerExceptionHandlingWebHandler 繼續處理。同時,從源碼中可以看出,交給內層處理的 Mono 還加入了異常處理和記錄響應信息的邏輯:

HttpWebHandlerAdapter.java

//交給內層處理封裝好的 `ServerWebExchange`
return getDelegate().handle(exchange)
        //記錄響應日志,trace 級別,一般用不上
		.doOnSuccess(aVoid -> logResponse(exchange))
		//處理內層沒有處理的異常,一般不會走到這里
		.onErrorResume(ex -> handleUnresolvedError(exchange, ex))
		//在所有處理完成后,將 response 設為 complete
		.then(Mono.defer(response::setComplete));

剩下的內層的 WebHandler,我們將在下一節中繼續分析

微信搜索“我的編程喵”關注公眾號,每日一刷,輕松提升技術,斬獲各種offer


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM