第6章 微服務的大門誰來守

程式設計師韓斌發表於2021-01-01

目錄

6.1 使用Spring Cloud Zuul構建基礎閘道器

6.2 Zuul的一些常用配置

6.2.1 自定義請求路徑

6.2.2 配置靜態 URL 路由

6.2.3 服務超時

6.3 過濾器

6.4 小結


截止目前,我們已經構建了Eureka註冊中心,customer和order微服務,並且建立了配置中心,讓微服務可以遠端獲取配置中心的配置項,同時,我們還研究了微服務之間是如何不通過域名來進行服務間呼叫。本章我們要通過外部來呼叫微服務,在本章,我們要為我們的微服務系統樹起一道大門,使用Spring Cloud Netflix的Zuul構建一道閘道器。

服務閘道器最基本的功能是用來作為服務客戶端和被呼叫服務之間的中介,為微服務系統新增閘道器,可以讓服務的客戶端請求只直接與閘道器服務進行互動對話,至於具體的服務介面則由閘道器根據配置規則進行自動選擇。有了閘道器服務,所有的客戶端請求都應該流經閘道器服務。

6.1 使用Spring Cloud Zuul構建基礎閘道器

首先,我們建立一個Spring Boot的工程,在pom檔案中包含如下內容:

注意:這裡的spring-cloud.version是Greenwich.RELEASE。Finchley.SR2的zuul有Bug,可以自行修改測試下。

<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

</dependencies>
<dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>

</dependencyManagement>

在application.properties中新增下面的配置項:

Eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
spring.cloud.config.fail-fast=true
spring.application.name=gateway
server.port=8003
spring.cloud.config.uri=http://localhost:8888
spring.cloud.config.name=gateway
spring.cloud.config.profile=dev
management.endpoints.web.exposure.include=*
spring.main.allow-bean-definition-overriding=true

最後,在啟動類新增@EnableZuulProxy(另一個@EnableZuulServer註解只會建立一個Zuul伺服器,不會載入Zuul的任何過濾器,本系列不涉及。),啟動專案。

啟動之前我們建立的customer,order,eureka-server。開啟Eureka的註冊頁面。可以看到如圖6.1所示的資訊。

1336be7bed6b33ea030b6449e4067527.png
圖6.1 加入了閘道器後的Eureka註冊頁面

閘道器作為系統中的一個微服務存在。同時,它又可以獨立於其他微服務存在,作為其他微服務的前置來進行服務訪問,將Zuul作為一個微服務註冊道Eureka Server上,開發人員可以通過Eureka Server動態地進行服務例項的新增和刪除,在這個過程中,不需要對Zuul進行任何修改以及配置,Zuul在不斷與Eureka Server進行通訊的過程中,會獲取到最新的例項資訊,並在Zuul中進行快取。

我們之前在order中提供了介面/users/{userId},這個介面可以通過RIbbon進行服務間的訪問,並且返回使用者資訊。直接通過order微服務訪問的話,完整地址是:http://localhost:8002/users/{userId}。現在我們要通過閘道器訪問這個介面,地址應該是http://localhost:8003/order/users/{userId}。瀏覽器中訪問,可以看到如圖6.2所示的結果。

6f469717c5693880abd11169e4733b7c.png
圖6.2 通過閘道器訪問order微服務介面

 

6.2 Zuul的一些常用配置

6.2.1 自定義請求路徑

當瀏覽器中輸入http://localhost:8003/order/users/100001時,Zuul能夠通過微服務在Eureka中註冊的instanceId訪問到對應的微服務。但有時候,我們會要求對這個服務名進行隱藏,比如用o代替order這個instanceId。這時候,只需要在配置檔案中新增如下配置:

