文章目錄
- Spring Cloud入門系列彙總
- 摘要
- Gateway 簡介
- 相關概念
- 創建 api-gateway模塊
- Route Predicate 的使用
- After Route Predicate
- Before Route Predicate
- Between Route Predicate
- Cookie Route Predicate
- Header Route Predicate
- Host Route Predicate
- Method Route Predicate
- Path Route Predicate
- Query Route Predicate
- RemoteAddr Route Predicate
- Weight Route Predicate
- Route Filter 的使用
- AddRequestParameter GatewayFilter
- StripPrefix GatewayFilter
- PrefixPath GatewayFilter
- Hystrix GatewayFilter
- RequestRateLimiter GatewayFilter
- Retry GatewayFilter
- 結合註冊中心使用
- 使用到的模塊
- 項目源碼地址
項目使用的Spring Cloud爲Hoxton版本,Spring Boot爲2.2.2.RELEASE版本
Spring Cloud入門系列彙總
摘要
Spring Cloud Gateway 爲 SpringBoot 應用提供了API網關支持,具有強大的智能路由與過濾器功能,本文將對其用法進行詳細介紹。
Gateway 簡介
Gateway是在Spring生態系統之上構建的API網關服務,基於Spring 5,Spring Boot 2和 Project Reactor等技術。Gateway旨在提供一種簡單而有效的方式來對API進行路由,以及提供一些強大的過濾器功能, 例如:熔斷、限流、重試等。
Spring Cloud Gateway 具有如下特性:
- 基於Spring Framework 5, Project Reactor 和 Spring Boot 2.0 進行構建;
- 動態路由:能夠匹配任何請求屬性;
- 可以對路由指定 Predicate(斷言)和 Filter(過濾器);
- 集成Hystrix的斷路器功能;
- 集成 Spring Cloud 服務發現功能;
- 易於編寫的 Predicate(斷言)和 Filter(過濾器);
- 請求限流功能;
- 支持路徑重寫。
相關概念
- Route(路由):路由是構建網關的基本模塊,它由ID,目標URI,一系列的斷言和過濾器組成,如果斷言爲true則匹配該路由;
- Predicate(斷言):指的是Java 8 的 Function Predicate。 輸入類型是Spring框架中的ServerWebExchange。 這使開發人員可以匹配HTTP請求中的所有內容,例如請求頭或請求參數。如果請求與斷言相匹配,則進行路由;
- Filter(過濾器):指的是Spring框架中GatewayFilter的實例,使用過濾器,可以在請求被路由前後對請求進行修改。
創建 api-gateway模塊
這裏我們創建一個api-gateway模塊來演示Gateway的常用功能。
在pom.xml中添加相關依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
兩種不同的配置路由方式
Gateway 提供了兩種不同的方式用於配置路由,一種是通過yml文件來配置,另一種是通過Java Bean來配置,下面我們分別介紹下。
使用yml配置
在application.yml中進行配置:
server:
port: 9201
service-url:
user-service: http://localhost:8201
spring:
cloud:
gateway:
routes:
# 路由的ID
- id: path_route
# 匹配後路由地址
uri: ${service-url.user-service}/user/{id}
predicates:
# 斷言,路徑相匹配的進行路由
- Path=/user/{id}
啓動eureka-server、user-service和api-gateway服務,並調用該地址測試:http://localhost:9201/user/1
我們發現該請求被路由到了user-service的該路徑上:http://localhost:8201/user/1
使用Java Bean配置
添加相關配置類,並配置一個RouteLocator對象:
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route2", r -> r.path("/user/getByUsername")
.uri("http://localhost:8201/user/getByUsername"))
.build();
}
}
重新啓動api-gateway服務,並調用該地址測試:http://localhost:9201/user/getByUsername?username=jourwon
我們發現該請求被路由到了user-service的該路徑上:http://localhost:8201/user/getByUsername?username=jourwon
Route Predicate 的使用
Spring Cloud Gateway將路由匹配作爲Spring WebFlux HandlerMapping基礎架構的一部分。 Spring Cloud Gateway包括許多內置的Route Predicate工廠。 所有這些Predicate都與HTTP請求的不同屬性匹配。 多個Route Predicate工廠可以進行組合,下面我們來介紹下一些常用的Route Predicate。
注意:Predicate中提到的配置都在application-predicate.yml文件中進行修改,並用該配置啓動api-gateway服務。
After Route Predicate
在指定時間之後的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
- id: after_route
uri: ${service-url.user-service}
predicates:
- After=2019-12-29T18:30:00+08:00[Asia/Shanghai]
Before Route Predicate
在指定時間之前的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
- id: before_route
uri: ${service-url.user-service}
predicates:
- Before=2019-12-29T18:30:00+08:00[Asia/Shanghai]
Between Route Predicate
在指定時間區間內的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
- id: before_route
uri: ${service-url.user-service}
predicates:
- Between=2019-12-29T18:30:00+08:00[Asia/Shanghai], 2019-12-30T18:30:00+08:00[Asia/Shanghai]
Cookie Route Predicate
帶有指定Cookie的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: ${service-url.user-service}
predicates:
- Cookie=username,jourwon
使用curl工具發送帶有cookie爲username=jourwon
的請求可以匹配該路由。
curl http://localhost:9201/user/1 --cookie "username=jourwon"
Header Route Predicate
帶有指定請求頭的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
- id: header_route
uri: ${service-url.user-service}
predicates:
- Header=X-Request-Id, \d+
使用curl工具發送帶有請求頭爲X-Request-Id:123
的請求可以匹配該路由。
curl http://localhost:9201/user/1 -H "X-Request-Id:123"
Host Route Predicate
帶有指定Host的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
- id: host_route
uri: ${service-url.user-service}
predicates:
- Host=**.jourwon.com
使用curl工具發送帶有請求頭爲Host:www.jourwon.com
的請求可以匹配該路由。
curl http://localhost:9201/user/1 -H "Host:www.jourwon.com"
Method Route Predicate
發送指定方法的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
- id: method_route
uri: ${service-url.user-service}
predicates:
- Method=GET
使用curl工具發送GET請求可以匹配該路由。
curl http://localhost:9201/user/1
使用curl工具發送POST請求無法匹配該路由。
curl -X POST http://localhost:9201/user/1
Path Route Predicate
發送指定路徑的請求會匹配該路由。
spring:
cloud:
gateway:
routes:
- id: path_route
uri: ${service-url.user-service}/user/{id}
predicates:
- Path=/user/{id}
使用curl工具發送/user/1
路徑請求可以匹配該路由。
curl http://localhost:9201/user/1
使用curl工具發送/abc/1
路徑請求無法匹配該路由。
curl http://localhost:9201/abc/1
Query Route Predicate
帶指定查詢參數的請求可以匹配該路由。
spring:
cloud:
gateway:
routes:
- id: query_route
uri: ${service-url.user-service}/user/getByUsername
predicates:
- Query=username
使用curl工具發送帶username=jourwon
查詢參數的請求可以匹配該路由。
curl http://localhost:9201/user/getByUsername?username=jourwon
使用curl工具發送帶不帶查詢參數的請求無法匹配該路由。
curl http://localhost:9201/user/getByUsername
RemoteAddr Route Predicate
從指定遠程地址發起的請求可以匹配該路由。
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: ${service-url.user-service}
predicates:
- RemoteAddr=192.168.1.1/24
使用curl工具從192.168.1.1發起請求可以匹配該路由。
curl http://localhost:9201/user/1
Weight Route Predicate
使用權重來路由相應請求,以下表示有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, 2
Route Filter 的使用
路由過濾器可用於修改進入的HTTP請求和返回的HTTP響應,路由過濾器只能指定路由進行使用。Spring Cloud Gateway 內置了多種路由過濾器,他們都由GatewayFilter的工廠類來產生,下面我們介紹下常用路由過濾器的用法。
AddRequestParameter GatewayFilter
給請求添加參數的過濾器。
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://localhost:8201
filters:
- AddRequestParameter=username, jourwon
predicates:
- Method=GET
以上配置會對GET請求添加username=jourwon
的請求參數,通過curl工具使用以下命令進行測試。
curl http://localhost:9201/user/getByUsername
相當於發起該請求:
curl http://localhost:8201/user/getByUsername?username=jourwon
StripPrefix GatewayFilter
對指定數量的路徑前綴進行去除的過濾器。
spring:
cloud:
gateway:
routes:
- id: strip_prefix_route
uri: http://localhost:8201
predicates:
- Path=/user-service/**
filters:
- StripPrefix=2
以上配置會把以/user-service/
開頭的請求的路徑去除兩位,通過curl工具使用以下命令進行測試。
curl http://localhost:9201/user-service/a/user/1
相當於發起該請求:
curl http://localhost:8201/user/1
PrefixPath GatewayFilter
與StripPrefix過濾器恰好相反,會對原有路徑進行增加操作的過濾器。
spring:
cloud:
gateway:
routes:
- id: prefix_path_route
uri: http://localhost:8201
predicates:
- Method=GET
filters:
- PrefixPath=/user
以上配置會對所有GET請求添加/user
路徑前綴,通過curl工具使用以下命令進行測試。
curl http://localhost:9201/1
相當於發起該請求:
curl http://localhost:8201/user/1
Hystrix GatewayFilter
Hystrix 過濾器允許你將斷路器功能添加到網關路由中,使你的服務免受級聯故障的影響,並提供服務降級處理。
要開啓斷路器功能,我們需要在pom.xml中添加Hystrix的相關依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
然後添加相關服務降級的處理類:
@RestController
public class FallbackController {
@GetMapping("/fallback")
public Object fallback() {
Map<String,Object> result = new HashMap<>();
result.put("data",null);
result.put("message","Get request fallback!");
result.put("code",500);
return result;
}
}
在application-filter.yml中添加相關配置,當路由出錯時會轉發到服務降級處理的控制器上:
spring:
cloud:
gateway:
routes:
- id: hystrix_route
uri: ${service-url.user-service}
predicates:
- Method=GET
filters:
- name: Hystrix
args:
name: fallbackcmd
fallback-uri: forward:/fallback
關閉user-service,調用該地址進行測試:http://localhost:9201/user/1 ,發現已經返回了服務降級的處理信息。
RequestRateLimiter GatewayFilter
RequestRateLimiter 過濾器可以用於限流,使用RateLimiter實現來確定是否允許當前請求繼續進行,如果請求太大默認會返回HTTP 429-太多請求狀態。
在pom.xml中添加相關依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
添加限流策略的配置類,這裏有兩種策略一種是根據請求參數中的username進行限流,另一種是根據訪問IP進行限流;
@Configuration
public class RedisRateLimiterConfig {
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));
}
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
}
我們使用Redis來進行限流,所以需要添加Redis和RequestRateLimiter的配置,這裏對所有的GET請求都進行了按IP來限流的操作;
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
# 每秒最大處理的請求數量
redis-rate-limiter.burstCapacity: 2
# 限流策略,對應策略的Bean
key-resolver: "#{@ipKeyResolver}"
predicates:
- Method=GET
logging:
level:
org.springframework.cloud.gateway: debug
多次請求該地址:http://localhost:9201/user/1 ,會返回狀態碼爲429的錯誤;
Retry GatewayFilter
對路由請求進行重試的過濾器,可以根據路由請求返回的HTTP狀態碼來確定是否進行重試。
修改配置文件:
spring:
cloud:
gateway:
routes:
- id: retry_route
uri: http://localhost:8201
predicates:
- Method=GET
filters:
- name: Retry
args:
retries: 1 #需要進行重試的次數
statuses: BAD_GATEWAY #返回哪個狀態碼需要進行重試,返回狀態碼爲5XX進行重試
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
當調用返回500時會進行重試,訪問測試地址:http://localhost:9201/user/111
可以發現user-service控制檯報錯2次,說明進行了一次重試。
2019-12-29 19:09:30.003 ERROR [user-service,,,] 2316 --- [nio-8201-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null
結合註冊中心使用
我們上次講到Spring Cloud入門-Zuul服務網關(Hoxton版本)結合註冊中心進行使用時,默認情況下Zuul會根據註冊中心註冊的服務列表,以服務名爲路徑創建動態路由,Gateway同樣也實現了該功能。下面我們演示下Gateway結合註冊中心如何使用默認的動態路由和過濾器。
使用動態路由
在pom.xml中添加相關依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
添加application-eureka.yml配置文件:
server:
port: 9201
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
#開啓從註冊中心動態創建路由的功能
enabled: true
#使用小寫服務名,默認是大寫
lower-case-service-id: true
eureka:
client:
service-url:
defaultZone: http://localhost:8001/eureka/
logging:
level:
org.springframework.cloud.gateway: debug
使用application-eureka.yml配置文件啓動api-gateway服務,訪問http://localhost:9201/user-service/user/1,可以路由到user-service的http://localhost:8201/user/1處。
使用過濾器
在結合註冊中心使用過濾器的時候,我們需要注意的是uri的協議爲
lb
,這樣才能啓用Gateway的負載均衡功能。
修改application-eureka.yml文件,使用了PrefixPath過濾器,會爲所有GET請求路徑添加/user
路徑並路由;
server:
port: 9201
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: prefixpath_route
#此處需要使用lb協議
uri: lb://user-service
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: debug
使用application-eureka.yml配置文件啓動api-gateway服務,訪問http://localhost:9201/1 ,可以路由到user-service的http://localhost:8201/user/1處。
使用到的模塊
springcloud-learning
├── eureka-server -- eureka註冊中心
├── user-service -- 提供User對象CRUD接口的服務
└── api-gateway -- gateway作爲網關的測試服務