Spring cloud(5)-路由閘道器(Zuul)

ohcomeyes發表於2018-10-24

Spring Cloud Zuul(路由閘道器)

基於Netflix的開源框架zuul實現的 各個微服務之間都不存在單點,並且都註冊於 Eureka ,基於此進行服務的註冊於發現,再通過 Ribbon 進行服務呼叫,並具有客戶端負載功能。 問題點?

  • 將我們具體的微服務地址加埠暴露出去?
  • 如果系統龐大,服務拆分的足夠多那又有誰來維護這些路由關係呢?
  • 服務呼叫之間的一些鑑權、簽名校驗怎麼做?
  • 由於服務端地址較多,客戶端請求維護難度?

針對上述問題:SpringCloud 全家桶自然也有對應的解決方案: Zuul Spring Cloud Zuul 將自身註冊為 Eureka 服務治理下的應用,從 Eureka 中獲取服務例項資訊,從而維護路由規則和服務例項。

API服務閘道器(API Gateway)服務

我們在所有的請求進來之前抽出一層閘道器應用,將服務提供的所有細節都進行了包裝,這樣所有的客戶端都是和閘道器進行互動(統一入口),簡化了客戶端開發

  • 將細粒度的服務組合起來提供一個粗粒度的服務
  • 所有請求都匯入一個統一的入口,那麼整個服務只需要暴露一個api
  • 對外遮蔽了服務端的實現細節,也減少了客戶端與伺服器的網路呼叫次數
    zuul
    通過Zuul我們可以完成以下功能
  • 客戶端負載:Zuul 註冊於 Eureka 並整合了 Ribbon 所以自然也是可以從註冊中心獲取到服務列表進行客戶端負載。
  • 動態路由:解放運維。
  • 監控與審查
  • 身份認證與安全
  • 壓力測試: 逐漸增加某一個服務叢集的流量,以瞭解服務效能;
  • 金絲雀測試
  • 服務遷移
  • 負載剪裁: 為每一個負載型別分配對應的容量,對超過限定值的請求棄用;
  • 靜態應答處理

新增依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
複製程式碼

spring-cloud-starter-zuul本身已經整合了hystrix和ribbon 注意點

  • (傳統路由)當使用path與url的對映關係來配置路由規則時,對於路由轉發的請求則不會採用HystrixCommand來包裝,所以這類路由請求就沒有執行緒隔離和斷路器保護功能,並且也不會有負載均衡的能力
  • (服務路由)使用Zuul的時候儘量使用**path和serviceId**的組合進行配置,這樣不僅可以保證API閘道器的健壯和穩定,也能用到Ribbon的客戶端負載均衡功能。

開啟服務註冊

@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
}
複製程式碼

路由配置順序注意

  • 按照配置的順序進行路由規則控制,則需要使用YAML(檔案格式,類似propeties)
  • 如果是使用propeties檔案,則會丟失順序。
# 專案配置
spring.application.name=sbc-gateway-zuul
server.port=8383
# 簡化路由配置[zuul.routes.微服務Id = 指定路徑]
zuul.routes.user = /api/**(指定正規表示式來匹配路徑)

# 忽略指定微服務
zuul.ignored-services=微服務Id1,微服務Id2...

# 指定多個微服務負載
zuul.routes.user.path: /user/**
zuul.routes.user.serviceId: user
ribbon.eureka.enabled=false
user.ribbon.listOfServers: 微服務1, 微服務2...

# forward跳轉本地
zuul.routes.user.path=/user/**
zuul.routes.user.url=forward:/user

# 對指定路由開啟自定義敏感頭
zuul.routes.[route].customSensitiveHeaders=true 
zuul.routes.[route].sensitiveHeaders=[這裡設定要過濾的敏感頭]
# 全域性
zuul.sensitiveHeaders=[這裡設定要過濾的敏感頭]

# --Zuul的Http客戶端支援Apache Http、Ribbon的RestClient和OkHttpClient,預設使用Apache HTTP客戶端。--
# 啟用Ribbon的RestClient
ribbon.restclient.enabled=true

# 啟用OkHttpClient
ribbon.okhttp.enabled=true

# eureka地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
複製程式碼

Zuul容錯與回退

  • Zuul的Hystrix監控的粒度微服務,而不是某個API
  • Zuul提供了一個ZuulFallbackProvider介面(最新版本建議直接繼承類FallbackProvider,新版增加異常處理),實現該介面就可以為Zuul實現回退功能
/**
 * @program: spring-cloud
 * @description: zuul容錯與回退
 * @author: Mr.Tang
 * @create: 2018-06-20 14:25
 **/
