1. 程式人生 > >【讀書筆記】7.API服務閘道器Spring Cloud Zuul

【讀書筆記】7.API服務閘道器Spring Cloud Zuul

介紹

背景:

  1. 系統規模增大時,需要一套機制來降低維護路由規則與服務例項列表的難度
  2. 微服務架構中,解決微服務介面訪問時各種前置檢驗的冗餘問題

為了解決上述問題,API閘道器應運而生。Spring Cloud Zuul首先整合eureka,並註冊為eureka的一個應用,同時從eureka獲取其他應用的例項資訊。此外,Zuul本身還有一套過濾機制。

快速入門

本節搭建示例在上一節(feign)已提供壓縮包,下載地址===>演示專案下載

1. 搭建一個SpringBoot工程,命名api-gateway

需要引入zuul和eureka-client依賴

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

並且在啟動類貼上@EnableZuulProxy和@EnableEurekaClient 最後,配置檔案新增應用名和埠

spring.application.name=api-gateway
server.port=5555
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka

2. 配置請求路由

2.1 傳統方式(path-url)

# 傳統路由1:單例項 zuul.routes.<路由名>.path=/xx與zuul.routes.<路由名>.url=http://xx繫結
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:5555/
# url方式還支援本地跳轉(forward)
zuul.routes.api-b-url.path=/api-b-url/**
zuul.routes.api-b-url.url=forward:/local

驗證方式:

  • 訪問http://localhost:5555/api-a-url/index會跳轉到http://localhost:5555/index
  • 訪問http://localhost:5555/api-b-url/hello會跳轉到http://localhost:5555/local/hello
# 傳統路由2:多例項 zuul.routes.<路由名>.path與zuul.routes.<路由名>.serviceId繫結(需要向註冊中心註冊)
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=hello-service
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=feign-consumer
# 如果是多例項,需要手動維護例項清單
ribbon.eureka.enabled=false
hello-service.ribbon.listOfServers=http://localhost:8081

驗證方式

  • 訪問http://localhost:5555/api-a/hello會跳轉到http://localhost:8081/hello(服務hello-service在8081埠)
  • 訪問http://localhost:5555/api-b/hello會跳轉到http://localhost:9001/hello(服務feign-consumer在9001埠)

2.2 面向服務

# 服務路由 zuul.routes.<serviceId>=<path>
zuul.routes.hello-service=/api-a/**
zuul.routes.feign-consumer=/api-b/**

驗證

  • 訪問http://localhost:5555/hello-service/api-a/hello會跳轉到http://localhost:8081/hello
  • 訪問http://localhost:5555/api-b/feign-consumer會跳轉到http://localhost:9001/feign-consumer

2.3 其他配置

  • 忽略表示式
    • zuul.ignored-patterns=/**/hello/**
    • 示例:令含有/hello的介面不被訪問(但/hello1可以正常訪問)
    • 現象:訪問http://localhost:5555/api-a/hello,404
  • 路由字首(Finchley.SR1正常,已修復bug):zuul.prefix=/api-a
  • Cookie與頭資訊,登入和鑑權問題(Cookie在SpringCloud Zuul預設不傳遞)
    • zuul.routes.<router>.custom-sensitive-headers=true
  • 重定向問題,暴露了例項地址,需要設定host頭資訊:zuul.add-host-header=true
  • Hystrix和Ribbon支援(需要path與serviceId繫結的方式)
    • HystrixCommand執行的超時時間(要大於Ribbon的超時時間才能觸發重試)
      • hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
    • 請求連線的超時時間:ribbon.ConnectTimeout=3000
    • 請求處理的超時時間:ribbon.ReadTimeout=60000
    • 關閉重試機制:zuul.retryable=false
  • 自定義對映規則
        /**
         * 自定義路由規則: 微服務名,helloservice-v1 ===>/v1/helloservice/**
         *               相當於zuul.routes.helloservice-v1=/v1/helloservice/**
         * @return
         */
        @Bean
        public PatternServiceRouteMapper serviceRouteMapper() {
            return new PatternServiceRouteMapper(
                    "(?<name>^.+)-(?<version>v.+$)",
                    "${version}/${name}");
        }
    
  • 禁用過濾器
    # 禁用指定型別的自定義攔截器,zuul.<SimpleClassName>.<filterType>.disable=true
    zuul.AccessFilter.pre.disable=true
    

