天天看點

springcloud (Finchley版本)簡易入門 | 第五課: Zuul路由網關一、Zuul簡介二、實戰

在微服務架構中,需要幾個基礎的服務治理元件,包括服務注冊與發現、服務消費、負載均衡、斷路器、智能路由、配置管理等,由這幾個基礎元件互相協作,共同組建了一個簡單的微服務系統。一個簡答的微服務系統如下圖:

springcloud (Finchley版本)簡易入門 | 第五課: Zuul路由網關一、Zuul簡介二、實戰

在Spring Cloud微服務系統中,一種常見的負載均衡方式是,用戶端的請求首先經過負載均衡(zuul、Ngnix),再到達服務網關(zuul叢集),然後再到具體的服。,服務統一注冊到高可用的服務注冊中心叢集,服務的所有的配置檔案由配置服務管理(下一篇文章講述),配置服務的配置檔案放在git倉庫,友善開發人員随時改配置。

一、Zuul簡介

Zuul的主要功能是路由轉發和過濾器。路由功能是微服務的一部分,比如api/user轉發到到user服務,/api/order轉發到到order服務。zuul預設和Ribbon結合實作了負載均衡的功能。

  • SpringCloud Zuul通過與SpringCloud Eureka進行整合,将自身注冊為Eureka服務治理下的應用,同時從Eureka中獲得了所有其他微服務的執行個體資訊。外層調用都必須通過API網關,使得将維護服務執行個體的工作交給了服務治理架構自動完成。
  • 在API網關服務上進行統一調用來對微服務接口做前置過濾,以實作對微服務接口的攔截和校驗。

Zuul天生就擁有線程隔離和斷路器的自我保護功能,以及對服務調用的用戶端負載均衡功能。也就是說:Zuul也是支援Hystrix和Ribbon。

zuul有以下功能:

  • Authentication
  • Insights
  • Stress Testing
  • Canary Testing
  • Dynamic Routing
  • Service Migration
  • Load Shedding
  • Security
  • Static Response handling
  • Active/Active traffic management

1.1、可能對Zuul的疑問

Zuul支援Ribbon和Hystrix,也能夠實作用戶端的負載均衡。我們的Feign不也是實作用戶端的負載均衡和Hystrix的嗎?既然Zuul已經能夠實作了,那我們的Feign還有必要嗎?

或者可以這樣了解:

  • zuul是對外暴露的唯一接口相當于路由的是controller的請求,而Ribbonhe和Fegin路由了service的請求
  • zuul做最外層請求的負載均衡 ,而Ribbon和Fegin做的是系統内部各個微服務的service的調用的負載均衡

有了Zuul,還需要Nginx嗎?他倆可以一起使用嗎?

  • 我的了解:Zuul和Nginx是可以一起使用的(畢竟我們的Zuul也是可以搭成叢集來實作高可用的),要不要一起使用得看架構的複雜度了(業務)~~~

二、實戰

2.1、建立zuul工程

其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-test</artifactId>
        <groupId>com.dukun.study</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>zuul</artifactId>
    <dependencies>
        <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>

    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


</project>
           

在其入口applicaton類加上注解@EnableZuulProxy,開啟zuul的功能:

package com.dukun.study.zuul;

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

/**
 * zuul網關
 *
 * @Author: dukun0210
 * @Date: 2021/1/12 14:17
 */
