1. 程式人生 > >SpringCloud實戰6-Zuul閘道器服務

SpringCloud實戰6-Zuul閘道器服務

為什麼需要閘道器呢?

我們知道我們要進入一個服務本身,很明顯我們沒有特別好的辦法,直接輸入IP地址+埠號,我們知道這樣的做法很糟糕的,這樣的做法大有問題,首先暴露了我們實體機器的IP地址,別人一看你的IP地址就知道服務部署在哪裡,讓別人很方便的進行攻擊操作。

第二,我們這麼多服務,我們是不是要挨個呼叫它呀,我們這裡假設做了個許可權認證,我們每一個客戶訪問的都是跑在不同機器上的不同的JVM上的服務程式,我們每一個服務都需要一個服務認證,這樣做煩不煩呀,明顯是很煩的。

那麼我們這時候面臨著這兩個極其重要的問題,這時我們就需要一個辦法解決它們。首先,我們看IP地址的暴露和IP地址寫死後帶來的單點問題,我是不是對這麼服務本身我也要動態的維護它服務的列表呀,我需要呼叫這服務本身,是不是也要一個負載均衡一樣的玩意,

還有關於IP地址暴露的玩意,我是不是需要做一個代理呀,像Nginx的反向代理一樣的東西,還有這玩意上部署公共的模組,比如所有入口的許可權校驗的東西。因此我們現在需要Zuul API閘道器。它就解決了上面的問題,你想呼叫某個服務,它會給你對映,把你服務的IP地址對映成

某個路徑,你輸入該路徑,它匹配到了,它就去替你訪問這個服務,它會有個請求轉發的過程,像Nginx一樣,服務機器的具體例項,它不會直接去訪問IP,它會去Eureka註冊中心拿到服務的例項ID,即服務的名字。我再次使用客戶端的負載均衡ribbon訪問其中服務例項中的一臺。

API閘道器主要為了服務本身對外的呼叫該怎麼呼叫來解決的,還有解決許可權校驗的問題,你可以在這裡整合呼叫一系列過濾器的,例如整合shiro,springsecurity之類的東西。

Zuul可以通過載入動態過濾機制,從而實現以下各項功能:

  1.驗證與安全保障: 識別面向各類資源的驗證要求並拒絕那些與要求不符的請求。

  2.審查與監控: 在邊緣位置追蹤有意義資料及統計結果,從而為我們帶來準確的生產狀態結論。

  3.動態路由: 以動態方式根據需要將請求路由至不同後端叢集處。

  4.壓力測試: 逐漸增加指向叢集的負載流量,從而計算效能水平。

  5.負載分配: 為每一種負載型別分配對應容量,並棄用超出限定值的請求。

  6.靜態響應處理: 在邊緣位置直接建立部分響應,從而避免其流入內部叢集。

  7.多區域彈性: 跨越AWS區域進行請求路由,旨在實現ELB使用多樣化並保證邊緣位置與使用者儘可能接近。

接著下來進行實戰小Demo

第一步,在原來的工程下,新建一個Zuul模組,引入依賴,程式碼如下:

複製程式碼

    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.3.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
            <version>1.3.5.RELEASE</version>
        </dependency>

複製程式碼

接著在啟動類上打上@EnableZuulProxy註解,程式碼如下:

複製程式碼

server:
  port: 5000
spring:
  application:
    name: api-geteway
zuul:
  routes:
#標識你服務的名字,這裡可以自己定義,一般方便和規範來講還是跟自己服務的名字一樣
    hello-service:
#服務對映的路徑,通過這路徑就可以從外部訪問你的服務了,目的是為了不爆露你機器的IP,面向服務的路由了,給你選一個可用的出來,
#這裡zuul是自動依賴hystrix,ribbon的,不是面向單機
      path: /hello-service/**
#這裡一定要是你Eureka註冊中心的服務的名稱,是所以這裡配置serviceId因為跟eureka結合了,如果單獨使用zuul,那麼就必須寫自己機器的IP了,
#如url:http://localhost:8080/  這樣的不好就是寫死IP了,萬一這IP掛了,這高可用性,服務註冊那套東西就用不起來了
      serviceId: hello-service

eureka:
#客戶端
  client:
#註冊中心地址
    service-url:
      defaultZone: http://localhost:8888/eureka/,http://localhost:8889/eureka/

複製程式碼

接著啟動先前文章中的註冊中心和兩個hello-service服務提供者,接著我們執行,看一下它的請求轉發功能,看他有沒有輪詢進入兩個服務,

輸入localhost:5000/hello-service/hello,如下:

接著再重新整理一遍:

 可以看到zuul進行了請求分發了。它是根據你的服務名字hello-servie來對映到具體的機器上,這不就是一個反向代理的功能嗎?

zuul還能進行請求過濾,那麼我們進行一下token校驗來演示一下,首先我們需要先新建一個TokenFilter類來繼承ZuulFilter這個類,實現它的四個介面,程式碼如下:

複製程式碼

package hjc.zuul;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;

import javax.servlet.http.HttpServletRequest;

/**
 * Created by cong on 2018/5/18.
 */
