1. 程式人生 > >springcloud系列—Zuul—第5章-1: Spring Cloud Zuul 入門

springcloud系列—Zuul—第5章-1: Spring Cloud Zuul 入門

資料參考:《Spring Cloud 微服務實戰》

目錄

API閘道器服務:Spring Cloud Zuul

快速入門

構建閘道器

請求路由

傳統路由方式

面向服務的路由

請求過濾


API閘道器服務:Spring Cloud Zuul

通過前幾章的介紹,我們對於Spring Cloud Netflix 下的核心元件已經瞭解了一大半。這些元件基本涵蓋了微服務架構中最為基礎的幾個核心設施,利用這些元件我們已經可以構建起-一個簡單的微服務架構系統,比如,通過使用Spring Cloud Eureka實現高可用的服務註冊中心以及實現微服務的註冊與發現;通過Spring Cloud Ribbon或Feign實現服務間負載均衡的介面呼叫:同時,為了使分散式系統更為健壯,對於依賴的服務呼叫使用SpringCloud Hytrix來進行包裝,實現執行緒隔離並加入熔斷機制,以避免在微服務架構中因個別服務出現異常而引起級聯故障蔓延。通過上述思路,我們可以設計出類似下圖的基礎系統架構。

在該架構中,我們的服務叢集包含內部服務ServiceA和ServiceB,它們都會向EurekaServer叢集進行註冊與訂閱服務,而Open Service是-一個對外的RESTful API服務,它通過F5、Nginx等網路裝置或工具軟體實現對各個微服務的路由與負載均衡,並公開給外部的客戶端呼叫。.

在本章中,我們將把視線聚焦在對外服務這塊內容,通常也稱為邊緣服務。首先需要肯定的是,上面的架構實現系統功能是完全沒有問題的,但是我們還是可以進一步思考一下,這樣的架構是否還有不足的地方會使運維人員或開發人員感到痛苦。

           首先

,我們從運維人員的角度來看看,他們平時都需要做一些什麼工作來支援這樣的架構。當客戶端應用單擊某個功能的時候往往會發出-些對微服務獲取資源的請求到後端,這些請求通過F5、Nginx 等設施的路由和負載均衡分配後,被轉發到各個不同的服務例項上。而為了讓這些設施能夠正E確路由與分發請求,運維人員需要手工維護這些路由規則與服務例項列表,當有例項增減或是IP地址變動等情況發生的時候,也需要手工地去同步修改這些資訊以保持例項資訊與中介軟體配置內容的一致性。在系統規模不大的時候,維護這些資訊的工作還不會太過複雜,但是如果當系統規模不斷增大,那麼這些看似簡單的維護任務會變得越來越難,並且出現配置錯誤的概率也會逐漸增加。
很顯然,這樣的做法並不可取,所以我們需要一- 套機制來有效降低維護路由規則與服務例項列表的難度。

          其次,我們再從開發人員的角度來看看,在這樣的架構下,會產生一些怎樣的問題呢?大多數情況下,為了保證對外服務的安全性,我們在服務端實現的微服務介面,往往都會有一定的許可權校驗機制,比如對使用者登入狀態的校驗等:同時為了防止客戶端在發起請求時被篡改等安全方面的考慮,還會有一些簽名校驗的機制存在。這時候,由於使用了微服務架構的理念,我們將原本處於一個應用中的多個模組拆成了多個應用,但是這些應用提供的介面都需要這些校驗邏輯,我們不得不在這些應用中都實現這樣一套校驗邏輯。隨著微服務規模的擴大,這些校驗邏輯的冗餘變得越來越多,突然有一天我們發現這套校驗邏輯有個BUG需要修復,或者需要對其做一些擴充套件和優化,此時我們就不得不去每個應用裡修改這些邏輯,而這樣的修改不僅會引起開發人員的抱怨,更會加重測試人員的負擔。所以,我們也需要一套機制能夠很好地解決微服務架構中,對於微服務介面訪問時各前置校驗的冗餘問題。

 

