天天看點

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

目錄

前言

7.1.5 Hystrix

7.1.5.1 什麼是Hystrix

7.1.5.2 雪崩問題

7.1.5.3 線程隔離,服務降級

7.1.5.4 搭建

7.1.5.4.1 引入依賴

7.1.5.4.2 開啟熔斷

7.1.5.4.3 編寫降級邏輯

1.局部降級邏輯

2.全局降級邏輯

7.1.5.4.4 設定逾時

7.1.5.5 服務熔斷

7.1.6 Feign

7.1.6.1 什麼是Feign

7.1.6.2 快速入門

7.1.6.2.1 引入依賴

7.1.6.2.2 開啟Feign功能

7.1.6.2.3 Feign的用戶端

7.1.6.2.4 測試

7.1.6.3 負載均衡

7.1.6.4 Hystrix

7.1.6.5 請求壓縮

7.1.6.6 日志級别

7.1.7 Zuul網關

7.1.7.1 Zuul概述

7.1.7.1.1 問題

7.1.7.1.2 Zuul

7.1.7.1.3 Zuul加入後的架構

7.1.7.2 快速入門

7.1.7.2.1 添加依賴

7.1.7.2.2 配置

7.1.7.2.3 啟動類添加注解

7.1.7.2.4 編寫路由規則

7.1.7.3 面向服務的路由

7.1.7.3.1 添加Eureka用戶端依賴

7.1.7.3.2 添加Eureka配置,擷取服務資訊

7.1.7.3.3 啟動類開啟Eureka用戶端發現功能

7.1.7.3.4 修改映射配置,通過服務名稱擷取

7.1.7.4 簡化的路由配置

7.1.7.5 預設的路由規則

7.1.7.6 路由字首

7.1.7.7 過濾器

7.1.7.7.1 ZuulFilter

7.1.7.7.2 過濾器執行生命周期

7.1.7.8 自定義過濾器

7.1.7.9 負載均衡和熔斷

前言

SpringCloud元件使用套路,三步走

  1. 引入元件的啟動器
  2. 覆寫預設配置
  3. 在引導類上添加注解,開發相關元件 

重點了解SpringCloud這些元件的使用細節,知道每個階段出現的注解,它們的順序以及具體功能

7.1.5 Hystrix

7.1.5.1 什麼是Hystrix

