微服務網關Zuul


1 Zuul簡介

  • Zuul是Netflix開源的微服務網關,它可以和Eureka、Ribbon以及Hystrix等組件配合使用,Zuul組件的核心是一系列的過濾器,這些過濾器可以完成以下功能:

  • 1️⃣動態路由:動態將請求路由到不同后端集群。

  • 2️⃣壓力測試:逐漸增加指向集群的流量,以了解性能。

  • 3️⃣負載分配:為每一種負載類型分配對應容量,並棄用超過限定值的請求。

  • 4️⃣靜態響應處理:邊緣位置進行響應,避免轉發到內部集群。

  • 5️⃣身份認證和安全:識別每一個資源的驗證要求,並拒絕那些不符合要求的請求。

  • SpringCloud對Zuul進行了整合和增強。

2 搭建Zuul網關服務器

2.1 創建工程並導入相關依賴的Maven坐標

  • 修改部分:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
  • 完整部分:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring_cloud_demo</artifactId>
        <groupId>org.sunxiaping</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>api_zuul_server7006</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>
    
</project>

2.2 編寫配置

  • 創建並配置application.yml:
server:
  port: 7006

spring:
  application:
    name: api-zuul-server

2.3 編寫啟動類

  • 啟動類:
package com.sunxiaping.zuul;

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

@SpringBootApplication
@EnableZuulProxy //開啟Zuul網關功能
public class Zuul7006Application {
    public static void main(String[] args) {
        SpringApplication.run(Zuul7006Application.class, args);
    }
}

3 Zuul中的路由轉發

3.1 概述

  • 最直觀的理解:“路由”是根據請求URL,將請求分配到對應的處理程序。

  • 在微服務體系中,Zuul負責接收所有的請求。根據不同的URL匹配規則,將不同的請求轉發到不同的微服務處理。

  • 示例:

  • 在application.yml中配置路由規則

server:
  port: 7006

spring:
  application:
    name: api-zuul-server

