這是SpringCloud實戰系列中第4篇文章,瞭解前面第兩篇文章更有助於更好理解本文內容:
①SpringCloud 實戰:引入Eureka組件,完善服務治理
②SpringCloud 實戰:引入Feign組件,發起服務間調用
③SpringCloud 實戰:使用 Ribbon 客戶端負載均衡
④SpringCloud 實戰:引入Hystrix組件,分佈式系統容錯
⑤SpringCloud 實戰:引入Zuul組件,開啓網關路由html
Spring Cloud Gateway 是 Spring Cloud 的一個子項目,該項目是基於 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在爲微服務架構提供一種簡單有效的統一的 API 路由管理方式。java
Spring Cloud Gateway 做爲 Spring Cloud 生態系統中的網關,目標是替代 Netflix Zuul,其不只提供統一的路由方式,而且基於 Filter 鏈的方式提供了網關基本的功能,例如:安全,監控/指標,和限流。react
Spring Cloud Gateway 具備以下特性:web
新建jlw-gateway
項目正則表達式
引入gateway依賴redis
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
加入Eureka註冊中心算法
開啓服務註冊和發現spring
# gateway 服務端口 server: port: 9000 spring: cloud: gateway: discovery: locator: # 啓用DiscoveryClient網關集成的標誌 enabled: true # 服務小寫匹配 lower-case-service-id: true
配置完上面,Gateway 就能夠自動根據服務發現爲每一個服務建立router了,而後將已服務名開頭的請求路徑轉發到對應的服務。api
pom中引入spring-boot-starter-actuator相關依賴,而後配置文件添加以下代碼,開啓gateway相關的端點。跨域
management: endpoints: web: exposure: #應該包含的端點ID,所有:* include: 'gateway'
重啓項目,訪問http://localhost:9000/actuator/gateway/routes就能夠查看到配置的路由信息了
更多gateway路由信息接口展現以下圖:
Gateway 提供了兩種不一樣的方式用於配置路由:一種是經過yml文件來配置,另外一種是經過Java Bean來配置
①使用配置文件
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - StripPrefix=2
字段含義解釋:
lb://eureka-provider
意思是請求要轉發到註冊中心的eureka-provider
服務上。②使用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
服務。
Spring Cloud Gateway 內置了不少 Predicates 工廠(能夠經過訪問路徑/actuator/gateway/routepredicates
查看),這些 Predicates 工廠經過不一樣的 HTTP 請求參數來匹配,多個 Predicates 工廠能夠組合使用。
按照其功能能夠大體分爲如下幾種不一樣 Predicate
在指定時間以後
的請求會匹配該路由: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,則此路由匹配。
該模式有兩個參數: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超時(響應和鏈接),而且爲每一個特定路由配置單獨的超時時間
全局的超時時間配置:
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: "jinglingwang.cn" allowedMethods: - GET add-to-simple-url-handler-mapping: true
上面的配置,對於全部Get請求,容許來自jinglingwang.cn
的跨域請求。
經過一個名爲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 網關相關的配置屬性列表,請參見附錄