1. 程式人生 > >SpringCloud--08、閘道器Zuul

SpringCloud--08、閘道器Zuul

1、概述

Zuul 作為路由閘道器元件、隸屬Netfix

SpringCloud微服務架構圖:

通過圖可以看到、zuul是整個架構對外的大門、pc端和移動端的請求都要通過Zuul這個閘道器、然後由網站

來實現鑑權、動態路由等操作、

2、工作原理

話不多說、先來個圖

Zuul 是通過Servlet 來實現的, Zuul 通過自定義的Zuu!Servlet 來對請求進行控制。

Zuul 的核心是一系列過濾器,可以在Http 請求的發起和響應返回期間執行一系列的過濾器。

Zuul 包括以下4 種過濾器:

口PRE 過濾器: 它是在請求路由到具體的服務之前執行的,這種型別的過濾器可以做安全驗證,例如身份驗證、引數驗證等。
口ROUTING 過濾器: 它用於將請求路由到具體的微服務例項。在預設情況下,它使用Http Client 進行網路請求。
口POST 過濾器:它是在請求己被路由到微服務後執行的。一般情況下,用作收集統計資訊、指標,以及將響應傳輸到客戶端。
口ERROR 過濾器:它是在其他過濾器發生錯誤時執行的。

3、入門案例

依賴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>cloud-demo</artifactId>
        <groupId>cn.itcast.demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.itcast.demo</groupId>
    <artifactId>zuul-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

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

啟動類ZuulDemoApp.java

package www.baidus.zuul;

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

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 13:47 2018/12/2
 */
@SpringBootApplication
@EnableZuulProxy //開啟Zuul的閘道器功能
@EnableDiscoveryClient //開啟Eureka客客戶端發現功能
public class ZuulDemoApp {
    public static void main(String[] args) {
        SpringApplication.run(ZuulDemoApp.class,args);
    }
}

application.yml全域性配置

server:
  port: 10010 #服務埠
spring:
  application:
    name: api-gateway #指定服務名

編寫路由規則

zuul:
  routes:
    user-service: # 這裡是路由id,隨意寫
      path: /user-service/** # 這裡是對映路徑
      url: http://127.0.0.1:8081 # 對映路徑對應的實際url地址
我們將符合path 規則的一切請求,都代理到 url引數指定的地址

本例中,我們將 /user-service/**開頭的請求,代理到http://127.0.0.1:8081

啟動測試:http://127.0.0.1:10010/user-service/user/1

4、動態路由Eureka註冊中心例項列表(面向服務的路由)

上面的例項中、(將 /user-service/**開頭的請求,代理到http://127.0.0.1:8081)

我們把路徑對應的服務地址寫死了!如果同一服務有多個例項的話,這樣做顯然就不合理了。

我們應該根據服務的名稱,去Eureka註冊中心查詢 服務對應的所有例項列表,然後進行動態路由才對!

在zuul-service中pom.xml新增Eureka客戶端依賴、使閘道器通過Eureka動態路由到(負載小的)伺服器上。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

zuul-service開啟Eureka客戶端發現功能

package www.baidus.zuul;

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

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 13:47 2018/12/2
 */
@SpringBootApplication
@EnableZuulProxy //開啟Zuul的閘道器功能
@EnableDiscoveryClient //開啟Eureka客客戶端發現功能
public class ZuulDemoApp {
    public static void main(String[] args) {
        SpringApplication.run(ZuulDemoApp.class,args);
    }
}

新增Eureka配置,獲取服務資訊 、修改對映檔案application.yml

eureka:
  client:
    registry-fetch-interval-seconds: 5 # 獲取服務列表的週期:5s
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka,http://127.0.0.1:10088/eureka,
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1

修改對映配置,通過服務名稱獲取