Hystrix,[hɪst'rɪks],英文意思是豪豬,是一種保護機制。

Hystrix也是Netflix公司的一款元件。

首頁:GitHub - Netflix/Hystrix: Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

Hystix作用:

Hystix是Netflix開源的一個延遲和容錯庫,用于隔離通路遠端服務、第三方庫,防止出現級聯失敗。  

7.1.5.2 雪崩問題

微服務中,服務間調用關系錯綜複雜,一個請求,可能需要調用多個微服務接口才能實作,會形成非常複雜的調用鍊路:  

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

 如圖,一次業務請求,需要調用A、P、H、I四個服務,這四個服務又可能調用其它服務。

如果此時,某個服務出現異常:

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

例如微服務I發生異常,請求阻塞,使用者不會得到響應,則tomcat的這個線程不會釋放,于是越來越多的使用者請求到來,越來越多的線程會阻塞:

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

 伺服器支援的線程和并發數有限,請求一直阻塞,會導緻伺服器資源耗盡,進而導緻所有其它服務都不可用,形成雪崩效應。

這就好比,一個汽車生産線,生産不同的汽車,需要使用不同的零件,如果某個零件因為種種原因無法使用,那麼就會造成整台車無法裝配,陷入等待零件的狀态,直到零件到位,才能繼續組裝。  此時如果有很多個車型都需要這個零件,那麼整個工廠都将陷入等待的狀态,導緻所有生産都陷入癱瘓。一個零件的波及範圍不斷擴大。

Hystix解決雪崩問題的手段有兩個:

  • 線程隔離
  • 服務熔斷

7.1.5.3 線程隔離,服務降級

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

Hystrix為每個依賴服務調用配置設定一個小的線程池,如果線程池已滿調用将被立即拒絕,預設不采用排隊.加速失敗判定時間。

使用者的請求将不再直接通路服務,而是通過線程池中的空閑線程來通路服務,如果線程池已滿,或者請求逾時,則會進行降級處理,什麼是服務降級?

服務降級:優先保證核心服務,而非核心服務不可用或弱可用。

使用者的請求故障時,不會被阻塞,更不會無休止的等待或者看到系統崩潰,至少可以看到一個執行結果(例如傳回友好的提示資訊) 。

服務降級雖然會導緻請求失敗,但是不會導緻阻塞,而且最多會影響這個依賴服務對應的線程池中的資源,對其它服務沒有響應。

觸發Hystix服務降級的情況:

  • 線程池已滿
  • 請求逾時

7.1.5.4 搭建

7.1.5.4.1 引入依賴

在消費者的pom.xml中引入Hystrix依賴:

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

7.1.5.4.2 開啟熔斷

在消費者啟動器中加入注解

@EnableCircuitBreaker

或者使用組合注解

@SpringCloudApplication  

/*
@SpringBootApplication
@EnableDiscoveryClient //springcloud提供的
@EnableCircuitBreaker//啟用斷路器(熔斷器)
*/
@SpringCloudApplication  //組合注解,相當于上面三個注解
public class ConsumerApplication {
           

7.1.5.4.3 編寫降級邏輯

1.局部降級邏輯

降級方法要和被降級的方法參數清單一緻,傳回值也要一緻

在需要降級的方法中使用注解@HystrixCommand(fallbackMethod = "findByIdFallback") ,指定降級方法的方法名

@Controller
@RequestMapping("/consumer")
public class UserController {
    @Resource
    RestTemplate restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;


    @RequestMapping("/findbyid/{id}")
    @ResponseBody
   @HystrixCommand(fallbackMethod = "findByIdFallback") //指定降級方法的方法名
    public String findById(@PathVariable Integer id){
        // 調用
        User user = restTemplate.getForObject("http://service-provider/provider/findbyid/" + id, User.class);
        return user.toString();
    }
   //降級方法要和被降級的方法參數清單一緻,傳回值也要一緻
    public String findByIdFallback(Integer id){
        //處理
        return "伺服器正忙,請稍後重試";
    }
}
           

2.全局降級邏輯

全局降級邏輯(預設FallBack、預設降級邏輯)

局部降級邏輯把fallback寫在了某個業務方法上,如果這樣的方法很多,會很備援。是以把Fallback配置加在類上),實作預設fallback:

@DefaultProperties(defaultFallback = "defaultFallBack"):在類上指明統一的失敗降級方法

@HystrixCommand:在方法上直接使用該注解,使用預設的降級方法。

定義defaultFallback:預設降級方法,不用任何參數,以比對更多方法,但是傳回值一定一緻

@Controller
@RequestMapping("/consumer")
@DefaultProperties(defaultFallback = "defaultFasllback")//指定全局降級方法的方法名
public class UserController {
    @Resource
    RestTemplate restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;
    @Autowired
    UserFeign userFeign;

    @RequestMapping("/findbyid/{id}")
    @ResponseBody
    @HystrixCommand //方法上的注解不能省略,因為不是所有的方法都需要降級方法
    public String findById(@PathVariable Integer id){
        User user = userFeign.findById(id);
        return user.toString();
    }

    //全局降級方法
    //降級方法要和被降級的方法參數清單一緻,那跟誰保持一緻呢?那就不要參數了,傳回值也一緻,那就用string
    public String defaultFasllback(){
        return "網絡擁擠,請稍後再試~~~";
    }
}
           

7.1.5.4.4 設定逾時

在之前的案例中,請求在超過1秒後都會傳回錯誤資訊,這是因為Hystix的預設逾時時長為1,我們可以通過配置修改這個值:

我們可以通過

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds

來設定Hystrix逾時時間。該配置沒有提示。

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000 # 設定hystrix的逾時時間為6000ms
           

改造服務提供者

改造服務提供者的UserController接口,随機休眠一段時間,以觸發熔斷:

@GetMapping("{id}")
public User queryUserById(@PathVariable("id") Long id) {
    try {
        Thread.sleep(6000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return this.userService.queryUserById(id);
}
           

7.1.5.5 服務熔斷

熔斷器,也叫斷路器,其英文單詞為:Circuit Breaker

熔斷機制,在分布式系統中,服務調用方可以自己進行判斷某些服務反應慢或者存在大量逾時的情況時,能夠主動熔斷,防止整個系統被拖垮。

Hystrix可以實作彈性容錯,當情況好轉後,可以自動連接配接。

通過斷路的方式,可以将後續請求直接拒絕,一段時間後允許部分請求通過,如果調用成功則回到閉合狀态,否則繼續斷開

熔斷狀态機3個狀态:

  • Closed:關閉狀态,所有請求都正常通路。
  • Open:打開狀态,所有請求都會被降級。Hystix會對請求情況計數,當一定時間内失敗請求百分比達到門檻值,則觸發熔斷,斷路器會完全打開。預設失敗比例的門檻值是50%,請求次數最少不低于20次。
  • Half Open:半開狀态,open狀态不是永久的,打開後會進入休眠時間(預設是5S)。随後斷路器會自動進入半開狀态。此時會釋放部分請求通過,若這些請求都是健康的,則會完全關閉斷路器,否則繼續保持打開,再次進行休眠計時

不過,預設的熔斷觸發要求較高,休眠時間窗較短,為了測試友善,我們可以通過配置修改熔斷政策:

circuitBreaker.requestVolumeThreshold=10
circuitBreaker.sleepWindowInMilliseconds=10000
circuitBreaker.errorThresholdPercentage=50
           
  • requestVolumeThreshold:觸發熔斷的最小請求次數,預設20
  • errorThresholdPercentage:觸發熔斷的失敗請求最小占比,預設50%
  • sleepWindowInMilliseconds:休眠時長,預設是5000毫秒

7.1.6 Feign

7.1.6.1 什麼是Feign

Fein ,[feɪn],假裝,僞裝

在前面的學習中,我們使用了Ribbon的負載均衡功能,大大簡化了遠端調用時的代碼:

String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);      

如果就學到這裡,你可能以後需要編寫類似的大量重複代碼,格式基本相同,無非參數不一樣。有沒有更優雅的方式,來對這些代碼再次優化呢?

這就是我們接下來要學的Feign的功能了。

什麼是僞裝?

Feign可以把Rest的請求進行隐藏,僞裝成類似SpringMVC的Controller一樣。你不用再自己拼接url,拼接參數等等操作,一切都交給Feign去做。

Feign是 Netflix開發的聲明式、模闆化的HTTP用戶端,其靈感來自 Retrofit,JAXRS-2.0以及WebSocket。Feign 可幫助我們更加便捷、優雅地調用 HTTP API。

在Spring Cloud中,使用Feign 非常簡單一一建立一個接口,并在接口上添加一些注解代碼就完成了。Feign支援多種注解,例如 Feign自帶的注解或者JAX-RS注解等。

Spring Cloud對 Feign進行了增強,使 Feign 支援了Spring MVC注解,并整合了 Ribbon和 Eureka,進而讓 Feign的使用更加友善。

7.1.6.2 快速入門

7.1.6.2.1 引入依賴

在消費者子產品中導入依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
           

7.1.6.2.2 開啟Feign功能

我們在啟動類上,添加@EnableFeignClients注解,開啟Feign功能

/*
@SpringBootApplication
@EnableDiscoveryClient //springcloud提供的
@EnableCircuitBreaker//啟用斷路器(熔斷器)
*/

@SpringCloudApplication  //組合注解,相當于上面三個注解
@EnableFeignClients // 開啟feign用戶端
public class ConsumerApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConsumerApplication.class,args);
        }
