1. 程式人生 > 實用技巧 >SpringCloud元件之zuul的使用

SpringCloud元件之zuul的使用

宣告:本文根據魯班學院商鞅老師課程資料整理而來

Zuul是什麼?

  Zuul包含了對請求的路由和過濾兩個最主要的功能:

    其中路由功能負責將外部請求轉發到具體的微服務例項上,是實現外部訪問統一入口的基礎而過濾器功能則負責對請求的處理過程進行干預,是實現請求校驗、服務聚合等功能的基礎.

    Zuul和Eureka進行整合,將Zuul自身註冊為Eureka服務治理下的應用,同時從Eureka中獲得其他微服務的訊息,即以後的訪問微服務都是通過Zuul跳轉後獲得。

    注意:Zuul服務最終還是會註冊進Eureka

路由:

  首先建立一個新的moudle,在專案中加入zuul的依賴,同時加入eureka的依賴

<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>

  

  然後填寫配置檔案,因為是依賴eureka的,所以前面需要配置eureka

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: 10 #eureka客戶需要多長時間傳送心跳給eureka伺服器,表明它仍然活著,預設為30 秒 (與下面配置的單位都是秒)
    leaseExpirationDurationInSeconds: 30 #Eureka伺服器在接收到例項的最後一次發出的心跳後,需要等待多久才可以將此例項刪除,預設為90秒

spring:
  application:
    name: zuul #此例項註冊到eureka服務端的name

  再建立zuul的啟動類,啟動即可

  啟動成功。這樣 簡單的zuul就搭建好了

  在實際生產環境中,我們呼叫微服務的時候,不會直接使用微服務的路徑名稱,比如我要呼叫power 可能只要一個/power就好了 而不是/server-power,這樣就完成了使用zuul的路由轉發。

  在yml檔案中加入以下配置:

zuul:
  routes:
    mypower:
      serviceId: server-power
      path: /power/**
    myorder:
      serviceId: server-order
      path: /order/**

  my***是自己制定的名字 這個就不解釋了

  注意/ **代表是所有層級 / * 是代表一層。 如果是/ * 的話 /power/admin/getUser.do 就不會被路由 。

  這樣,我們通過訪問這個路由,zuul就會幫我們轉發到server-power服務下的程式

  

  這時候我們能通過我們自定義的規則來訪問了,但是還有一個問題,就是我們現在依然能用之前的微服務名呼叫,這樣子是不合理的,第一是有多重地址了, 第二,一

般微服務名這種最好不要暴露在外。所以我們一般會禁用微服務名方式呼叫。

  需要加入配置:

  ignored-services:server-power

  

  這時再通過server-power訪問就訪問不了了。

  這裡能發現我們不能通過微服務名來呼叫了, 不過這個配置

  如果一個一個通過微服務名來配置難免有點複雜,所以一般這樣配置來禁用所有:

  ignored-services:"*"

  可能有時候我們的介面呼叫需要一定的規範,譬如呼叫微服務的API URL字首需要加上/api 對於這種情況, zuul也考慮到了並給出瞭解決方案:

  只需要在配置中加入:prefix:/api 即可

  

  加上一個prefix 即定義好了一個字首, 那麼我們每次需要路由的時候需要加上一個/api的字首

  

過濾器:

  過濾器(filter)zuul的核心元件 zuul大部分功能都是通過過濾器來實現的。 zuul中定義了4種標準過濾器型別,這些過濾器型別對應於請求的典型生命週期。

  PRE:這種過濾器在請求被路由之前呼叫。可利用這種過濾器實現身份驗證、在 叢集中選擇請求的微服務、記錄除錯資訊等。

  ROUTING:這種過濾器將請求路由到微服務。這種過濾器用於構建傳送給微服 務的請求,並使用 Apache HttpCIientNetfilx Ribbon請求微服務。

  POST:這種過濾器在路由到微服務以後執行。這種過濾器可用來為響應新增標準 的 HTTP Header、收集統計資訊和指標、將響應從微服務傳送給客戶端等。

  ERROR:在其他階段發生錯誤時執行該過濾器。

  如果要編寫一個過濾器,則需繼承ZuulFilter類 實現其中方法:並使用@Component將其注入到spring容器中

/**
 * 自定義zuul過濾器
 */
@Component
public class LogFilter extends ZuulFilter {
    @Override
    public String filterType() {
        //定義過濾器型別
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        //過濾器質性順序,數字越小,優先順序越高
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        //是否使用過濾器,改為true。預設false
        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

zuul的容錯與回退

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

  我們自行寫一個類,繼承FallbackProvider 類 然後重寫裡面的方法,並通過註解@Component將其注入到spring容器中

@Component
public class FallBackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        return null;
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return null;
    }
}

  這裡 會發現有這2個方法需要重寫, 那麼如何來寫呢? 我們可以查閱官方文件:

  這是官方提供的demo 程式碼:

  

class MyFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        //制定為哪個微服務提供回退(這裡寫微服務名 寫*代表所有微服務)
        return "*";
    }
    
    //此方法需要返回一個ClientHttpResponse物件  ClientHttpResponse是一個介面,具體的回退邏輯要實現此介面
    //route:出錯的微服務名     cause:出錯的異常物件
  @Override
    public ClientHttpResponse fallbackResponse(String route, final 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;
            }
        };
    }
}