zuul:
  routes:
    user-service: # 這裡是路由id,隨意寫
      path: /user-serverss/** # 這裡是對映路徑
      serviceId: user-service # 指定服務名稱

啟動測試:http://127.0.0.1:10010/user-serverss/user/1 檢視日誌

 

 

5、簡化的路由配置

 

zuul:
  routes:
    user-service: /user-service/** # 這裡是對映路徑

6、預設路由規則

預設情況下,一切服務的對映路徑就是服務名本身。

- 例如服務名為:user-service,則預設的對映路徑就是:/user-service/**

7、路由前緣

zuul:
  prefix: /ap # 新增路由字首
  routes:
      user-service: /user-service/** # 這裡是對映路徑

 路徑/ap/user-service/user/1將會被代理到/user-service/user/1

8、過慮器、自定義過慮器

Zuul作為閘道器的其中一個重要功能,就是實現請求的鑑權。而這個動作我們往往是通過Zuul提供的過濾器來實現的。

ZuulFilter是過濾器的頂級父類。在這裡我們看一下其中定義的4個最重要的方法:

public abstract ZuulFilter implements IZuulFilter{

    abstract public String filterType();

    abstract public int filterOrder();
    
    boolean shouldFilter();// 來自IZuulFilter

    Object run() throws ZuulException;// IZuulFilter
}

說明:

filterType:返回字串,代表過濾器的型別。包含以下4種:

- pre:請求在被路由之前執行
- routing:在路由請求時呼叫
- post:在routing和errror過濾器之後呼叫
- error:處理請求時發生錯誤呼叫

filterOrder:通過返回的int值來定義過濾器的執行順序,數字越小優先順序越高。
shouldFilter:返回一個Boolean值,判斷該過濾器是否需要執行。返回true執行,返回false不執行。
run:過濾器的具體業務邏輯。

過濾器執行生命週期:圖解 

正常流程:

請求到達首先會經過pre型別過濾器,而後到達routing型別,進行路由,
請求就到達真正的服務提供者,執行請求,返回結果後,會到達post過濾器。而後返回響應。

異常流程:

- 整個過程中,pre或者routing過濾器出現異常,都會直接進入error過濾器,
再error處理完畢後,會將請求交給POST過濾器,最後返回給使用者。
- 如果是error過濾器自己出現異常,最終也會進入POST過濾器,而後返回。
- 如果是POST過濾器出現異常,會跳轉到error過濾器,但是與pre和routing不同的時,
請求不會再到達POST過濾器了。

使用場景:

- 請求鑑權:一般放在pre型別,如果發現沒有訪問許可權,直接就攔截了
- 異常處理:一般會在error型別和post型別過濾器中結合來處理。
- 服務呼叫時長統計:pre和post結合使用。

自定義過濾器:如果請求引數中沒token就攔截不放行。

MyFilter.java

package www.baidus.zuul.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 14:15 2018/12/4
 */
@Component
public class MyFilter  extends ZuulFilter {
    @Override
    public String filterType() {
        return "pre";
    }

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

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

    @Override
    public Object run() throws ZuulException {

        //獲取上下文資源
        RequestContext currentContext = RequestContext.getCurrentContext();

        //從上下文獲取request
        HttpServletRequest request = currentContext.getRequest();

        //獲取引數
        String token = request.getParameter("token");

        if (StringUtils.isEmpty(token) || "".equals(token.trim())){
            // 沒有token,登入校驗失敗,攔截不響應
            currentContext.setSendZuulResponse(false);
            //返回401狀態碼
            currentContext.setResponseStatusCode(401);
        }
        return null;
    }
}

http://127.0.0.1:10010/ap/user-service/user/2

http://127.0.0.1:10010/ap/user-service/user/2?token=lan

9、負載均衡和熔斷

Zuul中預設就已經集成了Ribbon負載均衡和Hystix熔斷機制。但是所有的超時策略都是走的預設值,
比如熔斷超時時間只有1S,很容易就觸發了。因此建議我們手動進行配置:
ribbon:
  ConnectTimeout: 250 # 連線超時時間(ms)
  ReadTimeout: 2000 # 通訊超時時間(ms)
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000