/*
删除
    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }
*/

}

           
删除RestTemplate:feign已經自動內建了Ribbon負載均衡的RestTemplate。是以,此處不需要再注冊RestTemplate。  

7.1.6.2.3 Feign的用戶端

建立UserFeign 接口

@FeignClient(value = "service-provider",fallback = UserFeignImpl.class)   //feign用戶端,用于調用遠端服務
public interface UserFeign {
    //可以使用springmvc注解
    //注意完整路徑
    @RequestMapping("/provider/findbyid/{id}")
    User findById(@PathVariable("id") int id);
}
           

建立UserFeign 實作類UserFeignImpl 

@Component //放入容器
public class UserFeignImpl implements UserFeign {
    @Override
    public User findById(int id) {
        User user = new User();
        user.setName("伺服器繁忙,請稍後再試");
        return user;
    }
}
           
  • 這是一個接口,Feign會通過動态代理,幫我們生成實作類。這點跟mybatis的mapper很像
  • @FeignClient

    ,聲明這是一個Feign用戶端,類似

    @Mapper

    注解。同時通過

    value

    屬性指定服務名稱
  • 接口中的定義方法,完全采用SpringMVC的注解,Feign會根據注解幫我們生成URL,并通路擷取結果

修改controller中的調用邏輯

通過userFeign來擷取