# 路由配置
zuul:
  routes:
    # 以商品微服務為例
    product-service: # 路由的id,隨便寫
      path: /product-service/** # 這里是映射路徑
      url: http://localhost:9004 # 映射路徑對應的實際URL地址
      sensitiveHeaders: #默認zuul會屏蔽Cookie,Cookie不會傳到下游服務器,這里設置為空則Cookie可以傳遞到下游的服務器
  • 配置好Zuul路由之后啟動服務,在瀏覽器中輸入http://localhost:7006/product-service/product/findById/1,即可訪問到商品微服務。

3.2 面向服務的路由

3.2.1 概述

  • 微服務一般是由幾十、上百個服務組成,對於一個URL請求,最終會確認一個服務實例來進行處理。如果對每個服務實例手動指定一個唯一的訪問地址,然后根據URL去手動實現請求匹配,這樣做顯然不合理。
  • Zuul支持和Eureka整合開發,根據serviceId自動的從注冊中心獲取服務地址並轉發請求,這樣做的好處不僅可以通過單個端點來訪問應用的所有服務,而且在添加或移除服務實例的時候不用修改Zuul的路由配置。

3.2.2 步驟

  • 1️⃣添加Eureka的依賴。
  • 2️⃣開啟Eureka的客戶端服務發現。
  • 3️⃣在Zuul網關服務中配置Eureka注冊中心的相關配置。
  • 4️⃣修改路由中的映射配置。

3.2.3 應用示例

  • 添加Eureka的依賴:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 開啟Eureka的客戶端服務發現:
package com.sunxiaping.zuul;

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 //開啟Zuul網關功能
@EnableEurekaClient //開啟Eureka的客戶端服務發現
public class Zuul7006Application {
    public static void main(String[] args) {
        SpringApplication.run(Zuul7006Application.class, args);
    }
}
  • 在Zuul網關服務中配置Eureka注冊中心的相關配置(application.yml):
# 配置 eureka
eureka:
  instance:
    # 主機名稱:服務名稱修改,其實就是向eureka server中注冊的實例id
    instance-id: api-zuul-server:${server.port}
    # 顯示IP信息
    prefer-ip-address: true
  client:
    service-url: # 此處修改為 Eureka Server的集群地址
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  • 修改路由中的映射配置(application.yml):
# 路由配置
zuul:
  routes:
    # 以商品微服務為例
    product-service: # 路由的id,隨便寫
      path: /product-service/** # 這里是映射路徑
      serviceId: service-product # 配置轉發的微服務名稱
      sensitiveHeaders: #默認zuul會屏蔽Cookie,Cookie不會傳到下游服務器,這里設置為空則Cookie可以傳遞到下游的服務器
  • 完整的application.yml:
server:
  port: 7006

spring:
  application:
    name: api-zuul-server

# 路由配置
zuul:
  routes:
    # 以商品微服務為例
    product-service: # 路由的id,隨便寫
      path: /product-service/** # 這里是映射路徑
      serviceId: service-product # 配置轉發的微服務名稱
      sensitiveHeaders: #默認zuul會屏蔽Cookie,Cookie不會傳到下游服務器,這里設置為空則Cookie可以傳遞到下游的服務器

# 配置 eureka
eureka:
  instance:
    # 主機名稱:服務名稱修改,其實就是向eureka server中注冊的實例id
    instance-id: api-zuul-server:${server.port}
    # 顯示IP信息
    prefer-ip-address: true
  client:
    service-url: # 此處修改為 Eureka Server的集群地址
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

3.3 簡化的路由配置

  • 在上面的配置中,我們的規則是這樣的:
# 路由配置
zuul:
  routes:
    # 以商品微服務為例
    product-service: # 路由的id,隨便寫
      path: /product-service/** # 這里是映射路徑
      serviceId: service-product # 配置轉發的微服務名稱
      sensitiveHeaders: #默認zuul會屏蔽Cookie,Cookie不會傳到下游服務器,這里設置為空則Cookie可以傳遞到下游的服務器
  • 使用zuul.routes.<route>.path=/xxx/**:來指定映射路徑。<route>是自定義的路由名。

  • 使用zuul.routes.<route>.serviceId=xxx:來指定服務名。

  • 但是絕大多數情況下,我們的<route>路由名往往和服務名是一樣的。因此Zuul就提供了一種簡化配置語法:zuul.routes.<serviceId>=path

  • 上面的配置可以簡化:

# 路由配置
zuul:
  routes:
    # 以商品微服務為例
    #    product-service: # 路由的id,隨便寫
    #      path: /product-service/** # 這里是映射路徑
    #      serviceId: service-product # 配置轉發的微服務名稱
    #      sensitiveHeaders: #默認zuul會屏蔽Cookie,Cookie不會傳到下游服務器,這里設置為空則Cookie可以傳遞到下游的服務器
    service-product: /product-service/** # 如果路由id和對應微服務的serviceId一致,就可以使用簡化的路由配置

3.4 默認的路由規則

  • 在使用Zuul的過程中,簡化的路由配置已經大大簡化了我們手動配置。但是當服務較多的時候,配置也是比較繁瑣的,因此Zuul就 指定了默認的路由規則。
  • 默認情況下,一切服務的映射路徑就是服務名本身。比如:服務名是service-product,則默認的映射路徑就是:/service-product/**
# 路由配置
zuul:
  routes:
    # 以商品微服務為例
    #    product-service: # 路由的id,隨便寫
    #      path: /product-service/** # 這里是映射路徑
    #      serviceId: service-product # 配置轉發的微服務名稱
    #      sensitiveHeaders: #默認zuul會屏蔽Cookie,Cookie不會傳到下游服務器,這里設置為空則Cookie可以傳遞到下游的服務器
    service-product: /product-service/** # 如果路由id和對應微服務的serviceId一致,就可以使用簡化的路由配置
    # zuul中的默認路由規則,如果當前的微服務名是service-order,那么對應的映射路徑就是/service-order/**

4 Zuul加入后的架構

uul加入后的架構

5 Zuul中的過濾器

5.1 概述

  • 根據前面的知識,我們知道Zuul包含了兩個核心功能:對請求的路由過濾。其中路由功能負責將外部請求轉發到具體的微服務實例上,是實現外部訪問統一入口的基礎;而過濾器功能則負責對請求的處理過程進行干預,是實現請求校驗、服務聚合等功能的基礎。其實,路由功能在真正運行時,它的路由映射和請求轉發同樣也由不同的過濾器完成的。所以,過濾器可以說是Zuul實現API網關功能最為核心的部件,每一個進入Zuul的HTTP請求都會經過一系列的過濾器處理鏈得到請求響應並返回給客戶端。

5.2 ZuulFilter簡介

  • Zuul中的過濾器和我們之前使用的javax.servlet.Filter不一樣,javax.servlet.Filter只有一種類型,可以通過配置urlPatterns來攔截對應的請求。而Zuul中的過濾器總共有4種類型,且每種類型都有對應的使用場景。

    • 1️⃣PRE:這種過濾器在請求被路由之前調用。我們可以利用這種過濾器實現身份驗證、在救你中選擇請求的微服務、記錄調試信息等。
    • 2️⃣ROUTING:這種過濾器將請求路由到微服務。這種過濾器用於構建發送給微服務的請求,並使用Apache的HttpClient或Netflix的Ribbon請求微服務。
    • 3️⃣POST:這種過濾器在路由到微服務以后執行。這種過濾器可以用來響應添加標准的HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等等。
    • 4️⃣ERROR:在其他階段發生錯誤時執行該過濾器。
  • Zuul提供了自定義過濾器的功能實現也十分簡單,只需要編寫一個類去實現Zuul提供的接口。

public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
    
    //返回字符串,代表過濾器的類型。包含4種:pre、routing、post、error
    abstract public String filterType();
    
    //通過返回的int值來定義過濾器的執行順序,數字越小優先級越高
    abstract public int filterOrder();
    
    //返回一個boolean值,判斷該過濾器是否需要執行。返回true執行,返回false不執行。
    boolean shouldFilter(); //來自IZuulFilter
    
    //過濾器的具體業務邏輯。
    Object run() throws ZuulException; //來自IZuulFilter
    
}    

5.3 生命周期

uul的生命周期

  • 正常流程:
    • 請求到達首先會經過pre類型過濾器,而后到達routing類型,進行路由,請求就到達真正的服務提供者,執行請求,返回結果后,會到達post過濾器,而后返回響應。
  • 異常流程:
    • 整個過程中,pre或routing過濾器出現異常,都會直接進入error過濾器,error過濾器處理完畢后,會將請求交給post過濾器,最后返回給用戶。
    • 如果error過濾器自己出現異常,最終也會進入post過濾器,而后返回。
    • 如果是post過濾器出現異常,會跳轉到error過濾器,但是和pre以及routing不同的是,請求不會再到達post過濾器了。
  • 不同過濾器的場景:
    • 請求鑒權:一般放在pre類型,如果發現沒有訪問權限,直接就攔截了。
    • 異常處理:一般會在error類型和post類型過濾器中結合處理。
    • 服務調用時長統計:pre和post結合使用。
  • 所有內置的過濾器列表:

所有內置的過濾器列表

5.4 自定義過濾器

  • 需求:如果請求中有access-token參數,則認為請求有效,放行。
package com.sunxiaping.zuul.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;

/**
 * 自定義身份認證過濾器
 *
 * @author 許大仙
 * @version 1.0
 * @since 2020-10-05 13:24
 */
