SpringCloud 學習總結(二)

服務容錯保護Hystrix

Hystix是Netflix開源的一個延遲和容錯庫,其中提供了基礎的熔斷功能,用於隔離訪問遠程服務、第三方庫,防止出現級聯失敗。關於Hystrix更詳細的原理,可以參考官方文檔:https://github.com/Netflix/Hystrix/

在這裏插入圖片描述

雪崩問題: 在微服務框架中,系統間都通過微服務進行調用,在微服務之間會存在這相互依賴關係。假設每個微服務運行在不同的進程中,依賴的調用只則需要使用遠程調用方式。如果其中一個網絡出現問題,或者延遲,此時,調用方式在不斷的調用,後方的依賴會出現故障。當響應過多時,就可能出現雪崩效應,造成系統的崩潰。

下圖中,我們可以看到微服務中,服務間複雜的調用關係,一個請求,可能需要調用多個微服務接口才能實現,會形成非常複雜的調用鏈路:

在這裏插入圖片描述

如圖,一次業務請求,需要調用A、P、H、I四個服務,這四個服務又可能調用其它服務。如果此時,某個服務出現異常:

在這裏插入圖片描述

例如:微服務I發生異常,請求阻塞,用戶不會得到響應,則tomcat的這個線程不會釋放,於是越來越多的用戶請求到來,越來越多的線程會阻塞:
在這裏插入圖片描述

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

Hystix解決雪崩問題:

  • 線程隔離(服務降級)
  • 服務熔斷

服務降級

服務降級: 當系統的訪問量突然特別大時,因爲資源有限,不可能提供全部服務的時候,優先保證核心服務,非核心服務不可用或者弱可用。

在Hystrix中也提供了服務降級的機制,Hystrix爲每個依賴服務調用分配一個小的線程池,如果線程池已滿調用將被立即拒絕,默認不採用排隊,加速失敗判定時間。用戶的請求將不再直接訪問服務,而是通過線程池中的空閒線程來訪問服務,如果線程池已滿,或者請求超時,則會進行降級處理。

通過服務降級,用戶的請求故障時,不會被阻塞,更不會無休止的等待或者看到系統崩潰,至少可以看到一個執行結果(例如返回友好的提示信息)。服務降級雖然會導致請求失敗,但是不會導致阻塞,而且最多會影響這個依賴服務對應的線程池中的資源,對其它服務沒有響應。

觸發Hystix服務降級的情況:

  • 線程池已滿
  • 請求超時

Hystrix將降級策略封裝在Commend中,不同的Commend根據group分割開。Commend內置了run和fallback兩個方法,內置方法。正常情況下,會先執行run方法(正常執行邏輯),若發生了故障,再執行fallback方法並返回其結果。若發生多次故障會在一定時間範圍內觸發短路,即跳過run方法,直接執行fallback方法。

在程序中測試Hystrix:

引入依賴:首先在springboot-service-consumer的pom.xml中引入Hystrix依賴:

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

修改啓動器的代碼:添加開啓熔斷註解:@EnableCircuitBreaker

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker   //開啓熔斷
public class SpringbootServiceConsumerApplication {
   
   
		、、、、
}

組合註解:@SpringCloudApplication,相當於@SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker,可以用來簡化我們的註解:
在這裏插入圖片描述

使用組合註解代替之前的3個註解:

@SpringCloudApplication //組合註解,相當於@SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker
public class SpringbootServiceConsumerApplication {
   
   

    @Bean
    @LoadBalanced  //開啓負載均衡
    public RestTemplate restTemplate() {
   
   
        return new RestTemplate();
    }

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

}

編寫降級邏輯:

改造springboot-service-consumer,當目標服務的調用出現故障,我們希望快速失敗,給用戶一個友好提示。因此需要提前編寫好失敗時的降級處理邏輯,要使用@HystixCommond來完成:

@RestController
@RequestMapping("consumer/user")
public class UserController {
   
   

    @Autowired
    private RestTemplate restTemplate;