為了解決上面這些常見的架構問題,API閘道器的概念應運而生。API 閘道器是一個更為智慧的應用伺服器,它的定義類似於面向物件設計模式中的Facade模式,它的存在就像是整個微服務架構系統的門面一樣,所有的外部客戶端訪問都需要經過它來進行排程和過濾。它除了要實現請求路由、負載均衡、校驗過濾等功能之外,還需要更多能力,比如與服務治理框架的結合、請求轉發時的熔斷機制、服務的聚合等一系列高階功能。

           既然API閘道器對於微服務架構這麼重要,那麼在Spring Cloud中是否有相應的解決方案呢?答案是很肯定的,Spring Cloud中了提供了基於Netfix Zuul實現的API閘道器元件——Spring Cloud Zuul。那麼,它是如何解決上面這兩個普遍問題的呢?

      首先,對於路由規則與服務例項的維護問題。Spring Cloud Zuul通過與Spring CloudEureka進行整合,將自身註冊為Eureka 服務治理下的應用同時從Eurcka中獲得了所有其他微服務的例項資訊。這樣的設計非常巧妙地將服務治理體系中維護的例項資訊利用起來,使得將維護服務例項的工作交給了服務治理框架自動完成,不再需要人工介入。而對於路由規則的維護,Zuul預設會將通過以服務名作為ContextPath的方式來建立路由對映,大部分情況下,這樣的預設設定已經可以實現我們大部分的路由需求,除了一些特殊情況(比如相容一些老的URL)還需要做--些特別的配置。但是相比於之前架構下的運維工作量,通過引入Spring Cloud Zuul實現API閘道器後,已經能夠大大減少了。
      其次,對於類似簽名校驗、登入校驗在微服務架構中的冗餘問題。理論上來說,這些校驗邏輯在本質上與微服務應用自身的業務並沒有多大的關係,所以它們完全可以獨立成一個單獨的服務存在,只是它們被剝離和獨立出來之後,並不是給各個微服務呼叫,而是在API閘道器服務上進行統一呼叫來對微服務介面做前置過濾,以實現對微服務介面的攔截和校。Spring Cloud Zuul 提供了一套過濾器機制,它可以很好地支援這樣的任務。開發者可以通過使用Zuul來建立各種校驗過濾器,然後指定那些規則的請求需要執行校驗邏輯,只有通過校驗的才會被路由到具體的微服務介面,不然就返回錯誤提示。通過這樣的改造,各個業務層的微服務應用就不再需要非業務性質的校驗邏輯了,這使得我們的微服務應用可以更專注於業務邏輯的開發,同時微服務的自動化測試也變得更容易實現。
      微服務架構雖然可以將我們的開發單元拆分得更為細緻,有效降低了開發難度,但是它所引出的各種問題如果處理不當會成為實施過程中的不穩定因素,甚至掩蓋掉原本實施微服務帶來的優勢。所以,在微服務架構的實施方案中,API閘道器服務的使用幾乎成為了必然的選擇。


      下面我們將詳細介紹Spring Cloud Zuul的使用方法、配置屬性以及一些不足之處和需要進行的思考。
 

快速入門

介紹了這麼多關於API閘道器服務的概念和作用,在這一節中,我們不妨用實際的示例來直觀的體驗一下Spring Cloud Zuul中封裝的API閘道器是如何使用和運作,並應用到微服務架構中去的。

構建閘道器

首先,在實現各種API閘道器服務的高階功能之前,我們需要做一些準備工作,比如,構建起最基本的API閘道器服務,並且搭建幾個用於路由和過濾使用的微服務應用等。對於微服務應用,我們可以直接使用之前章節實現的hello-service和feign-consumer.雖然之前我們一直將 feign-consumer視為消費者,但是在Eureka的服務註冊與發現體系中,每個服務既是提供者也是消費者,所以feign-consumer實質上也是一個服務提供者。之前我們訪問的http://localhost : 9001/feign-consumer等一系列介面就是它提供的服務。讀者也可以使用自己實現的微服務應用,因為這部分不是本章的重點,任何微服務應用都可以被用來進行後續的試驗。這裡,我們詳細介紹一下API閘道器服務的構建過程。

  • 建立一個基礎的Spring Boot過程,命名為api-gateway,並在pom.xml中引入spring-cloud-starter-zuul依賴,具體如下:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </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>

對於spring-cloud-starter-zuul 依賴,可以通過檢視它的依賴內容瞭解到:該模組中不僅包含了 Netflix Zuul 的核心依賴 zuul-core,它還包含下面這些閘道器服務的重要依賴。

  1.   spring-cloud-starter-hystrix:該依賴用來在閘道器服務中實現對微服務轉發時候的保護機制,通過執行緒隔離和斷路器,防止微服務的故障引發的故障引發API閘道器資源無法釋放,從而影響其他應用的對外服務。
  2.   spring-cloud-starter-ribbon:該依賴用來實現在閘道器服務進行路由轉發時候的客戶端負載均衡以及請求重試。
  3.   spring-boot-starter-actuator:該依賴用來提供常規的微服務管理端點。另外,在Spring Cloud Zuul 中還特別提供了 /routes 端點來返回當前的所有路由規則。
  • 建立應用主類,使用@EnableZuulProxy註解開啟Zuul的API閘道器服務。
@SpringBootApplication
@EnableZuulProxy
public class GatewayApplication {

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

}
  • 在application.properties中配置zuul閘道器服務的配置資訊,比如 應用名,服務埠號等,具體如下:
