SpringCloud_網關Zuul

經過前面的學習,使用Spring Cloud實現微服務的架構基本成型,大體是這樣的:前端

 

咱們使用Spring Cloud Netflix中的Eureka實現了服務註冊中心以及服務註冊與發現;而服務間經過Ribbon或Feign實現服務的消費以及均衡負載。爲了使得服務集羣更爲健壯,使用Hystrix的融斷機制來避免在微服務架構中個別服務出現異常時引發的故障蔓延。

在該架構中,咱們的服務集羣包含:內部服務Service A和Service B,他們都會註冊與訂閱服務至Eureka Server,而Open Service是一個對外的服務,經過均衡負載公開至服務調用方。咱們把焦點彙集在對外服務這塊,直接暴露咱們的服務地址,這樣的實現是否合理,或者是否有更好的實現方式呢?

 

先來講說這樣架構須要作的一些事兒以及存在的不足:git

  • 破壞了服務無狀態特色。github

    爲了保證對外服務的安全性,咱們須要實現對服務訪問的權限控制,而開放服務的權限控制機制將會貫穿並污染整個開放服務的業務邏輯,這會帶來的最直接問題是,破壞了服務集羣中REST API無狀態的特色。spring

    從具體開發和測試的角度來講,在工做中除了要考慮實際的業務邏輯以外,還須要額外考慮對接口訪問的控制處理。api

  • 沒法直接複用既有接口。安全

    當咱們須要對一個即有的集羣內訪問接口,實現外部服務訪問時,咱們不得不經過在原有接口上增長校驗邏輯,或增長一個代理調用來實現權限控制,沒法直接複用原有的接口。架構

 

面對相似上面的問題,咱們要如何解決呢?答案是:服務網關!app

 

爲了解決上面這些問題,咱們須要將權限控制這樣的東西從咱們的服務單元中抽離出去,而最適合這些邏輯的地方就是處於對外訪問最前端的地方,咱們須要一個更強大一些的均衡負載器的 服務網關。負載均衡

 

服務網關是微服務架構中一個不可或缺的部分。經過服務網關統一貫外系統提供REST API的過程當中,除了具有服務路由均衡負載功能以外,它還具有了權限控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,爲微服務架構提供了前門保護的做用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集羣主體可以具有更高的可複用性和可測試性。ide

 

3.1.簡介

官網:https://github.com/Netflix/zuul

 

Zuul:維基百科

電影《捉鬼敢死隊》中的怪獸,Zuul,在紐約引起了巨大騷亂。

事實上,在微服務架構中,Zuul就是守門的大Boss!一夫當關,萬夫莫開!

 

 

3.2.Zuul加入後的架構

 

不論是來自於客戶端(PC或移動端)的請求,仍是服務內部調用。一切對服務的請求都會通過Zuul這個網關,而後再由網關來實現 鑑權、動態路由等等操做。Zuul就是咱們服務的統一入口。

 

3.3.快速入門

3.3.1.新建工程

填寫基本信息:

 

添加Zuul依賴:

 

 

3.3.2.編寫配置

server:
port: 10010 #服務端口
spring:
application:
  name: api-gateway #指定服務名

 

3.3.3.編寫引導類

經過@EnableZuulProxy註解開啓Zuul的功能:

@SpringBootApplication
@EnableZuulProxy // 開啓網關功能
public class ItcastZuulApplication {

   public static void main(String[] args) {
       SpringApplication.run(ItcastZuulApplication.class, args);
  }
}

 

3.3.4.編寫路由規則

咱們須要用Zuul來代理service-provider服務,先看一下控制面板中的服務狀態:

 

  • ip爲:127.0.0.1

  • 端口爲:8081

映射規則:

server:
port: 10010 #服務端口
spring:
application:
  name: api-gateway #指定服務名