	@GetMapping
    @HystrixCommand(fallbackMethod = "queryUserByIdFallback")  //聲明熔斷(降級邏輯)的方法
    public String queryUserById(@RequestParam("id") Long id) {
   
   
        return this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
    }
    
    public String queryUserByIdFallBack(Long id){
   
   
        return "請求繁忙,請稍後再試!";
    }
   
}

注意:熔斷的降級邏輯方法必須跟正常邏輯方法保證:相同的參數列表和返回值聲明。失敗邏輯中返回User對象沒有太大意義,一般會返回友好提示。所以我們把queryById的方法改造爲返回String,反正也是Json數據。這樣失敗邏輯中返回一個錯誤說明,會比較方便。

運行測試:

當springboot-service-provder正常提供服務時
在這裏插入圖片描述

將springboot-service-provider停機時,會發現頁面返回了降級處理信息:

在這裏插入圖片描述

指定全局熔斷的熔斷方法:

局部:(要和被熔斷的方法返回值和參數列表一致)
@HystrixCommand(fallbackMethod=“局部熔斷方法名”):聲明被熔斷的方法

全局:(返回值類型要被熔斷的方法一致,參數列表必須爲空)
@DefaultProperties(defaultFallback = “defaultFallBack”):在類上指明統一的失敗降級方法
defaultFallback:默認降級方法,不用任何參數,以匹配更多方法,但是返回值一定一致
@HystrixCommand:在方法上直接使用該註解,使用默認的剪輯方法。


優先級:局部>全局

我們剛纔把fallback寫在了某個業務方法上,如果這樣的方法很多,那豈不是要寫很多。所以我們可以把Fallback配置加在類上,實現默認fallback:

@RestController
@RequestMapping("consumer/user")
@DefaultProperties(defaultFallback = "fallBackMethod") // 指定一個類的全局熔斷方法
public class UserController {
   
   

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    @HystrixCommand // 標記該方法需要熔斷
    public String queryUserById(@RequestParam("id") Long id) {
   
   
        String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
        return user;
    }

    /**
     * 熔斷方法
     * 返回值要和被熔斷的方法的返回值一致
     * 熔斷方法不需要參數
     * @return
     */
    public String fallBackMethod(){
   
   
        return "服務器正忙,請稍後再試!";
    }
}

在這裏插入圖片描述

設置超時

在微服務中,由於網絡或者運算量等問題,超時是很常見的。Hystix中的默認超時時長爲1,在互聯網中,有些服務需要訪問第三方應用,或者處理大量的數據,使用默認的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(10000);
    } catch (InterruptedException e) {
   
   
        e.printStackTrace();
    }
    return this.userService.queryUserById(id);
}

運行查看結果:
在這裏插入圖片描述

服務熔斷

熔斷原理:熔斷器(斷路器),當某些服務反應慢或者存在大量超時問題時,服務調用方可以自己進行判斷,並進行熔斷,防止整個系統被拖垮。當情況好轉時,在進行自動重連。

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

熔斷狀態機3個狀態:

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

在程序中測試:

在consumer的調用業務中加入一段邏輯:

@GetMapping
@HystrixCommand(fallbackMethod = "queryUserByIdFallback")  //聲明熔斷的方法
public String queryUserById(@RequestParam("id") Long id) {
   
   
    if(id==1){
   
   
        throw new RuntimeException();
    }
    return this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
}

這樣如果參數是id爲1,一定失敗,其它情況都成功。(不要忘了清空service-provider中的休眠邏輯)

當我們連續訪問id爲1的請求時(超過20次,手速要快),就會觸發熔斷。斷路器會斷開,一切請求都會被降級處理。此時你訪問id爲2的請求,會發現返回的也是失敗,而且失敗時間很短,只有幾毫秒左右:

第一次訪問id=2:
在這裏插入圖片描述
連續訪問id=1:
在這裏插入圖片描述
再次訪問id=2:
在這裏插入圖片描述