spring.application.name=api-gateway
server.port=5555

完成上面的工作,通過Zuul實現API閘道器服務就構建完畢了。

請求路由

下面,我們將通過一個簡單的示例來為上面構建的閘道器服務增加請求路由的功能。為了演示請求路由的功能,我們先將之前準備的Eureka服務註冊中心和微服務應用都啟動起來。此時,我們在Eureka資訊面板中可以看到如下圖所示的兩個微服務應用已經被註冊成功了。

傳統路由方式

使用Spring Cloud Zuul實現路由功能很簡單,只需要對api-gateway服務增加一些關於路由的配置,就你實現傳統的路由轉發功能,比如:

#傳統請求路由
zuul.routes.hello.path=/api-gateway/**
zuul.routes.hello.url=http://localhost:1113/

該配置定義了發往API閘道器服務的請求中,所有符合/api-gateway/**規則的訪問都將被路由轉發到http://localhost:1113/ 地址上,也就是說,當我們訪問http://localhost :5555/api-gatewayrl/hello的時候,API閘道器服務會將該請求路由到http://localhost:5555/hello 提供的微服務介面上。其中,配置屬性zuul . routes.api-a-url.path中的api-a-url部分為路由的名字,可以任意定義,但是一-組path和url對映關係的路由名要相同,下面將要介紹的面向服務的對映方式也是如此。
 

面向服務的路由

很顯然,傳統路由的配置方式對我們來說並不友好,它同樣需要運維人員花費大量時間來維護path和url的關係。為了解決這個問題,Spring Cloud Zuul實現了與Spring Cloud Eureka的無縫整合,我們可以讓路由的path不是對映具體的url,而是對映到某個具體的服務,而具體的url交給Spring Cloud Eureka的服務發現機制去自動維護,這就是面向服務的路由。在上面構建閘道器的示例中使用的就是面向服務的路由。使用這種方式必須注意幾點:

  • 為了與Eureka整合,在api-gateway中引入eureka依賴:
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            <version>2.0.0.RC2</version>
        </dependency>
  • 在api-gateway的配置檔案中,對eureka進行配置,並且配置服務路由,具體如下:
#eureka配置
eureka.client.serviceUrl.defaultZone=http://eureka1:1111/eureka

# 服務路由配置
zuul.routes.api-gateway.path=/api-gateway-service/**
zuul.routes.api-gateway.service-id=PROVIDER-EUREKA

zuul.routes.api-gateway2.path=/api-gateway-service2/**
zuul.routes.api-gateway2.service-id=FEIGN-CONSUMER

針對我們之前準備的兩個微服務應用,我們在路由定義中分別配置了兩個路由去對映他們。另外,通過eureka,zuul閘道器服務網也將自己註冊到服務註冊中心。除了將自己註冊成服務,也能獲取到服務例項清單,用來實現服務路由的對映。

 

在完成上面的配置,我們將所有服務啟動,可以在註冊中心看到:

1:訪問http://localhost:5555/api-gateway/hello,服務傳統路由規則,符合

zuul.routes.hello.path=/api-gateway/**
zuul.routes.hello.url=http://localhost:1113/

轉發到http://localhost:1113/hello

2:訪問http://localhost:5555/api-gateway2/index,服務傳統路由規則,符合

zuul.routes.hello2.path=/api-gateway2/**
zuul.routes.hello2.url=http://localhost:1115/

轉發到http://localhost:1115/index

其他服務路由配置就不一一測試了

 

請求過濾

在實現了請求路由功能之後,我們的微服務應用提供的介面就可以通過統一的API閘道器入口被客戶端訪問到了。但是,每個客戶端使用者請求微服務的應用提供的介面時,它們的訪問許可權往往都有一定的限制,系統並不會將所有的微服務介面都對它們開發。然而,目前的服務路由並沒有限制許可權這樣的功能,所有請求都會毫無保留地轉發到具體的應用並返回結果,為了實現對客戶端請求的安全校驗和許可權控制,最簡單和粗暴的方法就是為每個微服務應用都實現一套用於校驗簽名和鑑別許可權的過濾器或攔截器。不過,這樣的做法並不可取,它會增加日後系統的維護難度,因為同一個系統的各種校驗邏輯很多情況下都是大致相同或類似的,這樣的實現方式使得相似的校驗邏輯程式碼被分散到了各個微服務中去,冗餘程式碼的出現是我們不希望看到的。所以,比較好的做法是將這些校驗邏輯剝離出去,構建出一個獨立的鑑權服務。在完成剝離之後,有不少開發者會直接再微服務應用中通過呼叫鑑權服務來實現校驗,但是這樣的做法僅僅只是解決了許可權邏輯的分離,並沒有在本質上將這部分不屬於冗餘的邏輯從原有的微服務應用中拆分出,冗餘的攔截器或過濾器依然會存在。
       對於這樣的問題,更好的做法是通過前置的閘道器服務來完成這些非業務性質的校驗。由於閘道器服務的假如,外部客戶端訪問我們的系統已經有了統一入口,既然這些校驗與具體業務無關,那何不在請求到達的時候就完成校驗和過濾,而不是轉發後再過濾而導致更長的請求延遲。同時,通過在閘道器中完成校驗和過濾,微服務應用端就可以去除各種複雜的過濾器和攔截器了,這使得微服務應用介面的開發和測試複雜度得到了相應降低。
      為了再API閘道器中實現對客戶端請求的校驗,我們將繼續介紹Spring Cloud Zuul的另一個核心功能:請求過濾。Zuul允許開發者在API閘道器上通過定義過濾器來實現對請求的攔截與過濾,實現的方法非常簡單,我們之需要繼承ZuulFilter抽象類並實現它定義的4個抽象函式就可以完成對請求的攔截和過濾了。
     下面的程式碼定義了一個簡單的Zuul過濾器,它實現了在請求被路由之前檢查HttpServletRequest中是否有accessToken引數,若有就進行路由,若沒有就拒絕訪問,返回401 Unauthorized錯誤。

public class AccessFilter extends ZuulFilter {
    private static Logger logger = LoggerFactory.getLogger(AccessFilter.class);

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

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

    /**
     * 請求被路由之前檢查HttpServletRequest中是否有accessToken引數,若有就進行路由,若沒有就拒絕訪問,返回401 Unauthorized錯誤。
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        logger.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());
        Object accessToken = request.getParameter("accessToken");
        if (accessToken == null) {
            logger.warn("access token is empty");
            context.setSendZuulResponse(false);
            context.setResponseStatusCode(401);
            return null;
        }
        logger.info("access token ok");
        return null;
    }
}

在上面實現都過濾器程式碼中,我們通過繼承ZuulFilter抽象類並重寫下面4個方法來實現自定義都過濾器。這4個方法分別定義瞭如下內容。

  • filterType:過濾器都型別,它決定過濾器在請求都哪一個生命週期中執行。這裡定義為pre,代表會在請求被路由之前執行。
  • filterOrder:過濾器的執行順序。當請求在一個階段中存在多個過濾器時,需要根據該方法返回都值來依次執行。
  • shouldFilter:判斷該過濾器是否需要被執行。這裡我們直接返回了true,因此過濾器對所有請求都會生效。實際運用中我們可以利用該函式來指定過濾器的有效範圍。
  • run:過濾器的具體邏輯。這裡我們通過context.setSendZuulResponse(false)令zuul過濾該請求,不對其進行路由,然後也可以進一步優化我們的返回,比如,通過context.setResponseBody(body)對返回的body內容進行編輯等。

 在實現了自定義過濾器之後,它並不會直接生效,我們還需要為其建立具體的Bean才能啟動該過濾器,比如,在應用啟動類中增加如下內容:

@SpringBootApplication
@EnableZuulProxy
public class GatewayApplication {

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

    @Bean
    public AccessFilter getAccessFilter(){
        return new AccessFilter();
    }
}

當然你也可以在AccessFilter使用@Component掃描載入成元件。在對api-gateway-zuul服務完成了上面的改造之後,我們可以重新啟動它,併發起下面的請求,對上面定義的過濾器做一個驗證。

到這裡對於API閘道器服務的快速入門示例就完成了。通過對Spring Cloud Zuul兩個核心功能的介紹,相信讀者已經能夠體會到API閘道器服務對微服務架構到重要性了,就目前掌握到API閘道器知識,我們可以將具體原因總結如下:

  • 它作為系統到統一入口,遮蔽了系統內部各個微服務的細節。
  • 它可以與服務治理框架結合,實現自動化的服務例項維護以及負載均衡陸游轉發。
  • 它可以實現介面許可權校驗與微服務業務邏輯到解耦。
  • 通過服務閘道器中到過濾器,在各生命週期中去校驗請求到內容,將原本在對外服務層做到校驗前移,保證了微服務的無狀態性,同時降低了微服務到測試難度,讓服務本身集中關注業務邏輯的處理

 實際上,基於Spring Cloud Zuul實現到API閘道器服務除了上面所示的優點之外,它還有一些更加強大到功能,我們將在後面對其進行更深入的介紹。通過本節的內容,我們只是希望以一個簡單到例子帶領大家先來簡單認識一下API閘道器服務提供的基礎功能以及它在微服務架構中的重要地位。