Spring Cloud Gateway 為 SpringBoot 應(yīng)用提供了API網(wǎng)關(guān)支持关摇,具有強大的智能路由與過濾器功能,本文將對其用法進行詳細介紹浮禾。
Gateway是在Spring生態(tài)系統(tǒng)之上構(gòu)建的API網(wǎng)關(guān)服務(wù),基于Spring 5,Spring Boot 2和 Project Reactor等技術(shù)比被。Gateway旨在提供一種簡單而有效的方式來對API進行路由,以及提供一些強大的過濾器功能泼舱, 例如:熔斷等缀、限流、重試等娇昙。
Spring Cloud Gateway 具有如下特性:
基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 進行構(gòu)建尺迂;
動態(tài)路由:能夠匹配任何請求屬性;
可以對路由指定 Predicate(斷言)和 Filter(過濾器)冒掌;
集成Hystrix的斷路器功能噪裕;
集成 Spring Cloud 服務(wù)發(fā)現(xiàn)功能;
易于編寫的 Predicate(斷言)和 Filter(過濾器)股毫;
請求限流功能膳音;
支持路徑重寫。
Route(路由):路由是構(gòu)建網(wǎng)關(guān)的基本模塊皇拣,它由ID严蓖,目標URI薄嫡,一系列的斷言和過濾器組成,如果斷言為true則匹配該路由颗胡;
Predicate(斷言):指的是Java 8 的 Function Predicate毫深。 輸入類型是Spring框架中的ServerWebExchange。 這使開發(fā)人員可以匹配HTTP請求中的所有內(nèi)容毒姨,例如請求頭或請求參數(shù)哑蔫。如果請求與斷言相匹配,則進行路由弧呐;
Filter(過濾器):指的是Spring框架中GatewayFilter的實例闸迷,使用過濾器,可以在請求被路由前后對請求進行修改俘枫。
這里我們創(chuàng)建一個api-gateway模塊來演示Gateway的常用功能腥沽。
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>Copy to clipboardErrorCopied
Gateway 提供了兩種不同的方式用于配置路由,一種是通過yml文件來配置鸠蚪,另一種是通過Java Bean來配置今阳,下面我們分別介紹下。
在application.yml中進行配置:
server:
? port: 9201
service-url:
? user-service: http://localhost:8201
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: path_route #路由的ID
? ? ? ? ? uri: ${service-url.user-service}/user/{id} #匹配后路由地址
? ? ? ? ? predicates: # 斷言茅信,路徑相匹配的進行路由
? ? ? ? ? ? - Path=/user/{id}Copy to clipboardErrorCopied
啟動eureka-server盾舌,user-service和api-gateway服務(wù),并調(diào)用該地址測試:http://localhost:9201/user/1
我們發(fā)現(xiàn)該請求被路由到了user-service的該路徑上:http://localhost:8201/user/1
添加相關(guān)配置類蘸鲸,并配置一個RouteLocator對象:
/**
* Created by macro on 2019/9/24.
*/@ConfigurationpublicclassGatewayConfig{@BeanpublicRouteLocatorcustomRouteLocator(RouteLocatorBuilderbuilder){returnbuilder.routes().route("path_route2",r->r.path("/user/getByUsername").uri("http://localhost:8201/user/getByUsername")).build();}}Copy to clipboardErrorCopied
重新啟動api-gateway服務(wù)妖谴,并調(diào)用該地址測試:http://localhost:9201/user/getByUsername?username=macro
我們發(fā)現(xiàn)該請求被路由到了user-service的該路徑上:http://localhost:8201/user/getByUsername?username=macro
Spring Cloud Gateway將路由匹配作為Spring WebFlux HandlerMapping基礎(chǔ)架構(gòu)的一部分。 Spring Cloud Gateway包括許多內(nèi)置的Route Predicate工廠酌摇。 所有這些Predicate都與HTTP請求的不同屬性匹配膝舅。 多個Route Predicate工廠可以進行組合,下面我們來介紹下一些常用的Route Predicate妙痹。
注意:Predicate中提到的配置都在application-predicate.yml文件中進行修改铸史,并用該配置啟動api-gateway服務(wù)。
在指定時間之后的請求會匹配該路由怯伊。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: after_route
? ? ? ? ? uri: ${service-url.user-service}
? ? ? ? ? predicates:
? ? ? ? ? ? - After=2019-09-24T16:30:00+08:00[Asia/Shanghai]Copy to clipboardErrorCopied
在指定時間之前的請求會匹配該路由琳轿。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: before_route
? ? ? ? ? uri: ${service-url.user-service}
? ? ? ? ? predicates:
? ? ? ? ? ? - Before=2019-09-24T16:30:00+08:00[Asia/Shanghai]Copy to clipboardErrorCopied
在指定時間區(qū)間內(nèi)的請求會匹配該路由。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: before_route
? ? ? ? ? uri: ${service-url.user-service}
? ? ? ? ? predicates:
? ? ? ? ? ? - Between=2019-09-24T16:30:00+08:00[Asia/Shanghai], 2019-09-25T16:30:00+08:00[Asia/Shanghai]Copy to clipboardErrorCopied
帶有指定Cookie的請求會匹配該路由耿芹。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: cookie_route
? ? ? ? ? uri: ${service-url.user-service}
? ? ? ? ? predicates:
? ? ? ? ? ? - Cookie=username,macroCopy to clipboardErrorCopied
使用curl工具發(fā)送帶有cookie為username=macro的請求可以匹配該路由崭篡。
curlhttp://localhost:9201/user/1 --cookie"username=macro"Copy to clipboardErrorCopied
帶有指定請求頭的請求會匹配該路由。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? - id: header_route
? ? ? ? uri: ${service-url.user-service}
? ? ? ? predicates:
? ? ? ? - Header=X-Request-Id, \d+Copy to clipboardErrorCopied
使用curl工具發(fā)送帶有請求頭為X-Request-Id:123的請求可以匹配該路由吧秕。
curlhttp://localhost:9201/user/1 -H"X-Request-Id:123"Copy to clipboardErrorCopied
帶有指定Host的請求會匹配該路由琉闪。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: host_route
? ? ? ? ? uri: ${service-url.user-service}
? ? ? ? ? predicates:
? ? ? ? ? ? - Host=**.macrozheng.comCopy to clipboardErrorCopied
使用curl工具發(fā)送帶有請求頭為Host:www.macrozheng.com的請求可以匹配該路由。
curlhttp://localhost:9201/user/1 -H"Host:www.macrozheng.com"Copy to clipboardErrorCopied
發(fā)送指定方法的請求會匹配該路由砸彬。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? - id: method_route
? ? ? ? uri: ${service-url.user-service}
? ? ? ? predicates:
? ? ? ? - Method=GETCopy to clipboardErrorCopied
使用curl工具發(fā)送GET請求可以匹配該路由颠毙。
curlhttp://localhost:9201/user/1Copy to clipboardErrorCopied
使用curl工具發(fā)送POST請求無法匹配該路由斯入。
curl-X POST http://localhost:9201/user/1Copy to clipboardErrorCopied
發(fā)送指定路徑的請求會匹配該路由。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: path_route
? ? ? ? ? uri: ${service-url.user-service}/user/{id}
? ? ? ? ? predicates:
? ? ? ? ? ? - Path=/user/{id}Copy to clipboardErrorCopied
使用curl工具發(fā)送/user/1路徑請求可以匹配該路由蛀蜜。
curlhttp://localhost:9201/user/1Copy to clipboardErrorCopied
使用curl工具發(fā)送/abc/1路徑請求無法匹配該路由刻两。
curlhttp://localhost:9201/abc/1Copy to clipboardErrorCopied
帶指定查詢參數(shù)的請求可以匹配該路由。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? - id: query_route
? ? ? ? uri: ${service-url.user-service}/user/getByUsername
? ? ? ? predicates:
? ? ? ? - Query=usernameCopy to clipboardErrorCopied
使用curl工具發(fā)送帶username=macro查詢參數(shù)的請求可以匹配該路由滴某。
curlhttp://localhost:9201/user/getByUsername?username=macroCopy to clipboardErrorCopied
使用curl工具發(fā)送帶不帶查詢參數(shù)的請求無法匹配該路由磅摹。
curlhttp://localhost:9201/user/getByUsernameCopy to clipboardErrorCopied
從指定遠程地址發(fā)起的請求可以匹配該路由。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? - id: remoteaddr_route
? ? ? ? uri: ${service-url.user-service}
? ? ? ? predicates:
? ? ? ? - RemoteAddr=192.168.1.1/24Copy to clipboardErrorCopied
使用curl工具從192.168.1.1發(fā)起請求可以匹配該路由霎奢。
curlhttp://localhost:9201/user/1Copy to clipboardErrorCopied
使用權(quán)重來路由相應(yīng)請求户誓,以下表示有80%的請求會被路由到localhost:8201,20%會被路由到localhost:8202幕侠。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? - id: weight_high
? ? ? ? uri: http://localhost:8201
? ? ? ? predicates:
? ? ? ? - Weight=group1, 8
? ? ? - id: weight_low
? ? ? ? uri: http://localhost:8202
? ? ? ? predicates:
? ? ? ? - Weight=group1, 2Copy to clipboardErrorCopied
路由過濾器可用于修改進入的HTTP請求和返回的HTTP響應(yīng)帝美,路由過濾器只能指定路由進行使用。Spring Cloud Gateway 內(nèi)置了多種路由過濾器橙依,他們都由GatewayFilter的工廠類來產(chǎn)生证舟,下面我們介紹下常用路由過濾器的用法。
AddRequestParameter GatewayFilter
給請求添加參數(shù)的過濾器窗骑。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: add_request_parameter_route
? ? ? ? ? uri: http://localhost:8201
? ? ? ? ? filters:
? ? ? ? ? ? - AddRequestParameter=username, macro
? ? ? ? ? predicates:
? ? ? ? ? ? - Method=GETCopy to clipboardErrorCopied
以上配置會對GET請求添加username=macro的請求參數(shù),通過curl工具使用以下命令進行測試漆枚。
curlhttp://localhost:9201/user/getByUsernameCopy to clipboardErrorCopied
相當(dāng)于發(fā)起該請求:
curlhttp://localhost:8201/user/getByUsername?username=macroCopy to clipboardErrorCopied
對指定數(shù)量的路徑前綴進行去除的過濾器创译。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? - id: strip_prefix_route
? ? ? ? uri: http://localhost:8201
? ? ? ? predicates:
? ? ? ? - Path=/user-service/**
? ? ? ? filters:
? ? ? ? - StripPrefix=2Copy to clipboardErrorCopied
以上配置會把以/user-service/開頭的請求的路徑去除兩位,通過curl工具使用以下命令進行測試墙基。
curlhttp://localhost:9201/user-service/a/user/1Copy to clipboardErrorCopied
相當(dāng)于發(fā)起該請求:
curlhttp://localhost:8201/user/1Copy to clipboardErrorCopied
與StripPrefix過濾器恰好相反软族,會對原有路徑進行增加操作的過濾器。
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? - id: prefix_path_route
? ? ? ? uri: http://localhost:8201
? ? ? ? predicates:
? ? ? ? - Method=GET
? ? ? ? filters:
? ? ? ? - PrefixPath=/userCopy to clipboardErrorCopied
以上配置會對所有GET請求添加/user路徑前綴残制,通過curl工具使用以下命令進行測試立砸。
curlhttp://localhost:9201/1Copy to clipboardErrorCopied
相當(dāng)于發(fā)起該請求:
curlhttp://localhost:8201/user/1Copy to clipboardErrorCopied
Hystrix 過濾器允許你將斷路器功能添加到網(wǎng)關(guān)路由中,使你的服務(wù)免受級聯(lián)故障的影響初茶,并提供服務(wù)降級處理颗祝。
要開啟斷路器功能,我們需要在pom.xml中添加Hystrix的相關(guān)依賴:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency>Copy to clipboardErrorCopied
然后添加相關(guān)服務(wù)降級的處理類:
/**
* Created by macro on 2019/9/25.
*/@RestControllerpublicclassFallbackController{@GetMapping("/fallback")publicObjectfallback(){Map<String,Object>result=newHashMap<>();result.put("data",null);result.put("message","Get request fallback!");result.put("code",500);returnresult;}}Copy to clipboardErrorCopied
在application-filter.yml中添加相關(guān)配置恼布,當(dāng)路由出錯時會轉(zhuǎn)發(fā)到服務(wù)降級處理的控制器上:
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: hystrix_route
? ? ? ? ? uri: http://localhost:8201
? ? ? ? ? predicates:
? ? ? ? ? ? - Method=GET
? ? ? ? ? filters:
? ? ? ? ? ? - name: Hystrix
? ? ? ? ? ? ? args:
? ? ? ? ? ? ? ? name: fallbackcmd
? ? ? ? ? ? ? ? fallbackUri: forward:/fallbackCopy to clipboardErrorCopied
關(guān)閉user-service螺戳,調(diào)用該地址進行測試:http://localhost:9201/user/1?,發(fā)現(xiàn)已經(jīng)返回了服務(wù)降級的處理信息折汞。
RequestRateLimiter GatewayFilter
RequestRateLimiter 過濾器可以用于限流倔幼,使用RateLimiter實現(xiàn)來確定是否允許當(dāng)前請求繼續(xù)進行,如果請求太大默認會返回HTTP 429-太多請求狀態(tài)爽待。
在pom.xml中添加相關(guān)依賴:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency>Copy to clipboardErrorCopied
添加限流策略的配置類损同,這里有兩種策略一種是根據(jù)請求參數(shù)中的username進行限流翩腐,另一種是根據(jù)訪問IP進行限流;
/**
* Created by macro on 2019/9/25.
*/@ConfigurationpublicclassRedisRateLimiterConfig{@BeanKeyResolveruserKeyResolver(){returnexchange->Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));}@BeanpublicKeyResolveripKeyResolver(){returnexchange->Mono.just(exchange.getRequest().getRemoteAddress().getHostName());}}Copy to clipboardErrorCopied
我們使用Redis來進行限流膏燃,所以需要添加Redis和RequestRateLimiter的配置栗菜,這里對所有的GET請求都進行了按IP來限流的操作;
server:
? port: 9201
spring:
? redis:
? ? host: localhost
? ? password: 123456
? ? port: 6379
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: requestratelimiter_route
? ? ? ? ? uri: http://localhost:8201
? ? ? ? ? filters:
? ? ? ? ? ? - name: RequestRateLimiter
? ? ? ? ? ? ? args:
? ? ? ? ? ? ? ? redis-rate-limiter.replenishRate: 1 #每秒允許處理的請求數(shù)量
? ? ? ? ? ? ? ? redis-rate-limiter.burstCapacity: 2 #每秒最大處理的請求數(shù)量
? ? ? ? ? ? ? ? key-resolver: "#{@ipKeyResolver}" #限流策略蹄梢,對應(yīng)策略的Bean
? ? ? ? ? predicates:
? ? ? ? ? ? - Method=GET
logging:
? level:
? ? org.springframework.cloud.gateway: debugCopy to clipboardErrorCopied
多次請求該地址:http://localhost:9201/user/1?疙筹,會返回狀態(tài)碼為429的錯誤;
對路由請求進行重試的過濾器禁炒,可以根據(jù)路由請求返回的HTTP狀態(tài)碼來確定是否進行重試而咆。
修改配置文件:
spring:
? cloud:
? ? gateway:
? ? ? routes:
? ? ? - id: retry_route
? ? ? ? uri: http://localhost:8201
? ? ? ? predicates:
? ? ? ? - Method=GET
? ? ? ? filters:
? ? ? ? - name: Retry
? ? ? ? ? args:
? ? ? ? ? ? retries: 1 #需要進行重試的次數(shù)
? ? ? ? ? ? statuses: BAD_GATEWAY #返回哪個狀態(tài)碼需要進行重試,返回狀態(tài)碼為5XX進行重試
? ? ? ? ? ? backoff:
? ? ? ? ? ? ? firstBackoff: 10ms
? ? ? ? ? ? ? maxBackoff: 50ms
? ? ? ? ? ? ? factor: 2
? ? ? ? ? ? ? basedOnPreviousValue: falseCopy to clipboardErrorCopied
當(dāng)調(diào)用返回500時會進行重試幕袱,訪問測試地址:http://localhost:9201/user/111
可以發(fā)現(xiàn)user-service控制臺報錯2次暴备,說明進行了一次重試。
2019-10-2714:08:53.435 ERROR2280---[nio-8201-exec-2]o.a.c.c.C.[.[.[/].[dispatcherServlet]:Servlet.service()forservlet[dispatcherServlet]incontext with path[]threw exception[Request processing failed;nested exception is java.lang.NullPointerException]with root causejava.lang.NullPointerException: null? ? at com.macro.cloud.controller.UserController.getUser(UserController.java:34)~[classes/:na]Copy to clipboardErrorCopied
我們上次講到使用Zuul作為網(wǎng)關(guān)結(jié)合注冊中心進行使用時们豌,默認情況下Zuul會根據(jù)注冊中心注冊的服務(wù)列表涯捻,以服務(wù)名為路徑創(chuàng)建動態(tài)路由,Gateway同樣也實現(xiàn)了該功能望迎。下面我們演示下Gateway結(jié)合注冊中心如何使用默認的動態(tài)路由和過濾器障癌。
在pom.xml中添加相關(guān)依賴:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>Copy to clipboardErrorCopied
添加application-eureka.yml配置文件:
server:
? port: 9201
spring:
? application:
? ? name: api-gateway
? cloud:
? ? gateway:
? ? ? discovery:
? ? ? ? locator:
? ? ? ? ? enabled: true #開啟從注冊中心動態(tài)創(chuàng)建路由的功能
? ? ? ? ? lower-case-service-id: true #使用小寫服務(wù)名,默認是大寫
eureka:
? client:
? ? service-url:
? ? ? defaultZone: http://localhost:8001/eureka/
logging:
? level:
? ? org.springframework.cloud.gateway: debugCopy to clipboardErrorCopied
使用application-eureka.yml配置文件啟動api-gateway服務(wù)辩尊,訪問http://localhost:9201/user-service/user/1?涛浙,可以路由到user-service的http://localhost:8201/user/1?處。
在結(jié)合注冊中心使用過濾器的時候摄欲,我們需要注意的是uri的協(xié)議為lb轿亮,這樣才能啟用Gateway的負載均衡功能。
修改application-eureka.yml文件胸墙,使用了PrefixPath過濾器我注,會為所有GET請求路徑添加/user路徑并路由;
server:
? port: 9201
spring:
? application:
? ? name: api-gateway
? cloud:
? ? gateway:
? ? ? routes:
? ? ? ? - id: prefixpath_route
? ? ? ? ? uri: lb://user-service #此處需要使用lb協(xié)議
? ? ? ? ? predicates:
? ? ? ? ? ? - Method=GET
? ? ? ? ? filters:
? ? ? ? ? ? - PrefixPath=/user
? ? ? discovery:
? ? ? ? locator:
? ? ? ? ? enabled: true
eureka:
? client:
? ? service-url:
? ? ? defaultZone: http://localhost:8001/eureka/
logging:
? level:
? ? org.springframework.cloud.gateway: debugCopy to clipboardErrorCopied
使用application-eureka.yml配置文件啟動api-gateway服務(wù)迟隅,訪問http://localhost:9201/1?但骨,可以路由到user-service的http://localhost:8201/user/1?處。
springcloud-learning
├── eureka-server -- eureka注冊中心
├── user-service -- 提供User對象CRUD接口的服務(wù)
└── api-gateway -- gateway作為網(wǎng)關(guān)的測試服務(wù)