不過,默認的熔斷觸發要求較高,休眠時間窗較短,爲了測試方便,我們可以通過配置修改熔斷策略:

circuitBreaker:
	requestVolumeThreshold: 10   #觸發熔斷的最小請求次數,默認20
	sleepWindowInMilliseconds: 10000   #觸發熔斷的失敗請求最小佔比,默認50%
	errorThresholdPercentage: 50    #休眠時長,默認是5000毫秒

聲明式服務調用Feign

Feign是Netfix開發的聲明式、模板化的HTTP客戶端,它可幫助我們更加快捷的調用HTTP API,其中支持自帶的註解,JAX-Rs註解,SpringMVC註解,並且還整合了Ribbon與Eureka。通過使用Feign可以把Rest的請求進行隱藏,僞裝成類似SpringMVC的Controller一樣。你不用再自己拼接url,拼接參數等等操作,一切都交給Feign去做,十分方便。

Feign項目地址:https://github.com/OpenFeign/feign

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

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

快速入門:

改造springboot-service-consumer工程:

在這裏插入圖片描述

導入依賴:

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

開啓Feign功能:在啓動類上,添加註解@EnableFeignClients開啓Feign功能

@SpringCloudApplication //組合註解,相當於@SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker
@EnableFeignClients     //啓用feign組件
public class SpringbootServiceConsumerApplication {
   
   

    /*@Bean
    @LoadBalanced  //開啓負載均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }*/

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

}

刪除RestTemplate:feign已經自動集成了Ribbon負載均衡的RestTemplate。所以,此處不需要再註冊RestTemplate。

配置Feign的客戶端:

在springboot-service-consumer工程中,添加UserClient接口:

//@FeignClient(value = "service-provider") // 標註該類是一個feign接口
@FeignClient(value = "service-provider",fallback = UserClientFallBack.class)
public interface UserClient {
   
   

    @GetMapping("user/{id}")
    User queryById(@PathVariable("id") Long id);
}
  • 首先這是一個接口,Feign會通過動態代理,幫我們生成實現類。這點跟mybatis的mapper很像
  • @FeignClient,聲明這是一個Feign客戶端,類似@Mapper註解。同時通過value屬性指定服務名稱
  • 接口中的定義方法,完全採用SpringMVC的註解,Feign會根據註解幫我們生成URL,並訪問獲取結果

改造原來的調用邏輯,調用UserClient接口:

@RestController
@RequestMapping("consumer/user")
public class UserController {
   
   

    @Autowired
    private UserClient userClient;

    @GetMapping
    public String queryUserById(@RequestParam("id") Long id){
   
   
        User user = this.userClient.queryUserById(id);
        return user.toString();
    }

}

啓動測試:

訪問接口:
在這裏插入圖片描述
正常獲取到了結果。

負載均衡與Hystrix支持:

Feign中本身已經集成了Ribbon依賴和自動配置,因此我們不需要額外引入依賴,也不需要再註冊RestTemplate對象。

同時Feign默認也有對Hystrix的集成,只不過,默認情況下是關閉的。我們需要通過下面的參數來開啓:(在springboot-service-consumer工程添加配置內容):

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

但是,Feign中的Fallback配置不像hystrix中那樣簡單了。首先,我們要定義一個類UserClientFallback,實現剛纔編寫的UserClient,作爲fallback的處理類

@Component
public class UserClientFallBack implements UserClient {
   
   
    @Override
    public User queryUserById(Long id) {
   
   
        User user = new User();
        user.setUsername("服務器正忙,請稍後再試!!!");
        return user;
    }
}

然後在UserFeignClient中,指定剛纔編寫的實現類

@FeignClient(value = "service-provider", fallback = UserClientFallback.class) // 標註該類是一個feign接口
public interface UserClient {
   
   