@SpringBootApplication
@EnableZuulProxy
public class AppZuul {

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

yml檔案:

server:
  port: 9000
eureka:
  client:
    serviceUrl:
        defaultZone: http://localhost:3000/eureka/  #eureka服務端提供的注冊位址 參考服務端配置的這個路徑
  instance:
    instance-id: zuul-0 #此執行個體注冊到eureka服務端的唯一的執行個體ID
    prefer-ip-address: true #是否顯示IP位址
    leaseRenewalIntervalInSeconds: 20 #eureka客戶需要多長時間發送心跳給eureka伺服器,表明它仍然活着,預設為30 秒 (與下面配置的機關都是秒)
    leaseExpirationDurationInSeconds: 60 #Eureka伺服器在接收到執行個體的最後一次發出的心跳後,需要等待多久才可以将此執行個體删除,預設為90秒

spring:
  application:
    name: zuul #此執行個體注冊到eureka服務端的name

zuul:
  prefix: /api
  ignored-services: "*"
#  stripPrefix: false
  routes:
    order:
      serviceId: server-order
      path: /order/**
    user:
      serviceId: client-user
      path: /user/**
    power:
      serviceId: server-power
      path: /power/**
           
注意/ **代表是所有層級 / * 是代表一層。 如果是/ * 的話 /power/admin/getUser.do 就不會被路由 。

這樣 簡單的zuul就搭建好了, 啟動項目 我們就可以通過zuul然後加上對應的通路微服務:

以/api/order/** 開頭的請求都轉發給server-order服務;以/api/user/**開頭的請求都轉發給client-user服務;

http://localhost:9000/api/user/getOrderAndPowerrFeign.do?name=8848 就可以通路到 user服務中的 /getOrderAndPowerrFeign.do 方法。

2.2、相關配置

2.2.1、統一字首

這個很簡單,就是我們可以在前面加一個統一的字首,這個時候我們在

yaml

配置檔案中添加如下。

zuul:

  prefix: /zuul

2.2.2、路由政策配置

你會發現前面的通路方式(直接使用服務名),需要将微服務名稱暴露給使用者,會存在安全性問題。是以,可以自定義路徑來替代微服務名稱,即自定義路由政策。

zuul:
  prefix: /api
  routes:
    order:
      serviceId: server-order
      path: /order/**
           

2.2.3、服務名屏蔽

這個時候你别以為你好了,你可以試試,在你配置完路由政策之後使用微服務名稱還是可以通路的,這個時候你需要将服務名屏蔽。

zuul:

  ignore-services: "*"

2.2.4、路徑屏蔽

Zuul

還可以指定屏蔽掉的路徑 URI,即隻要使用者請求中包含指定的 URI 路徑,那麼該請求将無法通路到指定的服務。通過該方式可以限制使用者的權限。

zuul:

  ignore-patterns: **/auto/**

2.3、Zuul 的過濾功能

如果說,路由功能是

Zuul

的基操的話,那麼過濾器就是

Zuul

的利器了。畢竟所有請求都經過網關(Zuul),那麼我們可以進行各種過濾,這樣我們就能實作限流,灰階釋出,權限控制等等。

先解釋一下關于過濾器的一些注意點:

springcloud (Finchley版本)簡易入門 | 第五課: Zuul路由網關一、Zuul簡介二、實戰

zuul中定義了4種标準過濾器類型,這 些過濾器類型對應于請求的典型生命周期。

過濾器類型:

  1. PRE:這種過濾器在請求被路由之前調用。可利用這種過濾器實作身份 驗證、在 叢集中選擇請求的微服務、記錄調試資訊等。
  2. ROUTING:這種過濾器将請求路由到微服務。這種過濾器 用于建構發送給微服 務的請求,并使用 Apache HttpCIient或 Netfilx Ribbon請求微服務
  3. POST:這種過濾器在路由 到微服務以後執行。這種過濾器可用來為響應添加标準 的 HTTP Header、收集統計資訊和名額、将響應從微服務 發送給用戶端等。
  4. ERROR:在其他階段發生錯誤時執行該過濾器。

2.3.1、簡單實作一個請求通路記錄列印

/**
 * ZUUL 網關的過濾器
 * 添加日志功能
 *
 * @Author: dukun0210
 * @Date: 2021/1/13 10:24
 */
@Component
public class LogFilter extends ZuulFilter {
    //Pre、Routing、Post。
    // 前置Pre就是在請求之前進行過濾,
    // Routing路由過濾器就是我們上面所講的路由政策,
    // 而Post後置過濾器就是在Response之前進行過濾的過濾器。
    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }

    // 指定過濾順序 越小越先執行,這裡第一個執行
    // 當然不是隻真正第一個 在Zuul内置中有其他過濾器會先執行
    // 那是寫死的 比如 SERVLET_DETECTION_FILTER_ORDER = -3
    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER;
    }

    // 什麼時候該進行過濾
    // 這裡我們可以進行一些判斷,這樣我們就可以過濾掉一些不符合規定的請求等等
    @Override
    public boolean shouldFilter() {
        return true;
    }

    // 如果過濾器允許通過則怎麼進行處理
    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String remoteAddr = request.getRemoteAddr();
        System.out.println("通路者IP:"+remoteAddr+"通路位址:"+request.getRequestURI());
        return null;
    }
}
           

由代碼可知,自定義的 zuul Filter需實作以下幾個方法。

filterType:傳回過濾器的類型。有 pre、 route、 post、 error等幾種取值,分别對應上文的幾種過濾器。 詳細可以參考 com.netflix.zuul.ZuulFilter.filterType()中的注釋。

