Spring Cloud學習|第五篇:路由網關-Zuul

1.Zuul簡介

zuul是從設備和網站到後端應用程序所有請求的前門,爲內部服務提供可配置的對外URL到服務的映射關係,基於JVM的後端路由器。爲所有下游服務的網關層,所有前臺請求均先達到zuul服務,而後在由zuul路由至下游服務

2.Zuul工作原理

​ Zuul是通過Servlet實現的,Zuul通過自定義的ZuulServlet來對請求進行控制,Zuul核心是一系列過濾器,可以在Http請求的發起和響應返回期間執行一系列的過濾器,Zuul包括如下4種過濾器:

(1) PRE過濾器: 它是請求具體實現邏輯前過濾,這種類型的過濾器可以做安全驗證,如身份驗證,參數驗證等

(2) ROUTING過濾器: 它用於將請求路由到具體的微服務實例。默認,它使用Http Client進行網絡請求

(3) POST過濾器: 它是處理具體邏輯後,返回前端前過濾

(4) ERROR過濾器: 它是在其它過濾器發生錯誤時執行的

@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
   try {
       // 省略代碼
       try {
           preRoute();
       } catch (ZuulException e) {
           error(e);
           postRoute();
           return;
       }
       try {
           route();
       } catch (ZuulException e) {
           error(e);
           postRoute();
           return;
       }
       try {
           postRoute();
       } catch (ZuulException e) {
           error(e);
           return;
       }

   } catch (Throwable e) {
       error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
   } finally {
       RequestContext.getCurrentContext().unset();
   }
}

分析上述代碼:

(1) 最先執行preRoute(),當出現異常時,執行error()和postRoute()

(2) 執行route(),當出現異常時,執行error()和postRoute()

(3)最後執行postRoute(),當出現異常時,執行error(),而不執行postRoute()

​ Zuul是通過RequestContext對象來共享數據的,每個請求都會創建一個RequestContext對象,每個過濾器包括如下幾個特性:

(1) FilterType: 表示過濾器類型

(2) FilterOrder:執行順序,值越小,越先執行

(3) shouldFilter: 表示是否繼續執行run中邏輯,如果爲false則表示不執行

(4) run: 執行具體的過濾細節

3.入門案例

服務名 端口 用途
eureka-ribbon-client 8764 ribbon客戶端
eureka-client 8762、8763 服務提供者
eureka-feign-client 8765 feign客戶端
eureka-zuul-client 5000 zuul客戶端
consul 8500 註冊中心
  • 創建eureka-zuul-client服務
  • 引入依賴
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
  • 書寫啓動類,添加註解@EnableZuulProxy
@EnableZuulProxy
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaZuulClientApplication {

   public static void main(String[] args) {
       SpringApplication.run(EurekaZuulClientApplication.class, args);
   }
}
  • 配置application.yml中路由規則