3. 請求過濾

3.1 自定義過濾器示例

  1. 繼承ZuulFilter並實現其抽象方法
  2. 指定過濾器型別filterType:pre,route,post,error
  3. 指定過濾器執行順序filterOrder
  4. 判斷過濾器是否需要執行shouldFilter
  5. 在run()實現過濾器的具體邏輯
  6. 使用@component標記該過濾器為Spring元件(或者手動建立) 在這裡插入圖片描述

3.2 請求生命週期

  • 過濾器型別filterType詳解
    • pre,在請求被路由之前呼叫
    • route,路由請求時呼叫
    • post,routing和error之後呼叫,返回給客戶端
    • error,處理請求發生錯誤時呼叫(上述三個階段)

核心過濾器原始碼位於spring-cloud-netflix-zuul依賴的org.springframework.cloud.netflix.zuul.filters包下 在這裡插入圖片描述

  • 異常處理(Brixton.SR5版本,即SpringCloud微服務實戰(2017.5 第一版)使用的版本) 原理:SendErrorFilter的執行順序是0,是post階段第一個執行的過濾器,執行的邏輯是上下文是否包含"error.status_code",下面的兩種方法利用了此特性
    • 方法1:在run()裡使用try-catch,一旦發生異常,在上下文中新增error.*引數,如下
      	public Object run() {
      		try {
      			int i = 1 / 0;
      		} catch(Throwable throwable) {
      			RequestContext ctx = RequestContext.getCurrentContext();
      	        Throwable throwable = ctx.getThrowable();
      	        ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      	        ctx.set("error.message", "自定義錯誤過濾器攔截到內部異常");
      	        ctx.set("error.exception", throwable.getCause());
      		}
              return null;
      }
      
    • 方法2:利用pre,route,post階段拋異常都會進入error階段的特性,自定義一個error過濾器統一處理
      @Component
      public class ErrorFilter extends ZuulFilter {
      
          @Override
          public String filterType() {
              return "error";
          }
      
          @Override
          public int filterOrder() {
              return 10;
          }
      
          @Override
          public boolean shouldFilter() {
              return true;
          }
      
          @Override
          public Object run() throws ZuulException {
              // 參考方法1
          }
      }
      
    不足與改進 方法1適合在各個過濾器中增加try-catch捕獲異常,方法2是作為方法1的補充,利用過濾器生命週期的特性,集中處理pre,route,post階段跑出的異常,一般情況兩種同時使用。 但是,如果post階段丟擲異常,error過濾器捕獲後,後續沒有post接手,也就沒有將請求響應給客戶端。 解決:自定義一個error過濾器並繼承SendErrorFilter,並且通過FilterProcessor.setProcessor(new ErrorExtFilter())啟動過濾器
    	@Component
    	public class ErrorExtFilter extends SendErrorFilter{
    	
    	    @Override
    	    public String filterType() {
    	        return "error";
    	    }
    	
    	    @Override
    	    public int filterOrder() {
    	    	//要大於上面ErrorFilter的順序(10)
    	        return 30;
    	    }
    	
    	    @Override
    	    public boolean shouldFilter() {
    	    	 // 僅處理post丟擲的異常
    	        RequestContext ctx = RequestContext.getCurrentContext();
    	        ZuulFilter failedFilter = (ZuulFilter) ctx.get("failed.filter");
    	        if (failedFilter != null && failedFilter.filterType().equals("post")) {
    	            return true;
    	        }
    	        return false;
    	    }
    	
    	    @Override
    	    public Object run() throws ZuulException {
    	        // 參考方法1
    	    }
    
  • 異常處理(Finchley.SR1版本) 先來看看SendErrorFilter的原始碼 在這裡插入圖片描述 在這裡插入圖片描述 顯然,往上下文新增error.*的方式不可行了,那麼新的方式是怎樣的呢? 在run()一樣使用try-catch,但是捕獲到異常後,直接丟擲異常,後面有過濾器接收; 同時,post階段丟擲異常的異常,也自動處理了,無需再建立一個error過濾器。

由於尚未接觸分散式配置中心Config,本章暫不介紹動態路由和動態過濾器