Spring Cloud Gateway:新一代API網關服務

SpringBoot實戰電商項目mall(20k+star)地址:github.com/macrozheng/…java

摘要

Spring Cloud Gateway 爲 SpringBoot 應用提供了API網關支持,具備強大的智能路由與過濾器功能,本文將對其用法進行詳細介紹。react

Gateway 簡介

Gateway是在Spring生態系統之上構建的API網關服務,基於Spring 5,Spring Boot 2和 Project Reactor等技術。Gateway旨在提供一種簡單而有效的方式來對API進行路由,以及提供一些強大的過濾器功能, 例如:熔斷、限流、重試等。git

Spring Cloud Gateway 具備以下特性:github

  • 基於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的經常使用功能。redis

在pom.xml中添加相關依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
複製代碼

兩種不一樣的配置路由方式

Gateway 提供了兩種不一樣的方式用於配置路由,一種是經過yml文件來配置,另外一種是經過Java Bean來配置,下面咱們分別介紹下。spring

使用yml配置

  • 在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}
複製代碼

使用Java Bean配置

  • 添加相關配置類,並配置一個RouteLocator對象:
/** * Created by macro on 2019/9/24. */
@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("path_route2", r -> r.path("/user/getByUsername")
                        .uri("http://localhost:8201/user/getByUsername"))
                .build();
    }
}
複製代碼

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-09-24T16:30:00+08:00[Asia/Shanghai]
複製代碼

Before Route Predicate

在指定時間以前的請求會匹配該路由。

spring:
 cloud:
 gateway:
 routes:
 - id: before_route
 uri: ${service-url.user-service}
 predicates:
 - Before=2019-09-24T16:30:00+08:00[Asia/Shanghai]
複製代碼

Between Route Predicate

在指定時間區間內的請求會匹配該路由。

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]
複製代碼

Cookie Route Predicate

帶有指定Cookie的請求會匹配該路由。

spring:
 cloud:
 gateway:
 routes:
 - id: cookie_route
 uri: ${service-url.user-service}
 predicates:
 - Cookie=username,macro
複製代碼

使用curl工具發送帶有cookie爲username=macro的請求能夠匹配該路由。

curl http://localhost:9201/user/1 --cookie "username=macro"
複製代碼

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=**.macrozheng.com
複製代碼

使用curl工具發送帶有請求頭爲Host:www.macrozheng.com的請求能夠匹配該路由。

curl http://localhost:9201/user/1 -H "Host:www.macrozheng.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=macro查詢參數的請求能夠匹配該路由。

curl http://localhost:9201/user/getByUsername?username=macro
複製代碼

使用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, macro
 predicates:
 - Method=GET
複製代碼

以上配置會對GET請求添加username=macro的請求參數,經過curl工具使用如下命令進行測試。

curl http://localhost:9201/user/getByUsername
複製代碼

至關於發起該請求:

curl http://localhost:8201/user/getByUsername?username=macro
複製代碼

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>
複製代碼
  • 而後添加相關服務降級的處理類:
/** * Created by macro on 2019/9/25. */
@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: http://localhost:8201
 predicates:
 - Method=GET
 filters:
 - name: Hystrix
 args:
 name: fallbackcmd
 fallbackUri: forward:/fallback
複製代碼

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進行限流;
/** * Created by macro on 2019/9/25. */
@Configuration
public class RedisRateLimiterConfig {
    @Bean
    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來限流的操做;
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 #每秒容許處理的請求數量
                redis-rate-limiter.burstCapacity: 2 #每秒最大處理的請求數量
 key-resolver: "#{@ipKeyResolver}" #限流策略,對應策略的Bean
 predicates:
 - Method=GET
logging:
 level:
    org.springframework.cloud.gateway: debug
複製代碼

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-10-27 14:08:53.435 ERROR 2280 --- [nio-8201-exec-2] 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
	at com.macro.cloud.controller.UserController.getUser(UserController.java:34) ~[classes/:na]
複製代碼

結合註冊中心使用

咱們上次講到使用Zuul做爲網關結合註冊中心進行使用時,默認狀況下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
 uri: lb://user-service #此處須要使用lb協議
 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做爲網關的測試服務
複製代碼

項目源碼地址

github.com/macrozheng/…

公衆號

mall項目全套學習教程連載中,關注公衆號第一時間獲取。

公衆號圖片
相關文章
相關標籤/搜索