0.學習目標
- 會配置Hystix熔斷
- 會使用Feign進行遠程調用
- 能獨立搭建Zuul網關
- 能編寫Zuul的過濾器
1.Hystrix
1.1.簡介
Hystrix,英文意思是豪豬,全身是刺,看起來就不好惹,是一種保護機制。
Hystrix也是Netflix公司的一款組件。
主頁:https://github.com/Netflix/Hystrix/
那么Hystix的作用是什么呢?具體要保護什么呢?
Hystix是Netflix開源的一個延遲和容錯庫,用于隔離訪問遠程服務、第三方庫,防止出現(xiàn)級聯(lián)失敗。
1.2.雪崩問題
微服務中,服務間調用關系錯綜復雜,一個請求,可能需要調用多個微服務接口才能實現(xiàn),會形成非常復雜的調用鏈路:
如圖,一次業(yè)務請求,需要調用A、P、H、I四個服務,這四個服務又可能調用其它服務。
如果此時,某個服務出現(xiàn)異常:
例如微服務I發(fā)生異常,請求阻塞,用戶不會得到響應,則tomcat的這個線程不會釋放,于是越來越多的用戶請求到來,越來越多的線程會阻塞:
服務器支持的線程和并發(fā)數(shù)有限,請求一直阻塞,會導致服務器資源耗盡,從而導致所有其它服務都不可用,形成雪崩效應。
這就好比,一個汽車生產線,生產不同的汽車,需要使用不同的零件,如果某個零件因為種種原因無法使用,那么就會造成整臺車無法裝配,陷入等待零件的狀態(tài),直到零件到位,才能繼續(xù)組裝。 此時如果有很多個車型都需要這個零件,那么整個工廠都將陷入等待的狀態(tài),導致所有生產都陷入癱瘓。一個零件的波及范圍不斷擴大。
Hystix解決雪崩問題的手段有兩個:
- 線程隔離
- 服務熔斷
1.3.線程隔離,服務降級
1.3.1.原理
線程隔離示意圖:
解讀:
Hystrix為每個依賴服務調用分配一個小的線程池,如果線程池已滿調用將被立即拒絕,默認不采用排隊.加速失敗判定時間。
用戶的請求將不再直接訪問服務,而是通過線程池中的空閑線程來訪問服務,如果線程池已滿,或者請求超時,則會進行降級處理,什么是服務降級?
服務降級:優(yōu)先保證核心服務,而非核心服務不可用或弱可用。
用戶的請求故障時,不會被阻塞,更不會無休止的等待或者看到系統(tǒng)崩潰,至少可以看到一個執(zhí)行結果(例如返回友好的提示信息) 。
服務降級雖然會導致請求失敗,但是不會導致阻塞,而且最多會影響這個依賴服務對應的線程池中的資源,對其它服務沒有響應。
觸發(fā)Hystix服務降級的情況:
- 線程池已滿
- 請求超時
1.3.2.動手實踐
1.3.2.1.引入依賴
首先在itcast-service-consumer的pom.xml中引入Hystrix依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
1.3.2.2.開啟熔斷
可以看到,我們類上的注解越來越多,在微服務中,經常會引入上面的三個注解,于是Spring就提供了一個組合注解:@SpringCloudApplication
因此,我們可以使用這個組合注解來代替之前的3個注解。
@SpringCloudApplication
public class ItcastServiceConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ItcastServiceConsumerApplication.class, args);
}
}
1.3.2.3.編寫降級邏輯
我們改造itcast-service-consumer,當目標服務的調用出現(xiàn)故障,我們希望快速失敗,給用戶一個友好提示。因此需要提前編寫好失敗時的降級處理邏輯,要使用HystixCommond來完成:
@Controller
@RequestMapping("consumer/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping
@ResponseBody
@HystrixCommand(fallbackMethod = "queryUserByIdFallBack")
public String queryUserById(@RequestParam("id") Long id) {
String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
return user;
}
public String queryUserByIdFallBack(Long id){
return "請求繁忙,請稍后再試!";
}
}
要注意,因為熔斷的降級邏輯方法必須跟正常邏輯方法保證:相同的參數(shù)列表和返回值聲明。失敗邏輯中返回User對象沒有太大意義,一般會返回友好提示。所以我們把queryById的方法改造為返回String,反正也是Json數(shù)據。這樣失敗邏輯中返回一個錯誤說明,會比較方便。
說明:
- @HystrixCommand(fallbackMethod = "queryByIdFallBack"):用來聲明一個降級邏輯的方法
測試:
當itcast-service-provder正常提供服務時,訪問與以前一致。但是當我們將itcast-service-provider停機時,會發(fā)現(xiàn)頁面返回了降級處理信息:
1.3.2.4.默認FallBack
我們剛才把fallback寫在了某個業(yè)務方法上,如果這樣的方法很多,那豈不是要寫很多。所以我們可以把Fallback配置加在類上,實現(xiàn)默認fallback:
@Controller
@RequestMapping("consumer/user")
@DefaultProperties(defaultFallback = "fallBackMethod") // 指定一個類的全局熔斷方法
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping
@ResponseBody
@HystrixCommand // 標記該方法需要熔斷
public String queryUserById(@RequestParam("id") Long id) {
String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
return user;
}
/**
* 熔斷方法
* 返回值要和被熔斷的方法的返回值一致
* 熔斷方法不需要參數(shù)
* @return
*/
public String fallBackMethod(){
return "請求繁忙,請稍后再試!";
}
}
- @DefaultProperties(defaultFallback = "defaultFallBack"):在類上指明統(tǒng)一的失敗降級方法
- @HystrixCommand:在方法上直接使用該注解,使用默認的剪輯方法。
- defaultFallback:默認降級方法,不用任何參數(shù),以匹配更多方法,但是返回值一定一致
1.3.2.5.設置超時
在之前的案例中,請求在超過1秒后都會返回錯誤信息,這是因為Hystix的默認超時時長為1,我們可以通過配置修改這個值:
我們可以通過hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds來設置Hystrix超時時間。該配置沒有提示。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 # 設置hystrix的超時時間為6000ms
改造服務提供者
改造服務提供者的UserController接口,隨機休眠一段時間,以觸發(fā)熔斷:
@GetMapping("{id}")
public User queryUserById(@PathVariable("id") Long id) {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.userService.queryUserById(id);
}
1.4.服務熔斷
1.4.1.熔斷原理
熔斷器,也叫斷路器,其英文單詞為:Circuit Breaker
熔斷狀態(tài)機3個狀態(tài):
- Closed:關閉狀態(tài),所有請求都正常訪問。
- Open:打開狀態(tài),所有請求都會被降級。Hystix會對請求情況計數(shù),當一定時間內失敗請求百分比達到閾值,則觸發(fā)熔斷,斷路器會完全打開。默認失敗比例的閾值是50%,請求次數(shù)最少不低于20次。
- Half Open:半開狀態(tài),open狀態(tài)不是永久的,打開后會進入休眠時間(默認是5S)。隨后斷路器會自動進入半開狀態(tài)。此時會釋放部分請求通過,若這些請求都是健康的,則會完全關閉斷路器,否則繼續(xù)保持打開,再次進行休眠計時
1.4.2.動手實踐
為了能夠精確控制請求的成功或失敗,我們在consumer的調用業(yè)務中加入一段邏輯:
@GetMapping("{id}")
@HystrixCommand
public String queryUserById(@PathVariable("id") Long id){
if(id == 1){
throw new RuntimeException("太忙了");
}
String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
return user;
}
這樣如果參數(shù)是id為1,一定失敗,其它情況都成功。(不要忘了清空service-provider中的休眠邏輯)
我們準備兩個請求窗口:
- 一個請求:http://localhost/consumer/user/1,注定失敗
- 一個請求:http://localhost/consumer/user/2,肯定成功
當我們瘋狂訪問id為1的請求時(超過20次),就會觸發(fā)熔斷。斷路器會斷開,一切請求都會被降級處理。
此時你訪問id為2的請求,會發(fā)現(xiàn)返回的也是失敗,而且失敗時間很短,只有幾毫秒左右:
不過,默認的熔斷觸發(fā)要求較高,休眠時間窗較短,為了測試方便,我們可以通過配置修改熔斷策略:
circuitBreaker.requestVolumeThreshold=10
circuitBreaker.sleepWindowInMilliseconds=10000
circuitBreaker.errorThresholdPercentage=50
解讀:
- requestVolumeThreshold:觸發(fā)熔斷的最小請求次數(shù),默認20
- errorThresholdPercentage:觸發(fā)熔斷的失敗請求最小占比,默認50%
- sleepWindowInMilliseconds:休眠時長,默認是5000毫秒
2.Feign
在前面的學習中,我們使用了Ribbon的負載均衡功能,大大簡化了遠程調用時的代碼:
String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
如果就學到這里,你可能以后需要編寫類似的大量重復代碼,格式基本相同,無非參數(shù)不一樣。有沒有更優(yōu)雅的方式,來對這些代碼再次優(yōu)化呢?
這就是我們接下來要學的Feign的功能了。
2.1.簡介
有道詞典的英文解釋:
為什么叫偽裝?
Feign可以把Rest的請求進行隱藏,偽裝成類似SpringMVC的Controller一樣。你不用再自己拼接url,拼接參數(shù)等等操作,一切都交給Feign去做。
項目主頁:https://github.com/OpenFeign/feign
2.2.快速入門
改造itcast-service-consumer工程
2.2.1.導入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.2.2.開啟Feign功能
我們在啟動類上,添加注解,開啟Feign功能
@SpringCloudApplication
@EnableFeignClients // 開啟feign客戶端
public class ItcastServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ItcastServiceConsumerApplication.class, args);
}
}
刪除RestTemplate:feign已經自動集成了Ribbon負載均衡的RestTemplate。所以,此處不需要再注冊RestTemplate。
2.2.3.Feign的客戶端
在itcast-service-consumer工程中,添加UserClient接口:
內容:
@FeignClient(value = "service-provider") // 標注該類是一個feign接口
public interface UserClient {
@GetMapping("user/{id}")
User queryById(@PathVariable("id") Long id);
}
- 首先這是一個接口,F(xiàn)eign會通過動態(tài)代理,幫我們生成實現(xiàn)類。這點跟mybatis的mapper很像
-
@FeignClient,聲明這是一個Feign客戶端,類似@Mapper注解。同時通過value屬性指定服務名稱 - 接口中的定義方法,完全采用SpringMVC的注解,F(xiàn)eign會根據注解幫我們生成URL,并訪問獲取結果
改造原來的調用邏輯,調用UserClient接口:
@Controller
@RequestMapping("consumer/user")
public class UserController {
@Autowired
private UserClient userClient;
@GetMapping
@ResponseBody
public User queryUserById(@RequestParam("id") Long id){
User user = this.userClient.queryUserById(id);
return user;
}
}
2.2.4.啟動測試
訪問接口:
正常獲取到了結果。
2.3.負載均衡
Feign中本身已經集成了Ribbon依賴和自動配置:
因此我們不需要額外引入依賴,也不需要再注冊RestTemplate對象。
2.4.Hystrix支持
Feign默認也有對Hystrix的集成:
只不過,默認情況下是關閉的。我們需要通過下面的參數(shù)來開啟:(在itcast-service-consumer工程添加配置內容)
feign:
hystrix:
enabled: true # 開啟Feign的熔斷功能
但是,F(xiàn)eign中的Fallback配置不像hystrix中那樣簡單了。
1)首先,我們要定義一個類UserClientFallback,實現(xiàn)剛才編寫的UserClient,作為fallback的處理類
@Component
public class UserClientFallback implements UserClient {
@Override
public User queryById(Long id) {
User user = new User();
user.setUserName("服務器繁忙,請稍后再試!");
return user;
}
}
2)然后在UserFeignClient中,指定剛才編寫的實現(xiàn)類
@FeignClient(value = "service-provider", fallback = UserClientFallback.class) // 標注該類是一個feign接口
public interface UserClient {
@GetMapping("user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
3)重啟測試:
2.5.請求壓縮(了解)
Spring Cloud Feign 支持對請求和響應進行GZIP壓縮,以減少通信過程中的性能損耗。通過下面的參數(shù)即可開啟請求與響應的壓縮功能:
feign:
compression:
request:
enabled: true # 開啟請求壓縮
response:
enabled: true # 開啟響應壓縮
同時,我們也可以對請求的數(shù)據類型,以及觸發(fā)壓縮的大小下限進行設置:
feign:
compression:
request:
enabled: true # 開啟請求壓縮
mime-types: text/html,application/xml,application/json # 設置壓縮的數(shù)據類型
min-request-size: 2048 # 設置觸發(fā)壓縮的大小下限
注:上面的數(shù)據類型、壓縮大小下限均為默認值。
2.6.日志級別(了解)
前面講過,通過logging.level.xx=debug來設置日志級別。然而這個對Fegin客戶端而言不會產生效果。因為@FeignClient注解修改的客戶端在被代理時,都會創(chuàng)建一個新的Fegin.Logger實例。我們需要額外指定這個日志的級別才可以。
1)設置com.leyou包下的日志級別都為debug
logging:
level:
cn.itcast: debug
2)編寫配置類,定義日志級別
內容:
@Configuration
public class FeignLogConfiguration {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
這里指定的Level級別是FULL,F(xiàn)eign支持4種級別:
- NONE:不記錄任何日志信息,這是默認值。
- BASIC:僅記錄請求的方法,URL以及響應狀態(tài)碼和執(zhí)行時間
- HEADERS:在BASIC的基礎上,額外記錄了請求和響應的頭信息
- FULL:記錄所有請求和響應的明細,包括頭信息、請求體、元數(shù)據。
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)重啟項目,即可看到每次訪問的日志:
3.Zuul網關
通過前面的學習,使用Spring Cloud實現(xiàn)微服務的架構基本成型,大致是這樣的:
我們使用Spring Cloud Netflix中的Eureka實現(xiàn)了服務注冊中心以及服務注冊與發(fā)現(xiàn);而服務間通過Ribbon或Feign實現(xiàn)服務的消費以及均衡負載。為了使得服務集群更為健壯,使用Hystrix的融斷機制來避免在微服務架構中個別服務出現(xiàn)異常時引起的故障蔓延。
在該架構中,我們的服務集群包含:內部服務Service A和Service B,他們都會注冊與訂閱服務至Eureka Server,而Open Service是一個對外的服務,通過均衡負載公開至服務調用方。我們把焦點聚集在對外服務這塊,直接暴露我們的服務地址,這樣的實現(xiàn)是否合理,或者是否有更好的實現(xiàn)方式呢?
先來說說這樣架構需要做的一些事兒以及存在的不足:
先來說說這樣架構需要做的一些事兒以及存在的不足:
-
破壞了服務無狀態(tài)特點。
為了保證對外服務的安全性,我們需要實現(xiàn)對服務訪問的權限控制,而開放服務的權限控制機制將會貫穿并污染整個開放服務的業(yè)務邏輯,這會帶來的最直接問題是,破壞了服務集群中REST API無狀態(tài)的特點。
從具體開發(fā)和測試的角度來說,在工作中除了要考慮實際的業(yè)務邏輯之外,還需要額外考慮對接口訪問的控制處理。 -
無法直接復用既有接口。
當我們需要對一個即有的集群內訪問接口,實現(xiàn)外部服務訪問時,我們不得不通過在原有接口上增加校驗邏輯,或增加一個代理調用來實現(xiàn)權限控制,無法直接復用原有的接口。
面對類似上面的問題,我們要如何解決呢?答案是:服務網關!
為了解決上面這些問題,我們需要將權限控制這樣的東西從我們的服務單元中抽離出去,而最適合這些邏輯的地方就是處于對外訪問最前端的地方,我們需要一個更強大一些的均衡負載器的 服務網關。
服務網關是微服務架構中一個不可或缺的部分。通過服務網關統(tǒng)一向外系統(tǒng)提供REST API的過程中,除了具備服務路由、均衡負載功能之外,它還具備了權限控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,為微服務架構提供了前門保護的作用,同時將權限控制這些較重的非業(yè)務邏輯內容遷移到服務路由層面,使得服務集群主體能夠具備更高的可復用性和可測試性。
3.1.簡介
官網:https://github.com/Netflix/zuul
Zuul:維基百科
電影《捉鬼敢死隊》中的怪獸,Zuul,在紐約引發(fā)了巨大騷亂。
事實上,在微服務架構中,Zuul就是守門的大Boss!一夫當關,萬夫莫開!
3.2.Zuul加入后的架構
不管是來自于客戶端(PC或移動端)的請求,還是服務內部調用。一切對服務的請求都會經過Zuul這個網關,然后再由網關來實現(xiàn) 鑒權、動態(tài)路由等等操作。Zuul就是我們服務的統(tǒng)一入口。
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.編寫路由規(guī)則
我們需要用Zuul來代理service-provider服務,先看一下控制面板中的服務狀態(tài):
- ip為:127.0.0.1
- 端口為:8081
映射規(guī)則:
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 規(guī)則的一切請求,都代理到 url參數(shù)指定的地址
本例中,我們將 /service-provider/**開頭的請求,代理到http://127.0.0.1:8081
3.3.5.啟動測試
訪問的路徑中需要加上配置規(guī)則的映射路徑,我們訪問:http://127.0.0.1:10010/service-provider/user/1
3.4.面向服務的路由
在剛才的路由規(guī)則中,我們把路徑對應的服務地址寫死了!如果同一服務有多個實例的話,這樣做顯然就不合理了。我們應該根據服務的名稱,去Eureka注冊中心查找 服務對應的所有實例列表,然后進行動態(tài)路由才對!
對itcast-zuul工程修改優(yōu)化:
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客戶端發(fā)現(xiàn)功能
@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.簡化的路由配置
在剛才的配置中,我們的規(guī)則是這樣的:
-
zuul.routes.<route>.path=/xxx/**: 來指定映射路徑。<route>是自定義的路由名 -
zuul.routes.<route>.serviceId=service-provider:來指定服務名。
而大多數(shù)情況下,我們的<route>路由名稱往往和服務名會寫成一樣的。因此Zuul就提供了一種簡化的配置語法:zuul.routes.<serviceId>=<path>
比方說上面我們關于service-provider的配置可以簡化為一條:
zuul:
routes:
service-provider: /service-provider/** # 這里是映射路徑
省去了對服務名稱的配置。
3.6.默認的路由規(guī)則
在使用Zuul的過程中,上面講述的規(guī)則已經大大的簡化了配置項。但是當服務較多時,配置也是比較繁瑣的。因此Zuul就指定了默認的路由規(guī)則:
- 默認情況下,一切服務的映射路徑就是服務名本身。例如服務名為:
service-provider,則默認的映射路徑就 是:/service-provider/**
也就是說,剛才的映射規(guī)則我們完全不配置也是OK的,不信就試試看。
3.7.路由前綴
配置示例:
zuul:
routes:
service-provider: /service-provider/**
service-consumer: /service-consumer/**
prefix: /api # 添加路由前綴
我們通過zuul.prefix=/api來指定了路由的前綴,這樣在發(fā)起請求時,路徑就要以/api開頭。
3.8.過濾器
Zuul作為網關的其中一個重要功能,就是實現(xiàn)請求的鑒權。而這個動作我們往往是通過Zuul提供的過濾器來實現(xiàn)的。
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值,判斷該過濾器是否需要執(zhí)行。返回true執(zhí)行,返回false不執(zhí)行。 -
run:過濾器的具體業(yè)務邏輯。 -
filterType:返回字符串,代表過濾器的類型。包含以下4種:-
pre:請求在被路由之前執(zhí)行 -
route:在路由請求時調用 -
post:在route和errror過濾器之后調用 -
error:處理請求時發(fā)生錯誤調用
-
-
filterOrder:通過返回的int值來定義過濾器的執(zhí)行順序,數(shù)字越小優(yōu)先級越高。
3.8.2.過濾器執(zhí)行生命周期
這張是Zuul官網提供的請求生命周期圖,清晰的表現(xiàn)了一個請求在各個過濾器的執(zhí)行順序。
正常流程:
- 請求到達首先會經過pre類型過濾器,而后到達route類型,進行路由,請求就到達真正的服務提供者,執(zhí)行請求,返回結果后,會到達post過濾器。而后返回響應。
異常流程:
- 整個過程中,pre或者route過濾器出現(xiàn)異常,都會直接進入error過濾器,在error處理完畢后,會將請求交給POST過濾器,最后返回給用戶。
- 如果是error過濾器自己出現(xiàn)異常,最終也會進入POST過濾器,將最終結果返回給請求客戶端。
- 如果是POST過濾器出現(xiàn)異常,會跳轉到error過濾器,但是與pre和route不同的是,請求不會再到達POST過濾器了。
所有內置過濾器列表:
3.8.3.使用場景
場景非常多:
- 請求鑒權:一般放在pre類型,如果發(fā)現(xiàn)沒有訪問權限,直接就攔截了
- 異常處理:一般會在error類型和post類型過濾器中結合來處理。
- 服務調用時長統(tǒng)計:pre和post結合使用。
3.9.自定義過濾器
接下來我們來自定義一個過濾器,模擬一個登錄的校驗?;具壿嫞喝绻埱笾杏衋ccess-token參數(shù),則認為請求有效,放行。
3.9.1.定義過濾器類
內容:
@Component
public class LoginFilter extends ZuulFilter {
/**
* 過濾器類型,前置過濾器
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 過濾器的執(zhí)行順序
* @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);
// 設置響應狀態(tài)碼,401
context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
// 設置響應信息
context.setResponseBody("{\"status\":\"401\", \"text\":\"request error!\"}");
}
// 校驗通過,把登陸信息放入上下文信息,繼續(xù)向后執(zhí)行
context.set("token", token);
return null;
}
}
3.9.2.測試
沒有token參數(shù)時,訪問失?。?/p>
添加token參數(shù)后:
3.10.負載均衡和熔斷
Zuul中默認就已經集成了Ribbon負載均衡和Hystix熔斷機制。但是所有的超時策略都是走的默認值,比如熔斷超時時間只有1S,很容易就觸發(fā)了。因此建議我們手動進行配置:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 # 設置hystrix的超時時間為6000ms