1. 為什麼是Spring Cloud Gateway
一句話,Spring Cloud已經放棄Netflix Zuul了。現在Spring Cloud中引用的還是Zuul 1.x版本,而這個版本是基於過濾器的,是阻塞IO,不支援長連線。Zuul 2.x版本跟1.x的架構大一樣,效能也有所提升。既然Spring Cloud已經不再整合Zuul 2.x了,那麼是時候瞭解一下Spring Cloud Gateway了。
可以看到,最新的Spring Cloud中的Zuul還是1.3.1版本
而且,官網中也明確說了不再維護Zuul了
(PS:順便補充幾個名詞: 服務發現(Eureka),斷路器(Hystrix),智慧路由(Zuul),客戶端負載均衡(Ribbon))
2. API閘道器
API閘道器是一個伺服器,是系統的唯一入口。從物件導向設計的角度看,它與外觀模式類似。API閘道器封裝了系統內部架構,為每個客戶端提供一個定製的API。它可能還具有其它職責,如身份驗證、監控、負載均衡、快取、請求分片與管理、靜態響應處理。API閘道器方式的核心要點是,所有的客戶端和消費端都通過統一的閘道器接入微服務,在閘道器層處理所有的非業務功能。通常,閘道器也是提供REST/HTTP的訪問API。
閘道器應當具備以下功能:
- 效能:API高可用,負載均衡,容錯機制。
- 安全:許可權身份認證、脫敏,流量清洗,後端簽名(保證全鏈路可信呼叫),黑名單(非法呼叫的限制)。
- 日誌:日誌記錄(spainid,traceid)一旦涉及分散式,全鏈路跟蹤必不可少。
- 快取:資料快取。
- 監控:記錄請求響應資料,api耗時分析,效能監控。
- 限流:流量控制,錯峰流控,可以定義多種限流規則。
- 灰度:線上灰度部署,可以減小風險。
- 路由:動態路由規則。
目前,比較流行的閘道器有:Nginx 、 Kong 、Orange等等,還有微服務閘道器Zuul 、Spring Cloud Gateway等等
對於 API Gateway,常見的選型有基於 Openresty 的 Kong、基於 Go 的 Tyk 和基於 Java 的 Zuul。這三個選型本身沒有什麼明顯的區別,主要還是看技術棧是否能滿足快速應用和二次開發。
以上說的這些功能,這些開源的閘道器元件都有,或者藉助Lua也能實現,比如:Nginx + Lua
那要Spring Cloud Gateway還有什麼用呢?
其實,我個人理解是這樣的:
- 像Nginx這類閘道器,效能肯定是沒得說,它適合做那種門戶閘道器,是作為整個全域性的閘道器,是對外的,處於最外層的;而Gateway這種,更像是業務閘道器,主要用來對應不同的客戶端提供服務的,用於聚合業務的。各個微服務獨立部署,職責單一,對外提供服務的時候需要有一個東西把業務聚合起來。
- 像Nginx這類閘道器,都是用不同的語言編寫的,不易於擴充套件;而Gateway就不同,它是用Java寫的,易於擴充套件和維護
- Gateway這類閘道器可以實現熔斷、重試等功能,這是Nginx不具備的
所以,你看到的閘道器可能是這樣的:
2.1. Netflix Zuul 1.x VS Netflix Zuul 2.x
3. Spring Cloud Gateway
3.1. 特性
- 基於Spring Framework 5、Project Reactor和Spring Boot 2.0構建
- 能夠在任意請求屬性上匹配路由
- predicates(謂詞) 和 filters(過濾器)是特定於路由的
- 整合了Hystrix斷路器
- 整合了Spring Cloud DiscoveryClient
- 易於編寫謂詞和過濾器
- 請求速率限制
- 路徑重寫
3.2. 術語
Route : 路由是閘道器的基本元件。它由ID、目標URI、謂詞集合和過濾器集合定義。如果聚合謂詞為true,則匹配路由
Predicate : This is a Java 8 Function Predicate
Filter : 是GatewayFilter的一個例項,在這裡,可以在傳送下游請求之前或之後修改請求和響應
3.3. 原理
(PS:看到這張圖是不是很熟悉,沒錯,很像SpringMVC的請求處理過程)
客戶端向Spring Cloud Gateway發出請求。如果Gateway Handler Mapping確定請求與路由匹配,則將其傳送給Gateway Web Handler。這個Handler執行通過特定於請求的過濾器鏈傳送請求。過濾器可以在傳送代理請求之前或之後執行邏輯。執行所有的“pre”過濾邏輯,然後發出代理請求,最後執行“post”過濾邏輯。
3.4. Route Predicate Factories
- Spring Cloud Gateway 包含許多內建的 Route Predicate Factories
- 所有這些predicates用於匹配HTTP請求的不同屬性
- 多個 Route Predicate Factories 可以通過邏輯與(and)結合起來一起使用
3.4.1. After Route Predicate Factory
spring: cloud: gateway: routes: - id: after_route uri: https://example.org predicates: - After=2017-01-20T17:42:47.789-07:00[America/Denver]
這個路由匹配“美國丹佛時間2017-01-20 17:42”之後的任意請求
3.4.2. Header Route Predicate Factory
spring: cloud: gateway: routes: - id: header_route uri: https://example.org predicates: - Header=X-Request-Id, \d+
這個路由匹配“請求頭包含X-Request-Id並且其值匹配正規表示式\d+”的任意請求
3.4.3. Method Route Predicate Factory
spring: cloud: gateway: routes: - id: method_route uri: https://example.org predicates: - Method=GET
這個路由匹配任意GET請求
3.4.4. Path Route Predicate Factory
spring: cloud: gateway: routes: - id: host_route uri: https://example.org predicates: - Path=/foo/{segment},/bar/{segment}
這個路由匹配這樣路徑的請求,比如:/foo/1 或 /foo/bar 或 /bar/baz
3.4.5. Query Route Predicate Factory
這個Predicate有兩個引數:一個必須的引數名和一個可選的正規表示式
spring: cloud: gateway: routes: - id: query_route uri: https://example.org predicates: - Query=baz
這個路由匹配“查詢引數中包含baz”的請求
spring: cloud: gateway: routes: - id: query_route uri: https://example.org predicates: - Query=foo, ba.
這個路由匹配“查詢引數中包含foo,並且其引數值滿足正規表示式ba.”的請求,比如:bar,baz
3.4.6. RemoteAddr Route Predicate Factory
這個路由接受一個IP(IPv4或IPv6)地址字串。例如:192.168.0.1/16,其中192.168.0.1,16是子網掩碼
spring: cloud: gateway: routes: - id: remoteaddr_route uri: https://example.org predicates: - RemoteAddr=192.168.1.1/24
這裡路由匹配遠端地址是這樣的請求,例如:192.168.1.10
3.5. GatewayFilter Factories(閘道器過濾器)
路由過濾器允許以某種方式修改傳入的HTTP請求或傳出HTTP響應。路由過濾器的作用域是特定的路由。Spring Cloud Gateway包含許多內建的閘道器過濾器工廠。
3.5.1. AddRequestHeader GatewayFilter Factory
spring: cloud: gateway: routes: - id: add_request_header_route uri: https://example.org filters: - AddRequestHeader=X-Request-Foo, Bar
對於所有匹配的請求,將會給傳給下游的請求新增一個請求頭 X-Request-Foo:Bar
3.5.2. AddRequestParameter GatewayFilter Factory
spring: cloud: gateway: routes: - id: add_request_parameter_route uri: https://example.org filters: - AddRequestParameter=foo, bar
對於所有匹配的請求,將給傳給下游的請求新增一個查詢引數 foo=bar
3.5.3. AddResponseHeader GatewayFilter Factory
spring: cloud: gateway: routes: - id: add_response_header_route uri: https://example.org filters: - AddResponseHeader=X-Response-Foo, Bar
對於所有匹配的請求,新增一個響應頭 X-Response-Foo:Bar
3.5.4. Hystrix GatewayFilter Factory
Hystrix閘道器過濾器允許你將斷路器引入閘道器路由,保護你的服務免受級聯失敗的影響,並在下游發生故障時提供預備響應。
為了啟用Hystrix閘道器過濾器,你需要引入 spring-cloud-starter-netflix-hystrix
Hystrix閘道器過濾器需要一個name引數,這個name是HystrixCommand的名字
spring: cloud: gateway: routes: - id: hystrix_route uri: https://example.org filters: - Hystrix=myCommandName
給這個過濾器包裝一個名字叫myCommandName的HystrixCommand
Hystrix閘道器過濾器也接受一個可選的引數fallbackUri,但是目前只支援forward:字首的URL。也就是說,如果這個fallback被呼叫,請求將被重定向到匹配的這個URL。
spring: cloud: gateway: routes: - id: hystrix_route uri: lb://backing-service:8088 predicates: - Path=/consumingserviceendpoint filters: - name: Hystrix args: name: fallbackcmd fallbackUri: forward:/incaseoffailureusethis - RewritePath=/consumingserviceendpoint, /backingserviceendpoint
當fallback被呼叫的時候,請求將被重定向到/incaseoffailureusethis
spring: cloud: gateway: routes: - id: ingredients uri: lb://ingredients predicates: - Path=//ingredients/** filters: - name: Hystrix args: name: fetchIngredients fallbackUri: forward:/fallback - id: ingredients-fallback uri: http://localhost:9994 predicates: - Path=/fallback
在這個例子中,專門定義了一個端點來處理/fallback請求,它在localhost:9994上。也就是說,當fallback被呼叫的時候將重定向到http://localhost:9994/fallback
3.5.5. PrefixPath GatewayFilter Factory
spring: cloud: gateway: routes: - id: prefixpath_route uri: https://example.org filters: - PrefixPath=/mypath
所有匹配的請求都將加上字首/mypath。例如,如果請求是/hello,那麼經過這個過濾器後,發出去的請求變成/mypath/hello
3.5.6. RequestRateLimiter GatewayFilter Factory
RequestRateLimiter閘道器過濾器使用一個RateLimiter實現來決定是否當前請求可以繼續往下走。如果不能,預設將返回HTTP 429 - Too Many Requests
這個過濾器接受一個可選的引數keyResolver,這個引數是一個特定的rate limiter
keyResolver是實現了KeyResolver介面的一個Bean。
在配置的時候,使用SpEL按名稱引用Bean。#{@myKeyResolver}是一個SpEL表示式,表示引用名字叫myKeyResolver的Bean。
KeyResolver.java
1 public interface KeyResolver {
2 Mono<String> resolve(ServerWebExchange exchange);
3 }
KeyResolver預設的實現是PrincipalNameKeyResolver,它從ServerWebExchange中檢索Principal,並呼叫Principal.getName()方法。
預設情況下,如果KeyResolver沒有找到一個key,那麼請求將會被denied(譯:否認,拒絕)。這種行為可以通過spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key (true or false) 和 spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code 屬性來進行調整.
Redis RateLimiter
需要引用 spring-boot-starter-data-redis-reactive
這個邏輯使用令牌桶演算法
- redis-rate-limiter.replenishRate : 允許使用者每秒處理多少個請求。這是令牌桶被填充的速率。
- redis-rate-limiter.burstCapacity : 使用者在一秒鐘內允許執行的最大請求數。這是令牌桶可以容納的令牌數量。將此值設定為0將阻塞所有請求。
一個穩定的速率是通過將replenishRate 和 burstCapacity設為相同的值來實現的。也可以將burstCapacity設得比replenishRate大,以應對臨時爆發的流量。在這種情況下,需要允許速率限制器在突發事件之間間隔一段時間,因為連續兩次突發事件將導致丟棄請求(HTTP 429 - Too Many Requests)
application.yml
spring: cloud: gateway: routes: - id: requestratelimiter_route uri: https://example.org filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20
Config.java
1 @Bean
2 KeyResolver userKeyResolver() {
3 return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
4 }
這裡定義了每個使用者的請求速率限制為10。允許使用20個請求,但是在接下來的一秒中,只有10個請求可用。
這個例子中只是簡單地從請求引數中獲取"user",在實際生產環境中不建議這麼做。
我們也可以通過實現RateLimiter介面來自定義,這個時候,在配置中我們就需要引用這個Bean,例如:#{@myRateLimiter}
spring: cloud: gateway: routes: - id: requestratelimiter_route uri: https://example.org filters: - name: RequestRateLimiter args: rate-limiter: "#{@myRateLimiter}" key-resolver: "#{@userKeyResolver}"
3.5.7. Default Filters
如果你想要新增一個過濾器並且把它應用於所有路由的話,你可以用spring.cloud.gateway.default-filters。這個屬性接受一個過濾器列表。
spring: cloud: gateway: default-filters: - AddResponseHeader=X-Response-Default-Foo, Default-Bar - PrefixPath=/httpbin
3.6. Global Filters(全域性過濾器)
GlobalFilter介面的方法簽名和GatewayFilter相同。這些是有條件地應用於所有路由的特殊過濾器。
3.6.1. GlobalFilter和GatewayFilter的順序
當一個請求過來的時候,將會新增所有的GatewayFilter例項和所有特定的GatewayFilter例項到過濾器鏈上。過濾器鏈按照org.springframework.core.Ordered介面對該鏈路上的過濾器進行排序。你可以通過實現介面中的getOrder()方法或者使用@Order註解。
Spring Cloud Gateway將過濾器執行邏輯分為“pre”和“post”階段。優先順序最高的過濾器將會是“pre”階段中的第一個過濾器,同時它也將是“post”階段中的最後一個過濾器。
ExampleConfiguration.java
1 @Bean
2 @Order(-1)
3 public GlobalFilter a() {
4 return (exchange, chain) -> {
5 log.info("first pre filter");
6 return chain.filter(exchange).then(Mono.fromRunnable(() -> {
7 log.info("third post filter");
8 }));
9 };
10 }
11
12 @Bean
13 @Order(0)
14 public GlobalFilter b() {
15 return (exchange, chain) -> {
16 log.info("second pre filter");
17 return chain.filter(exchange).then(Mono.fromRunnable(() -> {
18 log.info("second post filter");
19 }));
20 };
21 }
22
23 @Bean
24 @Order(1)
25 public GlobalFilter c() {
26 return (exchange, chain) -> {
27 log.info("third pre filter");
28 return chain.filter(exchange).then(Mono.fromRunnable(() -> {
29 log.info("first post filter");
30 }));
31 };
32 }
3.6.2. LoadBalancerClient Filter
LoadBalancerClientFilter查詢exchange屬性中查詢ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR一個URI。如果url符合lb schema(例如:lb://myservice),那麼它將使用Spring Cloud LoadBalancerClient 來解析這個名字到一個實際的主機和埠,並替換URI中相同的屬性。原始url中未被修改的部分被附加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR屬性列表中。
application.yml
spring: cloud: gateway: routes: - id: myRoute uri: lb://service predicates: - Path=/service/**
預設情況下,當一個服務例項在LoadBalancer中沒有找到時,將返回503。你可以通過配置spring.cloud.gateway.loadbalancer.use404=true來讓它返回404。
3.7. 配置
RouteDefinitionLocator.java
1 public interface RouteDefinitionLocator {
2 Flux<RouteDefinition> getRouteDefinitions();
3 }
預設情況下,PropertiesRouteDefinitionLocator通過@ConfigurationProperties機制載入屬性
下面兩段配置是等價的
spring: cloud: gateway: routes: - id: setstatus_route uri: https://example.org filters: - name: SetStatus args: status: 401 - id: setstatusshortcut_route uri: https://example.org filters: - SetStatus=401
下面用Java配置
GatewaySampleApplication.java
1 // static imports from GatewayFilters and RoutePredicates
2 @Bean
3 public RouteLocator customRouteLocator(RouteLocatorBuilder builder, ThrottleGatewayFilterFactory throttle) {
4 return builder.routes()
5 .route(r -> r.host("**.abc.org").and().path("/image/png")
6 .filters(f ->
7 f.addResponseHeader("X-TestHeader", "foobar"))
8 .uri("http://httpbin.org:80")
9 )
10 .route(r -> r.path("/image/webp")
11 .filters(f ->
12 f.addResponseHeader("X-AnotherHeader", "baz"))
13 .uri("http://httpbin.org:80")
14 )
15 .route(r -> r.order(-1)
16 .host("**.throttle.org").and().path("/get")
17 .filters(f -> f.filter(throttle.apply(1,
18 1,
19 10,
20 TimeUnit.SECONDS)))
21 .uri("http://httpbin.org:80")
22 )
23 .build();
24 }
這種風格允許自定義更多的謂詞斷言,預設是邏輯與(and)。你也可以用and() , or() , negate()
再來一個例子
1 @SpringBootApplication
2 public class DemogatewayApplication {
3 @Bean
4 public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
5 return builder.routes()
6 .route("path_route", r -> r.path("/get")
7 .uri("http://httpbin.org"))
8 .route("host_route", r -> r.host("*.myhost.org")
9 .uri("http://httpbin.org"))
10 .route("hystrix_route", r -> r.host("*.hystrix.org")
11 .filters(f -> f.hystrix(c -> c.setName("slowcmd")))
12 .uri("http://httpbin.org"))
13 .route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
14 .filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
15 .uri("http://httpbin.org"))
16 .route("limit_route", r -> r
17 .host("*.limited.org").and().path("/anything/**")
18 .filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
19 .uri("http://httpbin.org"))
20 .build();
21 }
22 }
3.8. CORS配置
spring: cloud: gateway: globalcors: corsConfigurations: '[/**]': allowedOrigins: "https://docs.spring.io" allowedMethods: - GET
上面的例子中,所有原始為docs.spring.io的GET請求均被允許跨域請求。
4. 示例
本例中又4個專案,如下圖:
4.1. cjs-eureka-server
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5 <parent>
6 <groupId>org.springframework.boot</groupId>
7 <artifactId>spring-boot-starter-parent</artifactId>
8 <version>2.1.6.RELEASE</version>
9 <relativePath/> <!-- lookup parent from repository -->
10 </parent>
11 <groupId>com.cjs.example</groupId>
12 <artifactId>cjs-eureka-server</artifactId>
13 <version>0.0.1-SNAPSHOT</version>
14 <name>cjs-eureka-server</name>
15
16 <properties>
17 <java.version>1.8</java.version>
18 <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
19 </properties>
20
21 <dependencies>
22 <dependency>
23 <groupId>org.springframework.cloud</groupId>
24 <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
25 </dependency>
26 <dependency>
27 <groupId>ch.qos.logback</groupId>
28 <artifactId>logback-classic</artifactId>
29 <version>1.2.3</version>
30 </dependency>
31 </dependencies>
32
33 <dependencyManagement>
34 <dependencies>
35 <dependency>
36 <groupId>org.springframework.cloud</groupId>
37 <artifactId>spring-cloud-dependencies</artifactId>
38 <version>${spring-cloud.version}</version>
39 <type>pom</type>
40 <scope>import</scope>
41 </dependency>
42 </dependencies>
43 </dependencyManagement>
44
45 <build>
46 <plugins>
47 <plugin>
48 <groupId>org.springframework.boot</groupId>
49 <artifactId>spring-boot-maven-plugin</artifactId>
50 </plugin>
51 </plugins>
52 </build>
53
54 </project>
application.yml
1 server:
2 port: 8761
3
4 spring:
5 application:
6 name: cjs-eureka-server
7
8 eureka:
9 client:
10 service-url:
11 defaultZone: http://10.0.29.92:8761/eureka/,http://10.0.29.232:8761/eureka/
12
13 logging:
14 file: ${spring.application.name}.log
Application.java
1 package com.cjs.example;
2
3 import org.springframework.boot.SpringApplication;
4 import org.springframework.boot.autoconfigure.SpringBootApplication;
5 import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
6
7 /**
8 * @author chengjiansheng
9 * @date 2019-06-26
10 */
11 @EnableEurekaServer
12 @SpringBootApplication
13 public class CjsEurekaServerApplication {
14
15 public static void main(String[] args) {
16 SpringApplication.run(CjsEurekaServerApplication.class, args);
17 }
18
19 }
4.2. cjs-gateway-server
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5 <parent>
6 <groupId>org.springframework.boot</groupId>
7 <artifactId>spring-boot-starter-parent</artifactId>
8 <version>2.1.6.RELEASE</version>
9 <relativePath/> <!-- lookup parent from repository -->
10 </parent>
11 <groupId>com.cjs.example</groupId>
12 <artifactId>cjs-gateway-server</artifactId>
13 <version>0.0.1-SNAPSHOT</version>
14 <name>cjs-gateway-server</name>
15
16 <properties>
17 <java.version>1.8</java.version>
18 <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
19 </properties>
20
21 <dependencies>
22 <dependency>
23 <groupId>org.springframework.cloud</groupId>
24 <artifactId>spring-cloud-starter-gateway</artifactId>
25 </dependency>
26
27 <dependency>
28 <groupId>org.springframework.boot</groupId>
29 <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
30 </dependency>
31 <dependency>
32 <groupId>ch.qos.logback</groupId>
33 <artifactId>logback-classic</artifactId>
34 <version>1.2.3</version>
35 </dependency>
36 </dependencies>
37
38 <dependencyManagement>
39 <dependencies>
40 <dependency>
41 <groupId>org.springframework.cloud</groupId>
42 <artifactId>spring-cloud-dependencies</artifactId>
43 <version>${spring-cloud.version}</version>
44 <type>pom</type>
45 <scope>import</scope>
46 </dependency>
47 </dependencies>
48 </dependencyManagement>
49
50 <build>
51 <plugins>
52 <plugin>
53 <groupId>org.springframework.boot</groupId>
54 <artifactId>spring-boot-maven-plugin</artifactId>
55 </plugin>
56 </plugins>
57 </build>
58
59 </project>
application.yml
1 server:
2 port: 8080
3 servlet:
4 context-path: /
5 spring:
6 application:
7 name: cjs-gateway-server
8 redis:
9 host: 10.0.29.187
10 password: 123456
11 port: 6379
12 cloud:
13 gateway:
14 routes:
15 - id: header_route
16 uri: http://10.0.29.187:8080/
17 predicates:
18 - Header=X-Request-Id, \d+
19 # - id: path_route
20 # uri: http://10.0.29.187:8080/
21 # predicates:
22 # - Path=/foo/{segment},/bar/{segment}
23 - id: query_route
24 uri: http://10.0.29.187:8080/
25 predicates:
26 - Query=baz
27 # default-filters:
28 # - AddResponseHeader=X-Response-Foo, Bar
29 # - AddRequestParameter=hello, world
30
31 logging:
32 file: ${spring.application.name}.log
Application.java
1 package com.cjs.example.gateway;
2
3 import org.springframework.boot.SpringApplication;
4 import org.springframework.boot.autoconfigure.SpringBootApplication;
5 import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
6 import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
7 import org.springframework.cloud.gateway.route.RouteLocator;
8 import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
9 import org.springframework.context.annotation.Bean;
10 import org.springframework.web.bind.annotation.RestController;
11 import reactor.core.publisher.Mono;
12
13 /**
14 * @author chengjiansheng
15 */
16 @RestController
17 @SpringBootApplication
18 public class CjsGatewayServerApplication {
19
20 public static void main(String[] args) {
21 SpringApplication.run(CjsGatewayServerApplication.class, args);
22 }
23
24 @Bean
25 public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
26 return builder.routes()
27 .route("path_route", r -> r.path("/price/**")
28 .filters(f -> f.addRequestHeader("hello", "world")
29 .addRequestParameter("name", "zhangsan")
30 .requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
31 .uri("http://10.0.29.232:8082/price"))
32 .route("path_route", r -> r.path("/commodity/**").uri("http://10.0.29.92:8081/commodity"))
33 .build();
34 }
35
36 @Bean
37 public RedisRateLimiter redisRateLimiter() {
38 return new RedisRateLimiter(2, 4);
39 }
40
41 @Bean
42 KeyResolver userKeyResolver() {
43 // return exchange -> Mono.just(exchange.getRequest().getHeaders().getFirst("userId"));
44 return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
45 }
46
47 }
其餘程式碼就不一一貼出來了,都在git上
https://github.com/chengjiansheng/cjs-springcloud-example
截兩張圖吧
下面看效果
效果一:正常路由
效果二:限流
1 ab -n 20 -c 10 http://10.0.29.187:8080/price/index/test01?userId=123
觀察控制檯會看到
1 2019-07-03 18:21:23.946 DEBUG 34433 --- [ioEventLoop-4-1] o.s.c.g.f.ratelimit.RedisRateLimiter : response: Response{allowed=false, headers={X-RateLimit-Remaining=0, X-RateLimit-Burst-Capacity=4, X-RateLimit-Replenish-Rate=2}, tokensRemaining=-1}
2 2019-07-03 18:21:23.946 DEBUG 34433 --- [ioEventLoop-4-1] o.s.w.s.adapter.HttpWebHandlerAdapter : [53089629] Completed 429 TOO_MANY_REQUESTS
5. 文件
https://spring.io/projects/spring-cloud-gateway
https://spring.io/guides/gs/gateway/
https://cloud.spring.io/spring-cloud-gateway/spring-cloud-gateway.html
https://github.com/spring-cloud/spring-cloud-gateway/tree/master/spring-cloud-gateway-sample
https://stripe.com/blog/rate-limiters
https://en.wikipedia.org/wiki/Token_bucket
https://blog.csdn.net/qingmengwuhen1/article/details/80742654
https://blog.mkfree.com/archives/236