@Component
public class ProductServiceFallbackProvider implements FallbackProvider {
    protected final Logger logger = LoggerFactory.getLogger(ProductServiceFallbackProvider.class);
    @Override
    public String getRoute() {
        // 注意: 這裡是route的名稱,不是服務的名稱,否則無法起到回退作用 
        return "ribbon-consumer";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
                return headers;
            }

            @Override
            public InputStream getBody() throws IOException {
                logger.info("ribbon-consumer: 服務不可以");
                return new ByteArrayInputStream("該服務暫不可用,請稍後重試!".getBytes());
            }

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

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

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

            @Override
            public void close() {

            }
        };
    }
    @Override
    public ClientHttpResponse fallbackResponse(Throwable cause) {
        if (cause != null && cause.getCause() != null) {
            String reason = cause.getCause().getMessage();
            logger.info("Excption {}",reason);
        }
        return fallbackResponse();
    }
}
複製程式碼

路由重試(或者啟動備用服務來分散壓力) 網路等原因導致服務不可用,需要進行重試,zuul結合Spring Retry 新增Spring Retry依賴

<dependency>
	<groupId>org.springframework.retry</groupId>
	<artifactId>spring-retry</artifactId>
</dependency>
複製程式碼

開啟重試 不使用重試,就必須考慮到是否能夠接受單個服務例項關閉和eureka重新整理服務列表之間帶來的短時間的熔斷(在未重新整理之前還是會訪問到熔斷中的服務降級方法)

#是否開啟重試功能
zuul.retryable=true
#對當前服務的重試次數
ribbon.MaxAutoRetries=2
#切換相同Server的次數
ribbon.MaxAutoRetriesNextServer=0
複製程式碼

注意 斷路器的其中一個作用就是防止故障或者壓力擴散。當壓力過大是,一個服務的當機,路由將壓力轉向其它服務時有可能也會壓垮其它服務。斷路器的形式更像是提供一種友好的錯誤資訊,提高服務之間的容錯性。

Zuul過濾器

Zuul大部分功能都是通過過濾器來實現的。Zuul中定義了四種標準過濾器型別

  • PRE 路由之前。我們可利用這種過濾器實現身份驗證、在叢集中選擇請求的微服務、記錄除錯資訊等。
  • ROUTING 路由之時。這種過濾器用於構建傳送給微服務的請求,並使用Apache HttpClient或Netfilx Ribbon請求微服務。
  • POST 路由之後。這種過濾器可用來為響應新增標準的HTTP Header、收集統計資訊和指標、將響應從微服務傳送給客戶端等。
  • ERROR 在其他階段發生錯誤時執行該過濾器。

Zuul中預設實現的Filter

型別 順序 過濾器 功能
pre -3 ServletDetectionFilter 標記處理Servlet的型別
pre -2 Servlet30WrapperFilter 包裝HttpServletRequest請求
pre -1 FormBodyWrapperFilter 包裝請求體
route 1 DebugFilter 標記除錯標誌
route 5 PreDecorationFilter 處理請求上下文供後續使用
route 10 RibbonRoutingFilter serviceId請求轉發
route 100 SimpleHostRoutingFilter url請求轉發
route 500 SendForwardFilter forward請求轉發
post 0 SendErrorFilter 處理有錯誤的請求響應
post 1000 SendResponseFilter 處理正常的請求響應

