1. 程式人生 > >SpringCloud(六):服務閘道器(Zuul)

SpringCloud(六):服務閘道器(Zuul)

一、服務閘道器 官方文件:  https://springcloud.cc/spring-cloud-dalston.html#_router_and_filter_zuul

路由在微服務體系結構的一個組成部分。例如,/可以對映到您的Web應用程式,/api/users對映到使用者服務,並將/api/shop對映到商店服務。Zuul是Netflix的基於JVM的路由器和伺服器端負載均衡器。

Netflix使用Zuul進行以下操作:

認證 洞察 壓力測試 金絲雀測試 動態路由 服務遷移 負載脫落 安全 靜態響應處理 主動/主動流量管理 在微服務架構中,需要幾個基礎的服務治理元件,包括服務註冊與發現、服務消費、負載均衡、斷路器、智慧路由、配置管理等,由這幾個基礎元件相互協作,共同組建了一個簡單的微服務系統。一個簡答的微服務系統如下圖:  

在Spring Cloud微服務系統中,一種常見的負載均衡方式是,客戶端的請求首先經過負載均衡(Ngnix),再到達服務閘道器(zuul叢集),然後再到具體的服務。服務統一註冊到高可用的服務註冊中心叢集(eureka, consul),服務的所有的配置檔案由配置服務管理,配置服務的配置檔案放在git倉庫,方便開發人員隨時改配置。

二、動態路由 專案結構:  

 <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.cloud</groupId>             <artifactId>spring-cloud-starter-eureka-server</artifactId>         </dependency> application.properties

spring.application.name=service-zuul server.port=8061

## 註冊服務中心的配置 eureka.client.service-url.defaultZone=http://localhost:8001/eureka/

zuul.routes.hello-service.path=/hello-service/** zuul.routes.hello-service.serviceId=hello-service zuul.routes.<route>.path與zuul.routes.<route>.serviceId分別配置zuul攔截請求的路徑,以及攔截之後路由到的指定的eureka服務

這裡除了能結合eureka服務,指定serviceId使用,還可以指定為一個url地址,比如zuul.routes.hello-service.path=http://localhost:8011

啟動類 Application.java

package cn.saytime;

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication @EnableZuulProxy @EnableEurekaClient public class ServiceZuulApplication {

    public static void main(String[] args) {         SpringApplication.run(ServiceZuulApplication.class, args);     } } 這裡使用@EnableZuulProxy表示開啟zuul閘道器。

@EnableEurekaClient為了結合eureka,呼叫註冊在eureka中的服務,所以zuul這裡也是作為eureka的客戶端。當然這裡也可以使用@EnableDiscoveryClient,可以發現@EnableEurekaClient註解實現包含了@EnableDiscoveryClient,這裡只用來呼叫eureka服務的話,兩個都可以使用,如果要使用其他的,比如consul,那就只能用@EnableDiscoveryClient了。

測試 啟動eureka:8001, hello-service:8011,8012,zuul-service:8061

我們訪問:http://localhost:8061/hello-service/hello?name=zuul

hello, zuul 表示路由成功。而且重複訪問還可以發現預設使用了ribbon負載均衡。

接下來我們改成:

zuul.routes.hello-service.path=/hello-service/** zuul.routes.hello-service.url=http://localhost:8011 同樣的,訪問:http://localhost:8061/hello-service/hello?name=zuul

hello, zuul 當然如果我們把連線改成百度網址,那麼就直接跳轉到百度去了。

既然在SpringCloud生態體系使用zuul,那麼最好結合eureka ribbon使用。

三、閘道器過濾 如果在整個體系中,每個微服務都自己去管理使用者狀態,那顯然是不可取的,所以一般都是放在服務閘道器中的。那麼我們就需要在服務閘道器中統一處理使用者登入狀態,是否放行使用者請求。

這裡我們來實現zuul閘道器過濾器,實現每個介面獲取引數中的access_token, 判斷是否合法,合法則放行,不合法則攔截並提示錯誤。

package cn.saytime.filter;

import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**  * 服務請求過濾器  */ @Component public class AccessFilter extends ZuulFilter {

    private static Logger log = LoggerFactory.getLogger(AccessFilter.class);

    @Override     public String filterType() {         return "pre";     }

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

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

    @Override     public Object run() {         RequestContext requestContext = RequestContext.getCurrentContext();         HttpServletRequest request = requestContext.getRequest();

        log.info("{} >>> {}", request.getMethod(), request.getRequestURL().toString());

        String access_token = request.getParameter("access_token");         if(StringUtils.isBlank(access_token) || !"test".equals(access_token)){             // zuul過濾該請求             requestContext.setSendZuulResponse(false);             requestContext.setResponseStatusCode(401);             requestContext.setResponseBody("token is invalid");             log.info("the request {} is fail, the token is invalid", request.getRequestURL().toString());         } else {             log.info("the request {} is ok", request.getRequestURL().toString());         }         return null;     } } filterType:返回一個字串代表過濾器的型別,在zuul中定義了四種不同生命週期的過濾器型別,具體如下:

pre:路由之前 routing:路由之時 post: 路由之後 error:傳送錯誤呼叫 filterOrder:過濾的順序

shouldFilter:這裡可以寫邏輯判斷,是否要過濾,本文true,永遠過濾。 run:過濾器的具體邏輯。可用很複雜,包括查sql,nosql去判斷該請求到底有沒有許可權訪問。 上面指定filterType:pre表示在路由之前攔截請求,shouldFilter始終為true,表示永遠過濾,並執行run方法。

requestContext.setSendZuulResponse(false); 表示不繼續轉發該請求。  requestContext.setResponseStatusCode(401);返回的狀態碼,這裡為401  requestContext.setResponseBody("token is invalid"); 返回的內容,可以指定為一串json

測試 重新啟動 zuul-service:8061

訪問:http://localhost:8061/hello-service/hello?name=zuul

瀏覽器返回401

token is invalid console 控制檯日誌輸出

GET >>> http://localhost:8061/hello-service/hello the request http://localhost:8061/hello-service/hello is fail, the token is invalid 表示攔截成功。

接下來我們訪問:http://localhost:8061/hello-service/hello?name=zuul&access_token=test

hello, zuul console 控制檯日誌輸出

GET >>> http://localhost:8061/hello-service/hello the request http://localhost:8061/hello-service/hello is ok 表示校驗過濾,放行請求。

四、請求生命週期  

從上圖中,我們可以看到,當外部HTTP請求到達API閘道器服務的時候,首先它會進入第一個階段pre,在這裡它會被pre型別的過濾器進行處理,該型別的過濾器主要目的是在進行請求路由之前做一些前置加工,比如請求的校驗等。在完成了pre型別的過濾器處理之後,請求進入第二個階段routing,也就是之前說的路由請求轉發階段,請求將會被routing型別過濾器處理,這裡的具體處理內容就是將外部請求轉發到具體服務例項上去的過程,當服務例項將請求結果都返回之後,routing階段完成,請求進入第三個階段post,此時請求將會被post型別的過濾器進行處理,這些過濾器在處理的時候不僅可以獲取到請求資訊,還能獲取到服務例項的返回資訊,所以在post型別的過濾器中,我們可以對處理結果進行一些加工或轉換等內容。另外,還有一個特殊的階段error,該階段只有在上述三個階段中發生異常的時候才會觸發,但是它的最後流向還是post型別的過濾器,因為它需要通過post過濾器將最終結果返回給請求客戶端