微服務網關netflix-zuul

微服務架構中整合網關、權限服務

認證鑑權與API權限控制在微服務架構中的設計與實現(一)

認證鑑權與API權限控制在微服務架構中的設計與實現(二)

認證鑑權與API權限控制在微服務架構中的設計與實現(三)

認證鑑權與API權限控制在微服務架構中的設計與實現(四)


引言:本文主要講下API網關的設計與實現。netflix-zuul是由netflix開源的API網關,在微服務架構下,網關作爲對外的門戶,實現動態路由、監控、授權、安全、調度等功能。

1. 網關介紹

當使用單體應用程序架構時,客戶端(web和移動端)通過向後端應用程序發起一次REST調用來獲取數據。負載均衡器將請求路由給N個相同的應用程序實例中的一個。然後應用程序會查詢各種數據庫表,並將響應返回給客戶端。微服務架構下,單體應用被切割成多個微服務,如果將所有的微服務直接對外暴露,勢必會出現安全方面的各種問題。
客戶端可以直接向每個微服務發送請求,其問題主要如下:

  • 客戶端需求和每個微服務暴露的細粒度API不匹配。
  • 部分服務使用的協議不是Web友好協議。可能使用Thrift二進制RPC,也可能使用AMQP消息傳遞協議。
  • 微服務難以重構。如果合併兩個服務,或者將一個服務拆分成兩個或更多服務,這類重構就非常困難了。

如上問題,解決的方法是使用API網關。API網關是一個服務,是系統的唯一入口。從面向對象設計的角度看,它與外觀模式類似。API網關封裝了系統內部架構,爲每個客戶端提供一個定製的API。它可能還具有其它職責,如身份驗證、監控、負載均衡、限流、降級與應用檢測。

zu

zuul

2. zuul網關

API Gateway,常見的選型有基於 Openresty 的 Kong和基於 JVM 的 Zuul,其他還有基於Go的Tyk。技術選型上,之前稍微調研了Kong,性能還可以。考慮到快速應用和二次開發,netflix-zuul也在Spring Cloud的全家桶中,和其他組件配合使用還挺方便,後期可能還會對網關的功能進行擴增,最後選了Zuul。

2.1 pom配置

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zuul</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
</dependencies>

在Spring Cloud的項目中,引入zuul的starter,consul-discovery是爲了服務的動態路由,這邊沒有用eureka,是通過註冊到consul上的服務實例進行路由。

2.2 入口類

@SpringBootApplication

@EnableZuulProxy

public class GatewayApplication {

public static void main(String[] args) {

SpringApplication.run(GatewayApplication.class, args);

}

}

Spring boot的入口類需要加上@EnableZuulProxy,下面看下這個註解。

@EnableCircuitBreaker

@EnableDiscoveryClient

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Import({ZuulProxyConfiguration.class})

public @interface EnableZuulProxy {

}

可以看到該註解還包含了@EnableCircuitBreaker 和 @EnableDiscoveryClient@EnableDiscoveryClient註解在服務啓動的時候,可以觸發服務註冊的過程,向配置文件中指定的服務註冊中心;@EnableCircuitBreaker則開啓了Hystrix的斷路器。

2.3 bootstrap.yml

server:
  port: 10101
    
#spring config
spring:
  application:
    name: gateway-server
  cloud:
    consul:
      discovery:
        preferIpAddress: true
        enabled: true
        register: true
        service-name: api-getway
        ip-address: localhost
        port: ${server.port}
        lifecycle:
          enabled: true
        scheme: http
        prefer-agent-address: false
      host: localhost
      port: 8500