    @GetMapping("user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}

重啓測試:
在這裏插入圖片描述

請求壓縮:

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 # 設置觸發壓縮的大小下限

注:上面的數據類型、壓縮大小下限均爲默認值。

Zuul網關

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

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

在這裏插入圖片描述

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

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

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

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

在這裏插入圖片描述

Zuul加入後的架構

在這裏插入圖片描述

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

快速入門

新建工程:

在這裏插入圖片描述
在這裏插入圖片描述

添加Zuul依賴:
在這裏插入圖片描述
在這裏插入圖片描述
工程目錄:
在這裏插入圖片描述
pom.xml文件




<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.11.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ly</groupId>
    <artifactId>ly-zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ly-zuul</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

新建application.yml文件,編寫配置

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

編寫引導類:

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

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

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

編寫路由規則:

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

在這裏插入圖片描述

映射規則:

server:
  port: 10010
spring:
  application:
    name: ly-zuul

zuul:
  routes:
    service-provider: /service-provider/** #路由名稱,可以隨便起,習慣上是服務名
      path: /service-provider/**   # 這裏是映射路徑
      url: http://localhost:8081   # 映射路徑對應的實際url地址

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

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

啓動測試:

訪問的路徑中需要加上配置規則的映射路徑,我們訪問:http://localhost:10010/service-provider/user/2
在這裏插入圖片描述

面向服務的路由

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

對ly-zuul工程修改優化,添加Eureka客戶端依賴:

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

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

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

開啓Eureka客戶端發現功能:

@SpringBootApplication
@EnableZuulProxy  //啓用zuul網關組件
@EnableDiscoveryClient  //啓用客戶端
public class LyZuulApplication {
   
   

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

}

修改映射配置,通過服務名稱獲取:

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

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

啓動測試:

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

在這裏插入圖片描述

路由配置的四種方式: (一般使用的是第三種)

zuul:
  routes:
    service-provider: 
      path: /service-provider/** #路由名稱,可以隨便起,習慣上是服務名
      url: http://localhost:8081
    
zuul:
  routes:
    service-provider:
      path: /service-provider/**   #路由名稱,可以隨便起,習慣上是服務名
      serviceId: service-provider  #指定服務名
      
#而大多數情況下,路由名稱往往和服務名會寫成一樣的,因此Zuul就提供了一種簡化的配置語法:
zuul:
  routes:
    service-provider: /provider/** #路由名稱,可以隨便起,習慣上是服務名
 
-不用配置,默認就是服務id開頭路徑 
-Zuul就指定了默認的路由規則:默認情況下,一切服務的映射路徑就是服務名本身

測試第三種方法:使用prefix: /*配置虛擬路徑

zuul:
  routes:
    service-provider: /provider/** #路由名稱,可以隨便起,習慣上是服務名
  prefix: /api  #虛擬路徑

在這裏插入圖片描述

過濾器

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

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

過濾器執行生命週期:

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

在這裏插入圖片描述

正常流程:

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

異常流程:

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

所有內置過濾器列表:

在這裏插入圖片描述

使用場景:

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

自定義過濾器:

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

定義過濾器類:

@Component
public class LoginFilter extends ZuulFilter {
   
   

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

    /**
     * 執行順序,返回值越小,優先級越高
     * @return
     */
    @Override
    public int filterOrder() {
   
   
        return 10;
    }

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

    /**
     * 編寫過濾器的業務邏輯
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
   
   
        //初始化context上下文對象,servlet spring
        RequestContext context =RequestContext.getCurrentContext();

        //獲取request對象
        HttpServletRequest request =context.getRequest();

        //獲取參數
        String token =request.getParameter("token");
        if(StringUtils.isBlank(token)){
   
   
            //攔截,不轉發請求
            context.setSendZuulResponse(false);
            //響應狀態碼,401-身份未認證
            context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
            //設置響應的提示
            context.setResponseBody("request error!");
        }

        //返回值爲null,就代表該過濾器什麼都不做
        return null;
    }
}

運行測試:

沒有token參數時,訪問失敗:
在這裏插入圖片描述

添加token參數後:

在這裏插入圖片描述

負載均衡和熔斷:

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

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000 # 設置hystrix的超時時間爲6000ms
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章