@Component
public class LoginFilter extends ZuulFilter {
    /**
     * 用來定義過濾器類型
     * pre、routing、post、error
     *
     * @return 過濾器類型
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 指定過濾器的執行順序,值越小,執行順序越高
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 1;
    }

    /**
     * 當前過濾器是否生效
     *
     * @return 如果是true,此過濾器生效。如果是false,此過濾器不生效。
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 執行過濾器中的業務邏輯
     * 身份認證:
     * ①所有的請求需要攜帶一個參數:access-token
     * ②獲取request請求
     * ③通過request請求對象獲取access-token
     * ④判斷token是否為空
     * 如果token == null,身份驗證失敗
     * 如果token != null,執行后續操作
     * 在Zuul網關中,通過RequestContext的上下文對象,可以獲取對象request對象。
     *
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();

        String token = request.getHeader("access-token");

        //如果token為空,身份認證失敗
        if (StringUtils.isEmpty(token)) {
            currentContext.setSendZuulResponse(false); //攔截請求
            currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); //設置響應的狀態碼
        }
        //如果token不為空,執行后續操作
        return null;
    }
}

6 Zuul網關存在的問題

  • 在實際使用中我們會發現直接使用Zuul存在諸多問題,包括:
  • 1️⃣性能問題:Zuul 1.x版本本質上就是一個同步的Servlet,采用多線程阻塞的模型進行請求轉發。簡單的講,每一個請求,Servlet容器要為該請求分配一個線程專門負責處理這個請求,直到響應返回客戶端這個線程才會被釋放返回容器線程池。如果后台服務調用比較耗時,那么這個線程就會被阻塞,阻塞期間線程資源被占用,不能干其他事情。我們知道Servlet容器線程池的大小是由限制的,當前端請求量大,而后台慢服務比較多的時候,很容易耗盡容器線程池內的線程,造成容器無法接收新的請求。
  • 2️⃣不支持任何長連接,如WebSockt。

7 Zuul網關的替換方案

  • Zuul 2.x版本:SpringCloud目前還沒有整合Zuul 2.x版本。
  • SpringCloud Gateway。


免責聲明!

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



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