zuul:
 routes:
   hiapi:
     path: /hiapi/**
     serviceId: eureka-client
   ribbonapi:
     path: /ribbonapi/**
     serviceId: eureka-ribbon-client
   feignapi:
     path: /feignapi/**
     serviceId: eureka-feign-client
  • 訪問服務

    (1) 訪問 http://localhost:8766/hiapi/hi?name=lisi結果如下

    hi lisi, i am from port:8762

    hi lisi, i am from port:8763

    (2) 訪問http://localhost8766/ribbonapi/hi?name=lisi結果與上邊相同

    (3) 訪問http://localhost8766/feignapi/hi?name=lisi結果與上邊相同

4.Zuul其它操作

  • 在Zuul上配置熔斷器

    (1) 在Zuul中實現熔斷功能需要實現 ZuulFallbackProvider接口

    (2) getRoute()方法:用於確定具體路由服務

    (3) fallbackResponse():返回信息處理

@Component
public class MyZuulFallbackProvider implements FallbackProvider {

    @Override
    public String getRoute() {
        return "eureka-client";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            return response(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private ClientHttpResponse response(final HttpStatus status) {

        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 0;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("sorroy,have error".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);
                return httpHeaders;
            }
        };

    }
}
  • 自定義ZuulFilter

    自定義ZuulFilter需要集成ZuulFilter,重寫其filterTyepe()、filterOrder()、shouldFilter()、run()方法,以下自定義ZuulFilter用於判定是否攜帶token

    方法 作用
    shouldFilter 表示是否執行run()方法,false:否,true:是
    RequetContext 每個請求均會維護一個RequestContext
    setSendZuulResponse 表示是否執行下一個過濾器,false:否,true:是

    代碼演示:

@Component
public class MyFilter extends ZuulFilter {

    private Logger logger = LoggerFactory.getLogger(MyFilter.class);

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

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

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

    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String token = request.getParameter("token");
        if(Objects.isNull(token)){
            logger.warn("token不存在");
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseStatusCode(401);
            try {
                currentContext.getResponse().getWriter().write("token is empty");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        logger.info("OK");
        return null;
    }
}

5.Zuul常見使用方式

  • 每個平臺使用獨立的Zuul網關層,從而使Zuul集羣化,以下截圖來自《深入理解Spring Cloud與微服務構建》

    [外鏈圖片轉存失敗(img-B1d4KUag-1567216688888)(C:\Users\zycao\AppData\Roaming\Typora\typora-user-images\1567216636803.png)]

  • 通過Nginx根據負載均衡策略將請求路由至Zuul集羣,以下截圖來自《深入理解Spring Cloud與微服務構建》

[外鏈圖片轉存失敗(img-3LR6vDCR-1567216688889)(C:\Users\zycao\AppData\Roaming\Typora\typora-user-images\1567216653706.png)]

6.Zuul常見配置

規則 解釋 例子
/** 匹配任意路徑及字符 /provider/a,/provider/bbb,/provider/aa/bb/cc
/* 匹配任意數量字符,只有一層路徑 /provider/a,/provider/bbb,/provider/ccdc
/? 匹配單個字符,一層路徑 /provider/a,/provider/c
  • 自定義指定服務訪問路徑

path:表示請求前綴路徑

serviceId:表示服務名

zuul:
	routes:
		provider-service:
			path: /provider/**
			serviceId: provider-service
  • 簡化寫法

自動會將服務映射至provider-service

zuul:
	routes:
		provider-service: /provider/**
  • 另一種寫法

path與serviceId均不需要,自動映射,此種情況下,則會自動生成path = /provider-service/**,serviceId = provdier-service

zuul:
	routes:
		provider-service:
  • 直接配置服務ip:port

url:服務ip:port

zuul:
	routes:
		provider-service:
			ptah: /provider/**
			url: http://localhost:8000
  • forward本地跳轉

url: forward:/本地請求路徑,當請求/provider路徑時,會跳轉至zuul服務本地的路徑前綴爲/provider的接口,如url:http://localhost:7000/provider/add?a=10&b=20,則會自動匹配zuul服務中的/provider/add服務

zuul:
  routes:
    provider-service:
      path: /provider/**
      url: forward:/provider
  • 相同路徑,後者覆蓋前者

如下配置,則會請求至provider-service-b服務

zuul:
	routes:
		provider-service-a:
			path: /provider/**
			serviceId: provider-service-a
        provider-service-b:
        	path: /provider/**
        	serviceId: provider-service-b
  • 使用指定前綴

prefix: /pre,表示請求前綴爲pre,但實際請求路徑爲/provider/,可以通過stripPrefix=false關閉此功能,則啓作用的路徑爲/pre/provider/,一般不配置功能

zuul:
	prefix: /pre
	routes:
		provider-a: /provider/**
  • 服務屏蔽及路徑屏蔽

ignored-service:屏蔽服務,當請求該服務時,會報404不存在

ignored-patterns:屏蔽路徑

zuul:
	ignored-service: provider-b
	ignored-patterns: /provider-b/**
	prefix: /pre
	routes:
		provider-a: /provider-a/**
  • 敏感頭信息

在構建系統時,有許多請求頭信息不需要傳遞至下層服務,則可在zuul層進行攔截,而不傳遞

zuul:
	routes:
		provider-service:
			path: /provider/**
			sensitiveHeaders: Cookie,Set-Cookie,Authorization
			serviceId: provider-service
  • 重試機制

在生產環境中,可能存在請求失敗,需重試情況,爲了用戶無感知,可通過zuul設置重試,但要考慮服務冪等性問題

zuul:
	retryable: true # 開啓重試
	
ribbon:
	MaxAutoRetries: 1 # 同一個服務重試的次數
	MaxAutoRetriesNextServer: 1 # 切換相同服務數量
  • 自定義請求路由

通過上述路由配置我們知道,當只服務服務,不配置路徑及serviceId時,則會默認生成path:/provider-service/**,servcieId: provider-service,但有時候我們想自定義生成的路徑,則可採用如下方式,定義返回的格式爲version/{version}/{name}

@Bean
public PatternServiceRouteMapper serviceRouteMapper(){
    return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}");
}

7.參考資料

  • 《重新定義Springcloud實戰》
  • 《Springcloud微服務實戰》
  • 《深入理解Spring Cloud與微服務構建》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章