@Controller
@RequestMapping("/consumer")
@DefaultProperties(defaultFallback = "defaultFasllback")//指定全局降級方法的方法名
public class UserController {
    @Resource
    RestTemplate restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;
    @Autowired
    UserFeign userFeign;

    @RequestMapping("/findbyid/{id}")
    @ResponseBody
    @HystrixCommand //方法上的注解不能省略,因為不是所有的方法都需要降級方法
    public String findById(@PathVariable Integer id){
        // 調用
        User user = userFeign.findById(id);
        return user.toString();
    }
}
           

7.1.6.2.4 測試

啟動子產品

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

 輸入  localhost:消費者端口号/以及路徑/二級路徑/id

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

7.1.6.3 負載均衡

Feign中本身已經內建了Ribbon依賴和自動配置:

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

是以我們不需要額外引入依賴,也不需要再注冊

RestTemplate

對象。

7.1.6.4 Hystrix

Feign預設也有對Hystrix的內建

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

 隻不過,預設情況下是關閉的。我們需要通過下面的參數來開啟:(在消費者子產品添加配置内容)

feign:
  hystrix:
    enabled: true # 開啟Feign的熔斷功能
           

但是,Feign中的Fallback配置不像hystrix中那樣簡單了。

1)首先,我們要定義一個類UserClientFallback,實作剛才編寫的UserClient,作為fallback的處理類

@Component
public class UserClientFallback implements UserClient {
​
    @Override
    public User queryById(Long id) {
        User user = new User();
        user.setUserName("伺服器繁忙,請稍後再試!");
        return user;
    }
}
           

2)然後在UserFeignClient中,指定剛才編寫的實作類

@FeignClient(value = "service-provider", fallback = UserClientFallback.class) // 标注該類是一個feign接口
public interface UserClient {
​
    @GetMapping("user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
           

3)重新開機測試:

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

7.1.6.5 請求壓縮

Spring Cloud Feign 支援對請求和響應進行GZIP壓縮,以減少通信過程中的性能損耗。通過下面的參數即可開啟請求與響應的壓縮功能:

feign:
  compression:
    request:
      enabled: true # 開啟請求壓縮
    response:
      enabled: true # 開啟響應壓縮
           

同時,我們也可以對請求的資料類型,以及觸發壓縮的大小下限進行設定:

feign:
  compression:
    request:
      enabled: true # 開啟請求壓縮
      mime-types: text/html,application/xml,application/json # 設定壓縮的資料類型
      min-request-size: 2048 # 設定觸發壓縮的大小下限
           

注:上面的資料類型、壓縮大小下限均為預設值。

7.1.6.6 日志級别

前面講過,通過

logging.level.xx=debug

來設定日志級别。然而這個對Fegin用戶端而言不會産生效果。因為

@FeignClient

注解修飾的用戶端在被代理時,都會建立一個新的Fegin.Logger執行個體。我們需要額外指定這個日志的級别才可以。

1)設定cn.bl包下的日志級别都為debug

logging:
  level:
    cn.bl: debug
           

2)編寫配置類,定義日志級别

@Configuration
public class FeignLogConfiguration {
​
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}
           

