Spring Cloud Zuul 快速入門

服務網關和Zuul

爲何要有服務網關:前端

咱們都知道在微服務架構中,系統會被拆分爲不少個微服務。那麼做爲客戶端要如何去調用這麼多的微服務呢?難道要一個個的去調用嗎?很顯然這是不太實際的,咱們須要有一個統一的接口與這些微服務打交道,這就是咱們須要服務網關的緣由。java

咱們已經知道,在微服務架構中,不一樣的微服務能夠有不一樣的網絡地址,各個微服務之間經過互相調用完成用戶請求,客戶端可能經過調用N個微服務的接口完成一個用戶請求。好比:用戶查看一個商品的信息,它可能包含商品基本信息、價格信息、評論信息、折扣信息、庫存信息等等,而這些信息獲取則來源於不一樣的微服務,諸如產品系統、價格系統、評論系統、促銷系統、庫存系統等等,那麼要完成用戶信息查看則須要調用多個微服務,這樣會帶來幾個問題:nginx

  1. 客戶端屢次請求不一樣的微服務,增長客戶端代碼或配置編寫的複雜性
  2. 認證繁雜,訪問每一個服務都要進行一次認證
  3. 每一個服務都經過http訪問,致使http請求增長,效率不高拖慢系統性能
  4. 多個服務存在跨域請求問題,處理起來比較複雜

以下圖所示:
Spring Cloud Zuul 快速入門git

咱們該如何解決這些問題呢?咱們能夠嘗試想一下,不要讓前端直接知道後臺諸多微服務的存在,咱們的系統自己就是從業務領域的層次上進行劃分,造成多個微服務,這是後臺的處理方式。對於前臺而言,後臺應該仍然相似於單體應用同樣,一次請求便可,因而咱們能夠在客戶端和服務端之間增長一個API網關,全部的外部請求先經過這個微服務網關。它只需跟網關進行交互,而由網關進行各個微服務的調用。web

這樣的話,咱們就能夠解決上面提到的問題,同時開發就能夠獲得相應的簡化,還能夠有以下優勢:spring

  1. 減小客戶端與微服務之間的調用次數,提升效率
  2. 便於監控,可在網關中監控數據,能夠作統一切面任務處理
  3. 便於認證,只須要在網關進行認證便可,無需每一個微服務都進行認證
  4. 下降客戶端調用服務端的複雜度

這裏能夠聯想到一個概念,面向對象設計中的門面模式,即對客戶端隱藏細節,API網關也是相似的東西,只不過叫法不一樣而已。它是系統的入口,封裝了應用程序的內部結構,爲客戶端提供統一服務,一些與業務自己功能無關的公共邏輯能夠在這裏實現,諸如認證、鑑權、監控、緩存、負載均衡、流量管控、路由轉發等等。示意圖:
Spring Cloud Zuul 快速入門bootstrap

總結一下,服務網關大概就是四個功能:統一接入、流量管控、協議適配、安全維護。而在目前的網關解決方案裏,有Nginx+ Lua、Kong、Tyk以及Spring Cloud Zuul等等。這裏以Zuul爲例進行說明,它是Netflix公司開源的一個API網關組件,Spring Cloud對其進行二次封裝作到開箱即用。同時,Zuul還能夠與Spring Cloud中的Eureka、Ribbon、Hystrix等組件配合使用。後端

能夠說,Zuul實現了兩個功能,路由轉發和過濾器: api

  • 路由轉發:接受請求,轉發到後端服務
  • 過濾器:提供一系列過濾器完成權限、日誌、限流等切面任務。
  • 能夠說路由+過濾器=Zuul

服務網關的要素:跨域

  • 網關做爲惟一的入口,因此穩定性和高可用是跑不了了
  • 以及具有良好的併發性能
  • 安全性,確保服務不被惡意訪問
  • 擴展性,網關容易成爲吞吐量的瓶頸,因此須要便於擴展

Zuul的四種過濾器API:

  • 前置(Pre)
  • 路由(Route)
  • 後置(Post)
  • 錯誤(Error)

zuul先後置過濾器的典型應用場景:

  • 前置(Pre)
    • 限流
    • 鑑權
    • 參數校驗調整
  • 後置(Post)
    • 統計
    • 日誌

Zuul的核心是一系列過濾器,開發者經過實現過濾器接口,能夠作大量切面任務,即AOP思想的應用。Zuul的過濾器之間沒有直接的相互通訊,而是經過本地ThreadLocal變量進行數據傳遞的。Zuul架構圖:
Spring Cloud Zuul 快速入門

在Zuul裏,一個請求的生命週期:
Spring Cloud Zuul 快速入門


Zuul:路由轉發,排除和自定義

本小節咱們來學習如何使用服務網關,也就是Spring Cloud Zuul這個組件,首先新建一個項目,選擇以下模塊:
Spring Cloud Zuul 快速入門

pom.xml配置的依賴以下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>

<dependencies>
    <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改成bootstrap.yml,編輯內容以下:

spring:
  application:
    name: api-gateway
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIG
      profile: dev
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

注:我這裏使用了配置中心,若對此不熟悉的話,能夠參考我另外一篇文章:Spring Cloud Config - 統一配置中心

在啓動類中,加上@EnableZuulProxy註解,代碼以下:

package org.zero.springcloud.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {

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

完成以上配置後啓動這個項目,我這裏項目啓動是正常的。而後咱們來經過這個網關訪問一下商品服務中獲取商品列表的接口。以下:
Spring Cloud Zuul 快速入門

訪問地址說明:

  • 該zuul項目跑在8951端口上
  • 第一個/product是須要訪問的服務的名稱
  • 後面跟的/buyer/product/list是商品服務中獲取商品列表的接口地址

只要是在eureka上註冊的服務都可以經過zuul進行轉發,例如我經過zuul來訪問config的配置文件:
Spring Cloud Zuul 快速入門

如上,能夠看到,報錯了,網關超時。這是由於默認狀況下,zuul的熔斷機制超時時間是2秒,當一個服務響應的時間較長就會報網關超時錯誤。

咱們在配置文件中,加上以下超時時間的配置便可:
Spring Cloud Zuul 快速入門

ribbon.ReadTimeout, ribbon.SocketTimeout這兩個就是ribbon超時時間設置,當在yml寫時,應該是沒有提示的,給人的感受好像是否是這麼配的同樣,其實不用管它,直接配上就生效了。

還有zuul.host.connect-timeout-millis, zuul.host.socket-timeout-millis這兩個配置,這兩個和上面的ribbon都是配超時的。區別在於,若是路由方式是serviceId的方式,那麼ribbon的生效,若是是url的方式,則zuul.host開頭的生效。(此處重要!使用serviceId路由和url路由是不同的超時策略)

若是你在zuul配置了熔斷fallback的話,熔斷超時也要配置,即hystrix那段配置。否則若是你配置的ribbon超時時間大於熔斷的超時,那麼會先走熔斷,至關於你配的ribbon超時就不生效了。

如今重啓項目,再次訪問以前的地址,就不會出現網關超時的錯誤了:
Spring Cloud Zuul 快速入門

以前咱們訪問的都是GET類型的接口,咱們來看看POST類型的是否可以正常訪問。以下:
Spring Cloud Zuul 快速入門

每次請求某個服務的接口,都須要帶上這個服務的名稱。有沒有辦法能夠自定義這個規則呢?答案是有的,在配置文件中,增長路由的自定義配置:

zuul:
  routes:
    myProduct:
      path: /myProduct/**
      serviceId: product

說明:

  • myProduct 自定義的前綴
  • path 匹配的地址
  • product 路由到哪一個服務

重啓項目,測試以下:
Spring Cloud Zuul 快速入門

在項目啓動的時候,咱們也能夠在控制檯中查看到zuul全部的路由規則:
Spring Cloud Zuul 快速入門

若是咱們有些服務的接口不但願對外暴露,只但願在服務間調用,那麼就能夠在配置文件中,增長路由排除的配置。例如我不但願listForOrder被外部訪問,則在配置文件中,增長以下配置便可:

zuul:
  ...
  ignored-patterns:
    - /myProduct/buyer/product/listForOrder
    - /product/buyer/product/listForOrder

重啓項目,這時訪問就會報404了。以下:
Spring Cloud Zuul 快速入門

還可使用通配符進行匹配。以下示例:

zuul:
  ...
  ignored-patterns:
    - /**/buyer/product/listForOrder

Zuul:Cookie和動態路由

咱們在web開發中,常常會利用到cookie來保存用戶的登陸標識。但咱們使用了zuul組件後,默認狀況下,cookie是沒法直接傳遞給服務的,由於cookie默認被列爲敏感的headers。因此咱們須要在配置文件中,將sensitiveHeaders的值置空。以下:

zuul:
  ...
  routes:
    myProduct:
      path: /myProduct/**
      serviceId: product
      sensitiveHeaders:  # 置空該屬性的值便可

咱們每次配置路由信息都須要重啓項目,顯得很麻煩,在線上環境也不能這樣隨便重啓項目。因此咱們得實現動態路由的功能,實現動態路由其實就利用一下咱們以前實現的動態刷新配置文件的功能便可。首先把Zuul路由相關的配置剪切到git上,以下:
Spring Cloud Zuul 快速入門

注:我這裏使用了配置中心,若對此不熟悉的話,能夠參考我另外一篇文章:Spring Cloud Config - 統一配置中心

在pom.xml文件中,增長以下依賴項:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-client</artifactId>
</dependency>

而後在bootstrap.yml中,增長rabbitmq的配置。以下:

spring:
  application:
    name: api-gateway
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIG
      profile: dev
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: admin
    password: admin
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

最後在項目中建立一個config包,在該包中建立一個ZuulConfig配置類,用於加載配置文件中的配置。代碼以下:

package org.zero.springcloud.apigateway.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.stereotype.Component;

/**
 * @program: api-gateway
 * @description: 網關路由配置類
 * @author: 01
 * @create: 2018-08-25 15:51
 **/
@Component
public class ZuulConfig {

    @RefreshScope
    @ConfigurationProperties("zuul")
    public ZuulProperties zuulProperties(){
        return new ZuulProperties();
    }
}

完成以上配置後,重啓項目,便可實現動態路由了,例如我如今把myProduct改爲yourProduct,以下:
Spring Cloud Zuul 快速入門

此時無需重啓項目,訪問新的地址便可。以下:
Spring Cloud Zuul 快速入門


Zuul的高可用

  • 由於Zuul也屬於一個微服務,因此咱們將多個Zuul節點註冊到Eureka Server便可實現Zuul的高可用性
  • 將Nginx和Zuul 「混搭」,利用nginx作負載均衡,轉發到多個Zuul上
相關文章
相關標籤/搜索