#zuul config and routes
zuul:
  host:
    maxTotalConnections: 500
    maxPerRouteConnections: 50
  routes:
    user:
      path: /user/**
      ignoredPatterns: /consul
      serviceId: user
      sensitiveHeaders: Cookie,Set-Cookie

 

配置主要包括三塊,服務端口,Spring Cloud服務註冊,最後是zuul的路由配置。

默認情況下,Zuul在請求路由時,會過濾HTTP請求頭信息中的一些敏感信息,默認的敏感頭信息通過zuul.sensitiveHeaders定義,包括Cookie、Set-Cookie、Authorization。

zuul.host.maxTotalConnections配置了每個服務的http客戶端連接池最大連接,默認值是200。maxPerRouteConnections每個route可用的最大連接數,默認值是20。

2.3 支持https

上線的項目一般域名都會改爲https協議,順手寫下https的配置。

  • 首先申請https的數字證書
    在阿里雲生成的針對tomcat服務器CA證書在申請成功後, 下載相應的tomcat證書文件。 包含如下:
    1): .pfx爲keystore文件,服務器用的就是這個文件
    2): pfx-password.txt裏包含有keystore所用到的密碼
    3): 
    .key裏面包含的是私鑰,暫時沒用到此文件
    4): *.pem裏面包含的是公鑰,主要給客戶端

  • bootstrap.yml增加如下配置

      

# https
server:
  port: 5443
  http: 10101
  ssl:
    enabled: true
    key-store: classpath:214329585620980.pfx
    key-store-password: password
    keyStoreType: PKCS12
  • 同時支持http和https

       

@Bean

public EmbeddedServletContainerFactory servletContainer() {

TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {

@Override

protected void postProcessContext(Context context) {

SecurityConstraint constraint = new SecurityConstraint();

constraint.setUserConstraint("CONFIDENTIAL");

SecurityCollection collection = new SecurityCollection();

collection.addPattern("");

constraint.addCollection(collection);

context.addConstraint(constraint);

}

};

tomcat.addAdditionalTomcatConnectors(httpConnector());

return tomcat;

}

@Bean

public Connector httpConnector() {

Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");

connector.setScheme("http");

//Connector監聽的http的端口號

connector.setPort(httpPort);

connector.setSecure(false);

//監聽到http的端口號後轉向到的https的端口號

connector.setRedirectPort(securePort);

return connector;

}
  • servletContainer()EmbeddedServletContainerFactory注入到web容器中,用postProcessContext攔截所有的/*請求,並把其關聯到下面的httpConnector中。最後,在httpConnector()中,把http設爲10101端口,並把http的請求跳轉到5443的https端口,這邊是讀取的配置文件。

至此,至此同時支持https和http的API網關完成,將匹配到/user的請求,路由到user服務,是不是很簡單?下面一起深入瞭解下Zuul。

3. 一些internals

internals可以理解爲內幕。

3.1 過濾器

filter是Zuul的核心,用來實現對外服務的控制。filter的生命週期有4個,分別是pre、route、post、error,整個生命週期可以用下圖來表示。

filter

zuul過濾器

一個請求會先按順序通過所有的前置過濾器,之後在路由過濾器中轉發給後端應用,得到響應後又會通過所有的後置過濾器,最後響應給客戶端。error可以在所有階段捕獲異常後執行。

一般來說,如果需要在請求到達後端應用前就進行處理的話,會選擇前置過濾器,例如鑑權、請求轉發、增加請求參數等行爲。後面銜接auth系統部分給出具體實現,也是基於pre過濾。

在請求完成後需要處理的操作放在後置過濾器中完成,例如統計返回值和調用時間、記錄日誌、增加跨域頭等行爲。路由過濾器一般只需要選擇 Zuul 中內置的即可。

錯誤過濾器一般只需要一個,這樣可以在 Gateway 遇到錯誤邏輯時直接拋出異常中斷流程,並直接統一處理返回結果。

3.2 配置管理

後端服務 API可能根據情況,有些不需要登錄校驗了,這個配置信息怎麼動態加載到網關配置當中?筆者認爲有兩種方式:一是配置信息存到庫中,定期實現對網關服務的配置刷新;另一種就是基於配置中心服務,當配置提交到配置中心時,觸發網關服務的熱更新。

後端應用無關的配置,有些是自動化的,例如惡意請求攔截,Gateway 會將所有請求的信息通過消息隊列發送給一些實時數據分析的應用,這些應用會對請求分析,發現惡意請求的特徵,並通過 Gateway 提供的接口將這些特徵上報給 Gateway,Gateway 就可以實時的對這些惡意請求進行攔截。

3.3 隔離機制

在微服務的模式下,應用之間的聯繫變得沒那麼強烈,理想中任何一個應用超過負載或是掛掉了,都不應該去影響到其他應用。但是在 Gateway 這個層面,有沒有可能出現一個應用負載過重,導致將整個 Gateway 都壓垮了,已致所有應用的流量入口都被切斷?

這當然是有可能的,想象一個每秒會接受很多請求的應用,在正常情況下這些請求可能在 10 毫秒之內就能正常響應,但是如果有一天它出了問題,所有請求都會 Block 到 30 秒超時纔會斷開(例如頻繁 Full GC 無法有效釋放內存)。那麼在這個時候,Gateway 中也會有大量的線程在等待請求的響應,最終會吃光所有線程,導致其他正常應用的請求也受到影響。

在 Zuul 中,每一個後端應用都稱爲一個 Route,爲了避免一個 Route 搶佔了太多資源影響到其他 Route 的情況出現,Zuul 使用 Hystrix 對每一個 Route 都做了隔離和限流。

Hystrix 的隔離策略有兩種,基於線程或是基於信號量。Zuul 默認的是基於線程的隔離機制,之前章節的配置可以回顧下,這意味着每一個 Route 的請求都會在一個固定大小且獨立的線程池中執行,這樣即使其中一個 Route 出現了問題,也只會是某一個線程池發生了阻塞,其他 Route 不會受到影響。

一般使用 Hystrix 時,只有調用量巨大會受到線程開銷影響時纔會使用信號量進行隔離策略,對於 Zuul 這種網絡請求的用途使用線程隔離更加穩妥。

3.4 重試機制

一般來說,後端應用的健康狀態是不穩定的,應用列表隨時會有修改,所以 Gateway 必須有足夠好的容錯機制,能夠減少後端應用變更時造成的影響。

簡單介紹下 Ribbon 支持哪些容錯配置。重試的場景分爲三種:

  • okToRetryOnConnectErrors:只重試網絡錯誤
  • okToRetryOnAllErrors:重試所有錯誤
  • OkToRetryOnAllOperations:重試所有操作

重試的次數有兩種:

  • MaxAutoRetries:每個節點的最大重試次數
  • MaxAutoRetriesNextServer:更換節點重試的最大次數

一般來說我們希望只在網絡連接失敗時進行重試、或是對 5XX 的 GET 請求進行重試(不推薦對 POST 請求進行重試,無法保證冪等性會造成數據不一致)。單臺的重試次數可以儘量小一些,重試的節點數儘量多一些,整體效果會更好。

如果有更加複雜的重試場景,例如需要對特定的某些 API、特定的返回值進行重試,那麼也可以通過實現 RequestSpecificRetryHandler 定製邏輯(不建議直接使用 RetryHandler,因爲這個子類可以使用很多已有的功能)。

4. 總結

本文首先介紹了API網關的相關知識;其次介紹了zuul網關的配置實現,同時支持https;最後介紹了zuul網關的一些內幕原理,這邊大部分參考了網上的文章。網關作爲內網與外網之間的門戶,所有訪問內網的請求都會經過網關,網關處進行反向代理。在整個Spring Cloud微服務框架裏,Zuul扮演着”智能網關“的角色。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章