這是SpringCloud實戰系列中第6篇文章,了解前面的文章更有助於更好理解本文內容:
①SpringCloud 實戰:引入Eureka組件,完善服務治理
②SpringCloud 實戰:引入Feign組件,發起服務間調用
③SpringCloud 實戰:使用 Ribbon 客戶端負載均衡
④SpringCloud 實戰:引入Hystrix組件,分布式系統容錯
⑤SpringCloud 實戰:引入Zuul組件,開啟網關路由
Spring Cloud Gateway 是 Spring Cloud 的一個子項目,該項目是基於 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。
Spring Cloud Gateway 作為 Spring Cloud 生態系統中的網關,目標是替代 Netflix Zuul,其不僅提供統一的路由方式,並且基於 Filter 鏈的方式提供了網關基本的功能,例如:安全,監控/指標,和限流。
特性Spring Cloud Gateway 具有如下特性:
基於Spring Framework 5, Project Reactor 和 Spring Boot 2.0 進行構建;
動態路由:能夠匹配任何請求屬性;
可以對路由指定 Predicate(斷言)和 Filter(過濾器);
集成Hystrix的斷路器功能;
集成 Spring Cloud 服務發現功能;
易於編寫的 Predicate(斷言)和 Filter(過濾器);
請求限流功能;
支持路徑重寫。
實戰 Gateway引入 gateway新建jlw-gateway項目
引入gateway依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
加入Eureka註冊中心
開啟服務註冊和發現
# gateway 服務埠
server:
port: 9000
spring:
cloud:
gateway:
discovery:
locator:
# 啟用DiscoveryClient網關集成的標誌
enabled: true
# 服務小寫匹配
lower-case-service-id: true
配置完上面,Gateway 就可以自動根據服務發現為每個服務創建router了,然後將已服務名開頭的請求路徑轉發到對應的服務。
pom中引入spring-boot-starter-actuator相關依賴,然後配置文件添加如下代碼,開啟gateway相關的端點。
management:
endpoints:
web:
exposure:
#應該包含的端點ID,全部:*
include: 'gateway'
重啟項目,訪問http://localhost:9000/actuator/gateway/routes就可以查看到配置的路由信息了
更多gateway路由信息接口展示如下圖:
Route(路由):
路由是構建網關的基本模塊,它由ID,目標URI,一系列的斷言和過濾器組成,如果斷言為true則匹配該路由;
Predicate(斷言):
指的是Java 8 的 Function Predicate。輸入類型是Spring框架中的ServerWebExchange。這使開發人員可以匹配HTTP請求中的所有內容,例如請求頭或請求參數。如果請求與斷言相匹配,則進行路由;
Filter(過濾器):
指的是Spring框架中GatewayFilter的實例,使用過濾器,可以在請求被路由前後對請求進行修改。
Gateway 提供了兩種不同的方式用於配置路由:一種是通過yml文件來配置,另一種是通過Java Bean來配置
①使用配置文件
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- StripPrefix=2
欄位含義解釋:
id
我們自定義的路由 ID,保持唯一
uri
目標服務地址,大部分場景我們是轉發到某個服務上,配置lb://eureka-provider意思是請求要轉發到註冊中心的eureka-provider服務上。
predicates
路由條件,接受一個參數,返回一個布爾結果決定是否匹配。Gateway 為我們內置了多種路由條件,包括 Path、Cookie、Param、Header、Before、After 等等,開箱即用,當然我們也可以自己實現 predicates
filters
過濾規則,當請求經過 predicate 匹配成功後,執行 filter,我們可以使用它修改請求和響應,示例表示目標服務收到的 path 將會忽略2位路徑path。
②使用Java Bean配置
配置RouteLocator對象,代碼示例如下:
@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder){
return builder.routes()
// 請求網關路徑包含 /api/ec/** 的都會被路由到eureka-client
.route("eureka-client",r->r.path("/api/ec/**")
.filters(f->f.stripPrefix(2))
.uri("lb://eureka-client"))
// 可以配置多個route
.route("eureka-client2",r->r.path("/api/ec2/**")
.filters(f->f.stripPrefix(2))
.uri("lb://eureka-client"))
.build();
}
以上配置後,通過http://localhost:9000/api/ec/sayHello 或者http://localhost:9000/api/ec2/sayHello 都會被路由到eureka-client服務。
路由規則:PredicateSpring Cloud Gateway 內置了很多 Predicates 工廠(可以通過訪問路徑/actuator/gateway/routepredicates查看),這些 Predicates 工廠通過不同的 HTTP 請求參數來匹配,多個 Predicates 工廠可以組合使用。
按照其功能可以大致分為以下幾種不同 Predicate
1.通過請求時間匹配路由在指定時間之後的請求會匹配該路由:AfterRoutePredicateFactory
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
predicates:
- After=2019-10-10T00:00:00+08:00[Asia/Shanghai]
在指定時間之前的請求會匹配該路由:BeforeRoutePredicateFactory
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
predicates:
- Before=2019-10-10T00:00:00+08:00[Asia/Shanghai]
在指定時間區間內的請求會匹配該路由:BetweenRoutePredicateFactory
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
predicates:
- Between=2019-10-01T00:00:00+08:00[Asia/Shanghai], 2019-10-10T00:00:00+08:00[Asia/Shanghai]
Cookie Route Predicate 可以接收兩個參數,一個是 Cookie name,一個是正則表達式。示例如下:
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
predicates:
- Cookie=name, jinglingwang.cn
Header Route Predicate 和 Cookie Route Predicate 一樣,也是接收 2 個參數,一個 header 中屬性名稱和一個正則表達式,示例如下:
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
predicates:
- Header=name, jinglingwang.cn
該模式接收一個參數:主機列表,示例如下:
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
predicates:
- Host=**.jinglingwang.cn,**.jinglingwang.com
可以通過是 POST、GET、PUT、DELETE 等不同的請求方式來進行路由,示例代碼如下:
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
predicates:
- Method=GET,POST
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
predicates:
- Path=/api/rc/**
該模式有兩個參數:一個必需的param和一個可選的regexp(Java正則表達式),示例如下
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
predicates:
- QUERY=name,jingling*
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
predicates:
- RemoteAddr=192.168.1.1/24 #192.168.1.1是IP 24是子網掩碼
如果請求的遠程地址是192.168.1.10,則此路由匹配。
9.通過權重來匹配路由該模式有兩個參數:group和Weight(一個int值),示例如下:
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
以上表示有80%的請求會被路由到localhost:8201,20%會被路由到localhost:8202
網關過濾器路由過濾器允許以某種方式修改傳入的HTTP請求或傳出的HTTP響應。路由過濾器只能指定路由進行使用。Spring Cloud Gateway 內置了多種路由過濾器,他們都由GatewayFilter的工廠類來產生。
添加請求Header過濾器
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
filters:
- AddRequestHeader=source, jinglingwang.cn
上面的示例會為所有匹配的請求向下遊請求時在Header中添加source=jinglingwang.cn
添加請求參數過濾器
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
filters:
- AddRequestParameter=source, jinglingwang.cn
上面的示例會把source=jinglingwang.cn添加到下遊的請求參數中
添加響應頭過濾器
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
filters:
- AddResponseHeader=source, jinglingwang.cn
上面的示例會把source=jinglingwang.cn添加到所有匹配請求的下遊響應頭中。
剔除重複的響應頭過濾器
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
DedupeResponseHeader過濾器也可接收可選策略參數,可接收參數值包括:RETAIN_FIRST (默認值,保留第一個值), RETAIN_LAST(保留最後一個值), and RETAIN_UNIQUE(保留所有唯一值,以它們第一次出現的順序保留)。
開啟Hystrix斷路器功能的過濾器
要開啟斷路器功能,我們需要在pom.xml中添加Hystrix的相關依賴:spring-cloud-starter-netflix-hystrix
然後添加相關服務降級的處理類:
@RestController
public class FallbackController{
@GetMapping("/fallback")
public Object fallback() {
Map<String,Object> result = new HashMap<>(3);
result.put("data","jinglingwang.cn");
result.put("message","Get request fallback!");
result.put("code",500);
return result;
}
}
添加配置
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- StripPrefix=2
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback
啟用resilience4j斷路器的過濾器
要啟用Spring Cloud斷路器過濾器,需要引入依賴spring-cloud-starter-circuitbreaker-reactor-resilience4j
spring:
cloud:
gateway:
routes:
- id: ribbon-client
uri: lb://ribbon-client
filters:
- CircuitBreaker=myCircuitBreaker
還可以接受一個可選的fallbackUri參數:
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- StripPrefix=2
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fallback
關閉eureka-provider服務,訪問http://localhost:9000/api/ep/hello接口,出現降級處理信息
上面的配置也可以用JAVA Bean的方式配置:
@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder){
return builder.routes()
.route("eureka-provider", r -> r.path("/api/ep/**")
.filters(f->f.stripPrefix(2).circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback")))
.uri("lb://eureka-provider"))
.build();
}
6.1 根據狀態碼使斷路器跳閘
根據返回的狀態碼,決定斷路器是否要跳閘
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- StripPrefix=2
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fallback
statusCodes:
- 500
- 'NOT_FOUND'
或者JAVA Bean配置:
@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder){
return builder.routes()
.route("eureka-provider", r -> r.path("/api/ep/**")
.filters(f->f.stripPrefix(2).circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback").addStatusCode("500")))
.uri("lb://eureka-provider"))
.build();
}
其中NOT_FOUND是HttpStatus枚舉的String表示形式
增加路徑的過濾器
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- PrefixPath=/mypath
把/mypath作為所有匹配請求路徑的前綴
去掉路徑前綴的過濾器
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- StripPrefix=2
以上配置會忽略兩位路徑path,當訪問網關API /api/ep/hello 時,向eureka-provider發起/hello請求
用於限流的過濾器
RequestRateLimiter 過濾器可以用於限流,RateLimiter實現來確定是否允許繼續當前請求。如果不是,則返回HTTP 429—太多請求(默認)的狀態。
該過濾器採用可選的keyResolver參數和速率限制器特定的參數,keyResolver是一個實現KeyResolver接口的bean。在配置中,使用SpEL按名稱引用bean。#{@myKeyResolver}是引用名為myKeyResolver的bean的SpEL表達式。
使用 Redis RateLimiter
引入redis依賴,配置好redis。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
添加配置,使用的算法是令牌桶算法。
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 #允許用戶每秒處理多少個請求,而不丟棄任何請求。這是令牌桶的填充速率
redis-rate-limiter.burstCapacity: 20 #一個用戶在一秒鐘內允許做的最大請求數。這是令牌桶可以容納的令牌數。將該值設置為零會阻止所有請求。
redis-rate-limiter.requestedTokens: 1 #一個請求花費多少令牌
通過在「replenishRate」和「burstCapacity」中設置相同的值來實現穩定的速率。通過將burstCapacity設置為高於replenishRate,可以允許臨時爆發。
配置KeyResolver
JAVA 代碼:
@Configuration
public class RedisRateLimiterConfig{
@Bean
KeyResolver userKeyResolver() {
// 根據請求參數中的phone進行限流
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("phone"));
}
@Bean
@Primary
public KeyResolver ipKeyResolver() {
// 根據訪問IP進行限流
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
}
配置RequestRateLimiter
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 #允許用戶每秒處理多少個請求,而不丟棄任何請求。這是令牌桶的填充速率
redis-rate-limiter.burstCapacity: 2 #一個用戶在一秒鐘內允許做的最大請求數。這是令牌桶可以容納的令牌數。將該值設置為零會阻止所有請求。
redis-rate-limiter.requestedTokens: 1 #一個請求花費多少令牌
key-resolver: "#{@ipKeyResolver}"
多次請求,會返回狀態碼為429的錯誤
用於重定向的過濾器
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- StripPrefix=2
- RedirectTo=302, https://jinglingwang.cn #302 重定向到https://jinglingwang.cn
效果圖如下:
用於重試的過濾器
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- StripPrefix=2
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
methods: GET,POST
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
參數解釋:
用於限制請求大小的過濾器
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
filters:
- StripPrefix=2
- name: RequestSize
args:
maxSize: 5MB
當請求大小大於允許的限制時,網關會限制請求到達下遊服務。maxSize參數後跟一個可選的數據單位,如「KB節」或「MB」,默認是「B」。
上面的配置如果超過限制會出現以下提示:
errorMessage:Request size is larger than permissible limit. Request size is 6.0 MB where permissible limit is 5.0 MB
配置http超時時間我們可以為所有路由配置Http超時(響應和連接),並且為每個特定路由配置單獨的超時時間
全局的超時時間配置:
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s
特定路由配置超時時間
spring:
cloud:
gateway:
routes:
- id: eureka-provider
uri: lb://eureka-provider
predicates:
- Path=/api/ep/**
metadata:
response-timeout: 2000
connect-timeout: 1000
或者使用JAVA Bean的方式配置:
@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder){
return builder.routes()
.route("eureka-provider", r -> r.path("/api/ep/**")
.filters(f->f.stripPrefix(2)
.requestRateLimiter(rate->rate.setKeyResolver(ipKeyResolver))
.circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback").addStatusCode("500").addStatusCode("NOT_FOUND")))
.uri("lb://eureka-provider")
.metadata(RESPONSE_TIMEOUT_ATTR, 200)
.metadata(CONNECT_TIMEOUT_ATTR, 200))
.build();
}
spring:
cloud:
gateway:
globalcors: # 全局跨域配置
cors-configurations:
'[/**]':
allowedOrigins: "https://docs.spring.io"
allowedMethods:
- GET
add-to-simple-url-handler-mapping: true
上面的配置,對於所有Get請求,允許來自jinglingwang.cn的跨域請求。
自定義 Route Predicate Factories通過一個名為AbstractRoutePredicateFactory的抽象類來進行擴展,示例代碼:
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
public MyRoutePredicateFactory(){
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config){
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
String host = exchange.getRequest().getHeaders().getFirst("Host");
return "jinglingwang.cn".equalsIgnoreCase(host);
}
@Override
public String toString() {
return String.format("host: name=%s ", config.host);
}
};
}
public static class Config {
//自定義過濾器的配置屬性
@NotEmpty
private String host;
}
}
要寫GatewayFilter,必須實現GatewayFilterFactory,可以通過擴展一個名為AbstractGatewayFilterFactory的抽象類來進行。
@Component
public class AddHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<AddHeaderGatewayFilterFactory.Config>{
public AddHeaderGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config){
return (exchange, chain) -> {
// 如果要構建「前置」過濾器,則需要在調用chain.filter之前處理
ServerHttpRequest request = exchange.getRequest().mutate()
.header("source", "jinglingwang.cn").build();
//使用構建器來處理請求
return chain.filter(exchange.mutate().request(request).build());
};
}
public static class Config {
//Put the configuration properties for your filter here
}
}
要查看所有與 Spring Cloud 網關相關的配置屬性列表,請參見附錄
版權聲明:本公眾號所有文章除特別聲明外,轉載請註明出處!