除了預設的過濾器型別,Zuul還允許我們建立自定義的過濾器型別。自定義(PRE)如下: ZuulFilter 是Zuul中核心元件,通過繼承該抽象類,覆寫幾個關鍵方法達到自定義排程請求的作用

/**
 * @program: spring-cloud
 * @description: token驗證
 * @author: Mr.Tang
 * @create: 2018-06-21 10:08
 **/
public class TokenFilter extends ZuulFilter{
    protected final Logger logger = LoggerFactory.getLogger(TokenFilter.class);
    @Override
    public String filterType() {
        //pre:路由之前
        //routing:路由中
        //post:路由後
        //error:發生錯誤時
        return "pre";
    }

    @Override
    public int filterOrder() {
        // filter執行順序,通過數字指定 ,優先順序為0,數字越大,優先順序越低
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        // 是否執行該過濾器,此處為true,說明需要過濾
        return true;
    }

    @Override
    public Object run() {
        this.logger.info("This is pre-type zuul filter.");
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        String token = request.getParameter("token");
        if(StringUtils.isNotBlank(token)){
            requestContext.setSendZuulResponse(true); //對請求進行路由
            requestContext.setResponseStatusCode(200);
            requestContext.set("isSuccess", true);
            return null;
        }else{
            requestContext.setSendZuulResponse(false); //不對其進行路由
            requestContext.setResponseStatusCode(400);
            requestContext.setResponseBody("token is empty");
            requestContext.set("isSuccess", false);
            return null;
        }
    }
}
複製程式碼
  • filterType()方法是該過濾器的型別;
  • filterOrder()方法返回的是執行順序;
  • shouldFilter()方法則是判斷是否需要執行該過濾器;
  • run()則是所要執行的具體過濾動作。

過濾器之間並不會直接進行通訊,而是通過RequestContext來共享資訊,RequestContext是執行緒安全的。 開啟過濾器 在程式的啟動類 新增 Bean

@Bean
public TokenFilter tokenFilter() {
	return new TokenFilter();
}
複製程式碼

禁用過濾器 格式為:zuul.[filter-name].[filter-type].disable=true 如:

zuul.FormBodyWrapperFilter.pre.disable=true
複製程式碼

@EnableZuulServer和@EnableZuulProxy

  • @EnableZuulProxy包含@EnableZuulServer的功能,不會自動載入任何代理過濾器。
  • 當我們需要執行一個沒有代理功能的Zuul服務,或者有選擇的開關部分代理功能時,那麼需要使用 @EnableZuulServer替代 @EnableZuulProxy。 這時候我們可以新增任何 ZuulFilter型別實體類都會被自動載入,

Zuul 高可用

Zuul 現在既然作為了對外的第一入口,那肯定不能是單節點,對於 Zuul 的高可用有以下兩種方式實現。

  1. Eureka 高可用:部署多個 Zuul 節點,並且都註冊於 Eureka(有一個嚴重的缺點:那就是客戶端也得註冊到 Eureka 上才能對 Zuul 的呼叫做到負載,這顯然是不現實的。)
  2. 基於 Nginx 高可用:在呼叫 Zuul 之前使用 Nginx 之類的負載均衡工具進行負載,這樣 Zuul 既能註冊到 Eureka ,客戶端也能實現對 Zuul 的負載
    nginx+zuul

結語

github上有關於Spring Cloud完整的部署。
其它相關文章
Spring cloud(1)-簡介以及選擇
Spring cloud(2)-服務發現(Eureka,Consul)
Spring cloud(3)-負載均衡(Feign,Ribbon)
Spring cloud(4)-熔斷(Hystrix)
Spring cloud(5)-路由閘道器(Zuul)
Spring cloud(6)-配置管理及重新整理(Config,Bus)
最後,給個 star 吧~
個人部落格~
簡書~

相關文章