zuul:
routes:
  service-provider: # 這裏是路由id,隨意寫
    path: /service-provider/** # 這裏是映射路徑
    url: http://127.0.0.1:8081 # 映射路徑對應的實際url地址

咱們將符合path 規則的一切請求,都代理到 url參數指定的地址

本例中,咱們將 /service-provider/**開頭的請求,代理到http://127.0.0.1:8081

 

3.3.5.啓動測試

訪問的路徑中須要加上配置規則的映射路徑,咱們訪問:http://127.0.0.1:10010/service-provider/user/1

 

 

3.4.面向服務的路由

在剛纔的路由規則中,咱們把路徑對應的服務地址寫死了!若是同一服務有多個實例的話,這樣作顯然就不合理了。咱們應該根據服務的名稱,去Eureka註冊中心查找 服務對應的全部實例列表,而後進行動態路由纔對!

對itcast-zuul工程修改優化:

3.4.1.添加Eureka客戶端依賴

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

 

3.4.2.添加Eureka配置,獲取服務信息

eureka:
client:
  registry-fetch-interval-seconds: 5 # 獲取服務列表的週期:5s
  service-url:
    defaultZone: http://127.0.0.1:10086/eureka

 

3.4.3.開啓Eureka客戶端發現功能

@SpringBootApplication
@EnableZuulProxy // 開啓Zuul的網關功能
@EnableDiscoveryClient
public class ZuulDemoApplication {

public static void main(String[] args) {
SpringApplication.run(ZuulDemoApplication.class, args);
}
}

 

3.4.4.修改映射配置,經過服務名稱獲取

由於已經有了Eureka客戶端,咱們能夠從Eureka獲取服務的地址信息,所以映射時無需指定IP地址,而是經過服務名稱來訪問,並且Zuul已經集成了Ribbon的負載均衡功能。

zuul:
routes:
  service-provider: # 這裏是路由id,隨意寫
    path: /service-provider/** # 這裏是映射路徑
    serviceId: service-provider # 指定服務名稱

 

3.4.5.啓動測試

再次啓動,此次Zuul進行代理時,會利用Ribbon進行負載均衡訪問:

 

 

3.5.簡化的路由配置

在剛纔的配置中,咱們的規則是這樣的:

  • zuul.routes.<route>.path=/xxx/**: 來指定映射路徑。<route>是自定義的路由名

  • zuul.routes.<route>.serviceId=service-provider:來指定服務名。

而大多數狀況下,咱們的<route>路由名稱每每和服務名會寫成同樣的。所以Zuul就提供了一種簡化的配置語法:zuul.routes.<serviceId>=<path>

比方說上面咱們關於service-provider的配置能夠簡化爲一條:

zuul:
routes:
  service-provider: /service-provider/** # 這裏是映射路徑
   #能夠省去控制器中的前綴路徑

省去了對服務名稱的配置。

 

3.6.默認的路由規則

在使用Zuul的過程當中,上面講述的規則已經大大的簡化了配置項。可是當服務較多時,配置也是比較繁瑣的。所以Zuul就指定了默認的路由規則:

  • 默認狀況下,一切服務的映射路徑就是服務名自己。例如服務名爲:service-provider,則默認的映射路徑就 是:/service-provider/**

也就是說,剛纔的映射規則咱們徹底不配置也是OK的,不信就試試看。

 

3.7.路由前綴

配置示例:

zuul:
routes:
  service-provider: /service-provider/**
  service-consumer: /service-consumer/**
prefix: /api # 添加路由前綴

咱們經過zuul.prefix=/api來指定了路由的前綴,這樣在發起請求時,路徑就要以/api開頭。

 

 

 

3.8.過濾器

Zuul做爲網關的其中一個重要功能,就是實現請求的鑑權。而這個動做咱們每每是經過Zuul提供的過濾器來實現的。

3.8.1.ZuulFilter

ZuulFilter是過濾器的頂級父類。在這裏咱們看一下其中定義的4個最重要的方法:

public abstract ZuulFilter implements IZuulFilter{

   abstract public String filterType();

   abstract public int filterOrder();
   
   boolean shouldFilter();// 來自IZuulFilter

   Object run() throws ZuulException;// IZuulFilter
}
  • shouldFilter:返回一個Boolean值,判斷該過濾器是否須要執行。返回true執行,返回false不執行。

  • run:過濾器的具體業務邏輯。

  • filterType:返回字符串,表明過濾器的類型。包含如下4種:

    • pre:請求在被路由以前執行

    • route:在路由請求時調用

    • post:在route和errror過濾器以後調用

    • error:處理請求時發生錯誤調用

  • filterOrder:經過返回的int值來定義過濾器的執行順序,數字越小優先級越高。

 

3.8.2.過濾器執行生命週期

這張是Zuul官網提供的請求生命週期圖,清晰的表現了一個請求在各個過濾器的執行順序。

 

正常流程:

  • 請求到達首先會通過pre類型過濾器,然後到達route類型,進行路由,請求就到達真正的服務提供者,執行請求,返回結果後,會到達post過濾器。然後返回響應。

異常流程:

  • 整個過程當中,pre或者route過濾器出現異常,都會直接進入error過濾器,在error處理完畢後,會將請求交給POST過濾器,最後返回給用戶。

  • 若是是error過濾器本身出現異常,最終也會進入POST過濾器,將最終結果返回給請求客戶端。

  • 若是是POST過濾器出現異常,會跳轉到error過濾器,可是與pre和route不一樣的是,請求不會再到達POST過濾器了。

全部內置過濾器列表:

 

 

3.8.3.使用場景

場景很是多:

  • 請求鑑權:通常放在pre類型,若是發現沒有訪問權限,直接就攔截了

  • 異常處理:通常會在error類型和post類型過濾器中結合來處理。

  • 服務調用時長統計:pre和post結合使用。

 

3.9.自定義過濾器

接下來咱們來自定義一個過濾器,模擬一個登陸的校驗。基本邏輯:若是請求中有access-token參數,則認爲請求有效,放行。

3.9.1.定義過濾器類

 

內容:

@Component
public class LoginFilter extends ZuulFilter {
   /**
    * 過濾器類型,前置過濾器
    * @return
    */
   @Override
   public String filterType() {
       return "pre";
  }

   /**
    * 過濾器的執行順序
    * @return
    */
   @Override
   public int filterOrder() {
       return 1;
  }

   /**
    * 該過濾器是否生效
    * @return
    */
   @Override
   public boolean shouldFilter() {
       return true;
  }

   /**
    * 登錄校驗邏輯
    * @return
    * @throws ZuulException
    */
   @Override
   public Object run() throws ZuulException {
       // 獲取zuul提供的上下文對象
       RequestContext context = RequestContext.getCurrentContext();
       // 從上下文對象中獲取請求對象
       HttpServletRequest request = context.getRequest();
       // 獲取token信息
       String token = request.getParameter("access-token");
       // 判斷
       if (StringUtils.isBlank(token)) {
           // 過濾該請求,不對其進行路由
           context.setSendZuulResponse(false);
           // 設置響應狀態碼,401
           context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
           // 設置響應信息
           context.setResponseBody("{\"status\":\"401\", \"text\":\"request error!\"}");
      }
       // 校驗經過,把登錄信息放入上下文信息,繼續向後執行
       context.set("token", token);
       return null;
  }
}

 

3.9.2.測試

沒有token參數時,訪問失敗:

 

添加token參數後:

 

 

3.10.負載均衡和熔斷

Zuul中默認就已經集成了Ribbon負載均衡和Hystix熔斷機制。可是全部的超時策略都是走的默認值,好比熔斷超時時間只有1S,很容易就觸發了。所以建議咱們手動進行配置:

hystrix:
command:
  default:
    execution:
      isolation:
        thread:
          timeoutInMilliseconds: 2000 # 設置hystrix的超時時間爲6000ms
相關文章
相關標籤/搜索