zuul.routes.order=/o/**

重啟閘道器,瀏覽器中輸入http://localhost:8003/o/users/10000,請求會被路由到order微服務的對應介面,可以看到如圖6.3所示的結果。

8bcaa99a3d9fc378e566f936d2ae6749.png
圖6.3 自定義請求路徑訪問

 

注意:這個時候,http://localhost:8003/order/users/100001
也是可以正常訪問的。同時,routes端點可以用來檢視當前Zuul的路由規則,這個端點是Zuul獨有的。

同時,訪問routes端點可以看到閘道器當前的對映關係,如圖6.4所示。

681c593547ef18b75ea8d04b397fa30c.png
圖6.4 Zuul的routes端點

 

閘道器作為多個微服務的前置服務,不但要對接收到的請求進行攔截、轉發,同時,還可以對某些特定的服務進行忽略,這個配置需要使用配置項zuul.ignored-services。我們在閘道器的配置檔案中新增配置項:

zuul.ignored-services=order

重啟服務,檢視閘道器服務的 routes 斷點,可以看到圖6.5的結果。

e1fb6119def6a4aad5ebe1a9379a5058.png
圖6.5 過濾掉 order 服務的預設路由配置

 

此時,http://localhost:8003/order/users/100001不能正常訪問,但是http://localhost:8003/o/users/100001可以正常獲取結果。

zuul.ignored-services可以直接配置成*,然後通過zuul.routes.xxx=yyy來配置生效的動態路由,這種方式類似於我們平常開發中的『白名單』。

最後,再介紹一個比較有用的配置項:zuul.prefix,這個配置主要用來為服務呼叫新增字首。比如我們想通過
http://localhost:8003/api/o/users/100001來訪問服務,那麼就可以在閘道器的配置檔案中新增配置項:

zuul.prefix=/api

可以看到閘道器的routes 端點變成了圖6.6的樣子。

ca78f7a859d19d98ec9108b6d930f9fe.png
圖6.6 為所有的閘道器呼叫新增介面字首

 

6.2.2 配置靜態 URL 路由

還有一種比較常見的需求,在某些場景下,我們可能需要將某個請求路由到一個非本系統的固定地址上去,這個地址不註冊在本系統的
Eureka Server 上,所以不能通過前面講到的方式去配置。

比如我們要在請求 http://localhost:8003/api/baidu/xxx時路由到地址
https://www.baidu.com/s?wd=xxx。就可以使用本節要講到的靜態 URL
路由配置。在閘道器的配置檔案中新增如下配置:

zuul.routes.user.path=/baidu/**
zuul.routes.user.url=http://localhost:8002/users/

重啟閘道器,配置生效後,檢視閘道器routes端點,可以看到如圖6.7所示結果:

769011d1afb441042e29f772867c75cd.png
圖6.7 配置靜態URL 路由後檢視 routes 端點

在瀏覽器位址列輸入地址http://localhost:8003/api/user/123,會自動跳轉到http://localhost:8002/users/123。通過這樣的配置,就可以讓某類請求直接路由到對應的外部請求介面上。

6.2.3 服務超時

Zuul 使用了 Hystrix 和
Ribbon來防止長時間執行的服務呼叫影響到閘道器的整體效能。預設情況,對於任何一個需要執行超過1s的請求來說,會觸發 Hystrix 超時,呼叫將被禁止並且返回 HTTP 500的錯誤。當然,在某些時候,這個1s 有點短,我們就可以通過 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds來自定義Hystrix的超時時間。

比如hystrix.command.default.execution.isolation.thread.timeoutInMillisecond=3000,就將Hystrix 的預設超時時間改成了3s。這裡改的是 Hystrix的全域性預設超時時間,某些時候,我們只需要對某些特定的服務進行超時時間自定義,只需要使用服務在 Eureka 中註冊的instanceId替換上面配置項中的 default即可。比如hystrix.command.order.execution.isolation.thread.timeoutInMillisecond=3000便是將 order 服務的 Hystrix 超時時間修改成了3s,其他服務還是預設的1s。

上面的超時時間是 Hystrix 的預設熔斷時間(熔斷這個概念我們會在後面專門講述),Ribbon的呼叫同樣存在超時可能。Ribbon 的呼叫超時預設為5s(對於微服務來說,5s 其實已經很長了。所以這個配置項不建議大家修改。)。可以使用 serviceId.ribbon.ReadTimeout=10000來配置閘道器的 Ribbon 超時時間為10s。

6.3 過濾器

閘道器作為所有的服務的入口必經服務,在提供路由基本路由功能的前提下,更重要的一個功能是可以很方便地編寫針對所有流經閘道器服務的自定義邏輯,這個時候,才是閘道器發揮真正威力的時候。一般來說,這種自定義邏輯類似於一個“橫切面”,可以方便地對安全性、日誌進行控制,甚至於對所有服務進行跟蹤。

在Zuul中一般使用過濾器實現這種自定義邏輯。

Zuul支援以下四種型別的過濾器。

  • pre過濾器:在Zuul將請求實際傳送到目的微服務之前呼叫。前置過濾器多用來進行安全控制。
  • post過濾器:在目標微服務被呼叫後還沒將響應返回客戶端時呼叫。後置過濾器一般用來處理返回資料,可以對資料進行一些特定修改。
  • route過濾器:在目標微服務被呼叫前呼叫。一般用來實現自定義的路由策略,可以用來實現動態路由,實現灰度路由等功能。
  • error過濾器:在處理請求傳送錯誤時被呼叫。

我們常用主要是pre、post和route三種過濾器。

通過下面一段程式碼可以看到如何定義不同型別的過濾器:

package cn.com.hanbinit.gateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

@Component
public class CustomFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

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

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        System.out.println("preFilter...");
        return null;
    }
}

在Zuul中建立filter,一定要先繼承ZuulFilter類,並實現上面程式碼中的四個方法。不同的型別主要取決於filterType的返回值。可以通過檢視com.netflix.zuul.ZuulFilter中的filterType方法註釋來了解具體的支援型別。

to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering, "route" for routing to an origin, "post" for post-routing filters, "error" for error handling. We also support a "static" type for static responses see StaticResponseFilter.Any filterType made be created or added and run by calling FilterProcessor.runFilters(type) @return A String representing that type.

通過註釋可以看到,這裡通過返回pre,route,post,以及和static來分別指定不同的攔截器型別。

shouldFilter方法根據返回值來確認當前的攔截器是否生效,這裡可以通過外部的配置項來指定,也可以加入一些特殊的業務邏輯來判斷是否要啟用當前攔截器。

filterOrder返回int數字,標識當前攔截器的優先順序,數字越小,級別越高。

最後的run方法中要實現的便是攔截器的自定義邏輯了。開動大腦,這裡可以乾的事情太多太多。

6.4 小結

本節簡單介紹了Spring Cloud Zuul,作為微服務系統的閘道器,Zuul通過一些列的攔截器可以完成很多的工作。新版本的Spring Cloud中推薦使用的Spring Cloud Gateway 後面再單獨聊。

相關文章