public class TokenFilter extends ZuulFilter {
    //四種類型:pre,routing,error,post
    //pre:主要用在路由對映的階段是尋找路由對映表的
    //routing:具體的路由轉發過濾器是在routing路由器,具體的請求轉發的時候會呼叫
    //error:一旦前面的過濾器出錯了,會呼叫error過濾器。
    //post:當routing,error執行完後才會呼叫該過濾器,是在最後階段的
    @Override
    public String filterType() {
        return "pre";
    }

    //自定義過濾器執行的順序,數值越大越靠後執行,越小就越先執行
    @Override
    public int filterOrder() {
        return 0;
    }

    //控制過濾器生效不生效,可以在裡面寫一串邏輯來控制
    @Override
    public boolean shouldFilter() {
        return true;
    }

    //執行過濾邏輯
    @Override
    public Object run() {

        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String token = request.getParameter("token");
        if (token == null){
            context.setSendZuulResponse(false);
            context.setResponseStatusCode(401);
            context.setResponseBody("unAuthrized");


            return null;
        }
        return null;
    }
}

複製程式碼

filterType:返回一個字串代表過濾器的型別,在zuul中定義了四種不同生命週期的過濾器型別,具體如下:

  1.pre:可以在請求被路由之前呼叫,用在路由對映的階段是尋找路由對映表的

  2.route:在路由請求時候被呼叫,具體的路由轉發過濾器是在routing路由器具體的請求轉發的時候會呼叫

  3.error:處理請求時發生錯誤時被呼叫

  4.post:當routing,error執行完後才會呼叫該過濾器,是在最後階段的

這裡宣告一下zuul過濾器執行網路請求發生的異常,過濾器裡面是不能直接將try-catch捕捉的異常丟擲給頁面的。應用程式丟擲的異常是可以返回出的需解決辦法就是在catch裡面用context.set()方法返回給頁面。如下:

複製程式碼

try{
    業務邏輯......
}catch(Exception e){
        RequestContext context = RequestContext.getCurrentContext();
          context.set("error.status_code",401);
            context.set("error.exception",e);
            context.set("error.message","sfdfsdf");
}

複製程式碼

接著,你還需要把這個過濾器加入spring中,讓spring管理,程式碼如下:

複製程式碼

package hjc;

import hjc.zuul.TokenFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {

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

  //將過濾器交給Spring管理
    @Bean
    public TokenFilter tokenFilter(){
        return new TokenFilter();
    }

}

複製程式碼

接著,讓我們啟動啟動類,先進行不帶token的訪問,如下:

可以看到,返回一個沒許可權的資訊,這裡要說一下,Token一般都是放在請求頭中的,這裡我們只是為了演示才沒那麼幹,

接著將token帶上再去訪問,如下:

可以看到這是已經將我們的請求放過去了。

這裡我還要講一下什麼是預設路由,將zuul的配置刪除路由配置,如下:

複製程式碼

server:
  port: 5000
spring:
  application:
    name: api-geteway


eureka:
#客戶端
  client:
#註冊中心地址
    service-url:
      defaultZone: http://localhost:8888/eureka/,http://localhost:8889/eureka/

複製程式碼

接著,重啟繼續訪問,如下:

 

可以看到,還是能繼續訪問,我們什麼都沒配,居然還能訪問,那是因為,這裡預設用你的服務名字hello-service自動聲明瞭。

那麼,如果說我不想讓它幫我自動宣告,我要我自己定義,那麼可以在yml配置檔案中使用zuu.ignored-services就可以把自己像過濾的過濾,如下:”

zuul:
#如果ignored-services:*  表示所有的預設路由都失效了,要自己一個個配,沒人會那麼操蛋,除非遇到奇葩業務

  ignored-services: 

接著我們再說一下對映規則,比方說

複製程式碼

zuul:
  routes:
#標識你服務的名字,這裡可以自己定義,一般方便和規範來講還是跟自己服務的名字一樣
    hello-service:
#服務對映的路徑,通過這路徑就可以從外部訪問你的服務了,目的是為了不爆露你機器的IP,面向服務的路由了,給你選一個可用的出來,
#這裡zuul是自動依賴hystrix,ribbon的,不是面向單機
      path: /hello-service/**
#這裡一定要是你Eureka註冊中心的服務的名稱,是所以這裡配置serviceId因為跟eureka結合了,如果單獨使用zuul,那麼就必須寫自己機器的IP了,
#如url:http://localhost:8080/  這樣的不好就是寫死IP了,萬一這IP掛了,這高可用性,服務註冊那套東西就用不起來了
      serviceId: hello-service

zuul:
  routes:
    hello-service:
      path: /hello-service/ext/**
      serviceId: hello-service

複製程式碼

這裡的兩個zuul配置對映路徑都有/hello-service/,可以看到/hello-service/**是包括/hello-service/ext/**的,這兩個路徑進行匹配的時候是不是有衝突呀,怎麼處理呢?誰先匹配呢?

這裡是yml中定義的順序來匹配的。如果是application.properties格式的配置檔案,它這個順序是不能保證的,yml格式的配置檔案是有順序的,可以保證,這裡要注意下一下。

如果我們想定義一下匹配規則怎麼辦呢?那麼我們就需要在啟動類中定義一個bean,這個類就是決定你的路由的,如下:

這裡就不演示了,需要用到的時候自己再去慢慢查詢資料吧。

還有就是ignored-patterns:,如下:

複製程式碼

zuul:
  routes:
#標識你服務的名字,這裡可以自己定義,一般方便和規範來講還是跟自己服務的名字一樣
    hello-service:
#服務對映的路徑,通過這路徑就可以從外部訪問你的服務了,目的是為了不爆露你機器的IP,面向服務的路由了,給你選一個可用的出來,
#這裡zuul是自動依賴hystrix,ribbon的,不是面向單機
      path: /hello-service/**
#這裡一定要是你Eureka註冊中心的服務的名稱,是所以這裡配置serviceId因為跟eureka結合了,如果單獨使用zuul,那麼就必須寫自己機器的IP了,
#如url:http://localhost:8080/  這樣的不好就是寫死IP了,萬一這IP掛了,這高可用性,服務註冊那套東西就用不起來了
      serviceId: hello-service
  ignored-patterns: /hello/**

複製程式碼

ignored-patterns:表示遮蔽掉/hello/**的路徑,就算你/hello-service/hello/**也不行,照樣遮蔽。這個配置我們可以進一步細化,比如說我不想給/hello介面路由,那我們可以按照上面方式配置

如果我們還想配置一個服務的字首該怎麼辦?程式碼如下:

複製程式碼

zuul:
  routes:
#標識你服務的名字,這裡可以自己定義,一般方便和規範來講還是跟自己服務的名字一樣
    hello-service:
#服務對映的路徑,通過這路徑就可以從外部訪問你的服務了,目的是為了不爆露你機器的IP,面向服務的路由了,給你選一個可用的出來,
#這裡zuul是自動依賴hystrix,ribbon的,不是面向單機
      path: /hello-service/**
#這裡一定要是你Eureka註冊中心的服務的名稱,是所以這裡配置serviceId因為跟eureka結合了,如果單獨使用zuul,那麼就必須寫自己機器的IP了,
#如url:http://localhost:8080/  這樣的不好就是寫死IP了,萬一這IP掛了,這高可用性,服務註冊那套東西就用不起來了
      serviceId: hello-service
  prefix: /api/**

複製程式碼

可以看到那麼你訪問的服務都必須要加/api/字首,例如/api/hello-service/**

如果我們還想進行一個路徑訪問就跳轉到我的本地,那該怎麼辦呢?

我希望使用者在訪問/local時能夠自動跳轉到這個方法上來處理,那麼此時我們需要用到Zuul的本地跳轉,配置方式如下:

複製程式碼

zuul:
  prefix: /api
  ignored-patterns: /**/hello/**
  routes:
    local:
      path: /hello-service/**
      url: forward:/local

複製程式碼

我們常用的一些,對接springsecurity,或者是一些第三方元件,它們會獲取你的一些cookie資訊,那麼Zuul閘道器為了安全起見,把你的cookie資訊都給幹掉了,這個是沒辦法去搞cookie的。它是預設幹掉的。

這裡Zuul提供了zuul.sensitive-headers來給你搞這些cookie,header,這些資訊不要進行過濾。控制你的敏感資訊。

預設情況下,敏感的頭資訊無法經過API閘道器進行傳遞,我們可以通過如下配置使之可以傳遞:

複製程式碼

zuul:
  routes:
    hello-service:
      path: /hello-service/**
      serviceId: hello-service
  sensitive-headers:   cookie,header之類額東西

複製程式碼

 還可以配合Hystrix的一些詳細配置一起使用,前面也講過了。這裡就不說了