這裡指定的Level級别是FULL,Feign支援4種級别:

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關
  • NONE:不記錄任何日志資訊,這是預設值
  • BASIC:僅記錄請求的方法,URL以及響應狀态碼和執行時間
  • HEADERS:在BASIC的基礎上,額外記錄了請求和響應的頭資訊
  • FULL:記錄所有請求和響應的明細,包括頭資訊、請求體、中繼資料

3)在FeignClient中指定配置類:

@FeignClient(value = "service-privider", fallback = UserFeignClientFallback.class, configuration = FeignConfig.class)
public interface UserFeignClient {
    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
           

4)重新開機項目,即可看到每次通路的日志:

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

7.1.7 Zuul網關

7.1.7.1 Zuul概述

7.1.7.1.1 問題

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

我們使用SpringCloud Netflix中的Eureka實作了服務注冊中心以及服務注冊與發現;而服務間通過Ribbon或Feign實作服務的消費以及均衡負載。為了使得服務叢集更為健壯,使用Hystrix的融斷機制來避免在微服務架構中個别服務出現異常時引起的故障蔓延。

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

先來說說這樣架構需要做的一些事兒以及存在的不足:

  • 破壞了服務無狀态特點。

    為了保證對外服務的安全性,我們需要實作對服務通路的權限控制,而開放服務的權限控制機制将會貫穿并污染整個開放服務的業務邏輯,這會帶來的最直接問題是,破壞了服務叢集中REST API無狀态的特點。 從具體開發和測試的角度來說,在工作中除了要考慮實際的業務邏輯之外,還需要額外考慮對接口通路的控制處理。

  • 無法直接複用既有接口。

    當我們需要對一個即有的叢集内通路接口,實作外部服務通路時,我們不得不通過在原有接口上增加校驗邏輯,或增加一個代理調用來實作權限控制,無法直接複用原有的接口。

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

為了解決上面這些問題,我們需要将權限控制這樣的東西從我們的服務單元中抽離出去,而最适合這些邏輯的地方就是處于對外通路最前端的地方,我們需要一個更強大一些的均衡負載器的 服務網關。

服務網關是微服務架構中一個不可或缺的部分。通過服務網關統一向外系統提供REST API的過程中,除了具備

服務路由

均衡負載

功能之外,它還具備了

權限控制

等功能。SpringCloud Netflix中的Zuul就擔任了這樣的一個角色,為微服務架構提供了前門保護的作用,同時将權限控制這些較重的非業務邏輯内容遷移到服務路由層面,使得服務叢集主體能夠具備更高的可複用性和可測試性。

zuul主要是請求的鑒權,部署在“門口”,如下圖

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

7.1.7.1.2 Zuul

Zuul是Netflix開源的微服務網關,它可以和 Eureka、Ribbon、Hystrix等元件配合使用。Zuul 的核心是一系列的過濾器,這些過濾器可以完成以下功能:

  • 身份認證與安全:識别每個資源的驗證要求,并拒絕那些與要求不符的請求。
  • 審查與監控:在邊緣位置追蹤有意義的資料和統計結果,進而帶來精确的生産視圖。
  • 動态路由:動态地将請求路由到不同的後端叢集。
  • 壓力測試:逐漸增加指向叢集的流量,以了解性能。
  • 負載配置設定:為每一種負載類型配置設定對應容量,并棄用超出限定值的請求。
  • 靜态響應處理:在邊緣位置直接建立部分響應,進而避免其轉發到内部叢集.
  • 多區域彈性:跨越AWS Region進行請求路由,旨在實作ELB (Elastic Load Balancing )使用的多樣化,以及讓系統的邊緣更貼近系統的使用者。

SpringCloud對Zuul進行了整合與增強。目前,Zuul使用的預設HTTP用戶端是 Apache HTTP Client,也可以使用RestClient或者okhttp3.OkHttpClient。如果想要使用RestClient,可以設定ribbon.restclient.enabled=true;

7.1.7.1.3 Zuul加入後的架構

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

不管是來自于用戶端(PC或移動端)的請求,還是服務内部調用。一切對服務的請求都會經過Zuul這個網關,然後再由網關來實作 鑒權、動态路由等等操作。Zuul就是我們服務的統一入口。

7.1.7.2 快速入門

7.1.7.2.1 添加依賴

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

7.1.7.2.2 配置

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