filter0rder:傳回一個 int值來指定過濾器的執行順序,不同的過濾器允許傳回相同的數字。

shouldFilter:傳回一個 boolean值來判斷該過濾器是否要執行, true表示執行, false表示不執行。

run:過濾器的具體邏輯。 禁用zuul過濾器 Spring Cloud預設為Zuul編寫并啟用了一些過濾器,例如DebugFilter、 FormBodyWrapperFilter 等,這些過濾器都存放在spring-cloud-netflix-core這個jar包 裡,一些場景下,想要禁用掉部分過濾器,該怎麼辦 呢? 隻需在application.yml裡設定zuul...disable=true 例如,要禁用上面我們寫的過濾器,這樣配置就行了: zuul.LogFilter.pre.disable=true

2.3.2、令牌桶限流

當然不僅僅是令牌桶限流方式,

Zuul

隻要是限流的活它都能幹,這裡我隻是簡單舉個例子。

springcloud (Finchley版本)簡易入門 | 第五課: Zuul路由網關一、Zuul簡介二、實戰

我先來解釋一下什麼是令牌桶限流吧。

首先我們會有個桶,如果裡面沒有滿那麼就會以一定固定的速率會往裡面放令牌,一個請求過來首先要從桶中擷取令牌,如果沒有擷取到,那麼這個請求就拒絕,如果擷取到那麼就放行。很簡單吧,啊哈哈、

下面我們就通過

Zuul

的前置過濾器來實作一下令牌桶限流。

@Component
@Slf4j
public class RouteFilter extends ZuulFilter {
    // 定義一個令牌桶,每秒産生2個令牌,即每秒最多處理2個請求
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

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

    @Override
    public Object run() throws ZuulException {
        log.info("放行");
        return null;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        if(!RATE_LIMITER.tryAcquire()) {
            log.warn("通路量超載");
            // 指定目前請求未通過過濾
            context.setSendZuulResponse(false);
            // 向用戶端傳回響應碼429,請求數量過多
            context.setResponseStatusCode(429);
            return false;
        }
        return true;
    }
}
           

這樣我們就能将請求數量控制在一秒兩個,有沒有覺得很酷?

2.3.3、zuul容錯與回退

zuul預設是整合了hystrix和ribbon的, 提供降級回退,那如何來使用hystrix呢?

我們自行寫一個類,繼承FallbackProvider 類 然後重寫裡面的方法。

package com.dukun.study.zuul.provider;

import com.netflix.hystrix.exception.HystrixTimeoutException;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * zuul網關的 容錯
 *
 * @Author: dukun0210
 * @Date: 2021/1/13 11:10
 */
@Component
public class FallBackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        //制定為哪個微服務提供回退(這裡寫微服務名 寫*代表所有微服務)
        return "*";
    }

    //此方法需要傳回一個ClientHttpResponse對象 ClientHttpResponse是一個接口,具體的回退邏輯要實 現此接口
    //route:出錯的微服務名 cause:出錯的異常對象
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {

        //這裡可以判斷根據不同的異常來做不同的處理, 也可以不判斷
        //完了之後調用response方法并根據異常類型傳入HttpStatus
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            return response(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private ClientHttpResponse response(final HttpStatus status) {
        //這裡傳回一個ClientHttpResponse對象 并實作其中的方法,關于回退邏輯的詳細,便在下面的方法中
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                傳回一個HttpStatus對象 這個對象是個枚舉對象, 裡面包含了一個status code 和
                //reasonPhrase資訊
                return status;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                //傳回status的code 比如 404,500等
                return status.value();
            }

            @Override
            public String getStatusText() throws IOException {
                //傳回一個HttpStatus對象的reasonPhrase資訊
                return status.getReasonPhrase();
            }

            @Override
            public void close() {
                //close的時候調用的方法, 講白了就是當降級資訊全部響應完了之後調用的方法
            }

            @Override
            public InputStream getBody() throws IOException {
                //吧降級資訊響應回前端
                return new ByteArrayInputStream("系統繁忙,稍後再試".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                //需要對響應報頭設定的話可以在此設定
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }


}
           

關于 Zuul  的其他

Zuul

的過濾器的功能肯定不止上面我所實作的兩種,它還可以實作權限校驗,包括我上面提到的灰階釋出等等。

當然,

Zuul

作為網關肯定也存在單點問題,如果我們要保證

Zuul

的高可用,我們就需要進行

Zuul

的叢集配置,這個時候可以借助額外的一些負載均衡器比如

Nginx

繼續閱讀