7.1.7.2.3 啟動類添加注解

添加@EnableZuulProxy 

@SpringBootApplication
@EnableZuulProxy // 開啟網關功能
public class YhZuulApplication {

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

7.1.7.2.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

7.1.7.3 面向服務的路由

在剛才的路由規則中,我們把路徑對應的服務位址寫死了!如果同一服務有多個執行個體的話,這樣做顯然就不合理了。我們應該根據服務的名稱,去Eureka注冊中心查找 服務對應的所有執行個體清單,然後進行動态路由才對!

7.1.7.3.1 添加Eureka用戶端依賴

在zuul子產品中引入依賴

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

7.1.7.3.2 添加Eureka配置,擷取服務資訊

eureka:
  client:
    registry-fetch-interval-seconds: 5 # 擷取服務清單的周期:5s
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
           

7.1.7.3.3 啟動類開啟Eureka用戶端發現功能

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

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

7.1.7.3.4 修改映射配置,通過服務名稱擷取

因為已經有了Eureka用戶端,我們可以從Eureka擷取服務的位址資訊,是以映射時無需指定IP位址,而是通過服務名稱來通路,而且Zuul已經內建了Ribbon的負載均衡功能。

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

7.1.7.4 簡化的路由配置

在剛才的配置中,我們的規則是這樣的:

  • 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/** # 這裡是映射路徑
           

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

7.1.7.5 預設的路由規則

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

  • 預設情況下,一切服務的映射路徑就是服務名本身。例如服務名為:

    service-provider

    ,則預設的映射路徑就是:

    /service-provider/**

也就是說,剛才的映射規則我們可以不配置

7.1.7.6 路由字首

配置示例:

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

我們通過

zuul.prefix=/api

來指定了路由的字首,這樣在發起請求時,路徑就要以/api開頭。

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

場景: 可以判斷,哪些請求經過網關,哪些沒有經過網關

7.1.7.7 過濾器

Zuul作為網關的其中一個重要功能,就是實作請求的鑒權。而這個動作我們往往是通過Zuul提供的過濾器來實作的。

7.1.7.7.1 ZuulFilter

在IDEA中連續按兩次SHIFT,搜尋ZuulFilter

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關
7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

 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值來定義過濾器的執行順序,數字越小優先級越高。

進入IZuulFilter

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

裡面定義了兩個最基本的方法

boolean shouldFilter();
 Object run() throws ZuulException;      

所有的過濾器,包括自定義的過濾器都要實作這兩個最基本的方法

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

7.1.7.7.2 過濾器執行生命周期

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

正常流程:

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

異常流程:

  • 整個過程中,pre或者route過濾器出現異常,都會直接進入error過濾器,在error處理完畢後,會将請求交給POST過濾器,最後傳回給使用者。
  • 如果是error過濾器自己出現異常,最終也會進入POST過濾器,将最終結果傳回給請求用戶端。
  • 如果是POST過濾器出現異常,會跳轉到error過濾器,但是與pre和route不同的是,請求不會再到達POST過濾器了。

所有内置過濾器清單:

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

使用場景

  • 請求鑒權:一般放在pre類型,如果發現沒有通路權限,直接就攔截了
  • 異常處理:一般會在error類型和post類型過濾器中結合來處理。
  • 服務調用時長統計:pre和post結合使用。

7.1.7.8 自定義過濾器

描述:自定義一個過濾器,模拟一個登入的校驗。基本邏輯:如果請求中有access-token參數,則認為請求有效,放行

建立gateway包,裡面建立一個類LoginFilter 

@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 {
        //所有的請求對象都在RequestContext中
        //所有的響應對象都在ResponseContext中
        // 擷取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;
    }
}
           

測試 

7.1 微服務-SpringCloud(二)前言7.1.5 Hystrix7.1.6 Feign7.1.7 Zuul網關

7.1.7.9 負載均衡和熔斷

Zuul中預設就已經內建了Ribbon負載均衡和Hystix熔斷機制。但是所有的逾時政策都是走的預設值,比如熔斷逾時時間隻有1S,很容易就觸發了。是以建議我們手動進行配置:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000 # 設定hystrix的逾時時間為6000ms