(44)java Spring Cloud企業快速開發架構之Gateway實戰案例限流、熔斷、跨
SpringCloud Gateway 作為新一代閘道器,在效能上有很大提升,並且附加了諸如限流等實用的功能。本節主要講解 Gateway 的一些實用功能的例項。 需要框架原始碼的朋友可以看我個人簡介聯絡我。 推薦鴻鵠分散式雲架構
限流實戰
開發高併發系統時有三把利器用來保護系統:快取、降級和限流。API 閘道器作為所有請求的入口,請求量大,我們可以通過對併發訪問的請求進行限速來保護系統的可用性。
目前限流提供了基於 Redis 的實現,我們需要增加對應的依賴,程式碼如下所示。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency>
我們可以通過 KeyResolver 來指定限流的 Key,比如我們需要根據使用者來做限流,或是根據 IP 來做限流等。
IP 限流
IP 限流的 Key 指定具體程式碼如下所示。
@Beanpublic KeyResolver ipKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); }public static String getIpAddr(ServerHttpRequest request) { HttpHeaders headers = request.getHeaders(); List<String> ips = headers.get("X-Forwarded-For"); String ip = "192.168.1.1"; if (ips != null && ips.size() > 0) { ip = ips.get(0); } return ip; }
使用者限流
根據使用者來做限流只需要獲取當前請求的使用者 ID 或者使用者名稱,程式碼如下所示。
@BeanKeyResolver userKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId")); }
介面限流
獲取請求地址的 uri 作為限流 Key,程式碼如下所示。
@BeanKeyResolver apiKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getPath().value()); }
然後配置限流的過濾器資訊:
server: port: 8084spring: redis: host: 127.0.0.1 port: 6379 cloud: gateway: routes: - id: fsh-house uri: lb://fsh-house predicates: - Path=/house/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 key-resolver: "#{@ipKeyResolver}"
filter 名稱必須是 RequestRateLimiter。
redis-rate-limiter.replenishRate:允許使用者每秒處理多少個請求。
redis-rate-limiter.burstCapacity:令牌桶的容量,允許在 1s 內完成的最大請求數。
key-resolver:使用 SpEL 按名稱引用 bean。
可以訪問介面進行測試,這時候 Redis 中會有對應的資料:
127.0.0.1:6379> keys *1) "request_rate_limiter.{localhost}.timestamp"2) "request_rate_limiter.{localhost}.tokens"
大括號中就是我們的限流 Key,這裡是 IP,本地的就是 localhost。
timestamp:儲存的是當前時間的秒數,也就是 System.currentTimeMillis()/1000 或者 Instant.now().getEpochSecond()。
tokens:儲存的是當前這秒鐘對應的可用令牌數量。
熔斷回退實戰
在 Spring Cloud Gateway 中使用 Hystrix 進行回退需要增加 Hystrix 的依賴,程式碼如下所示。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency>
內建了 HystrixGatewayFilterFactory 來實現路由級別的熔斷,只需要配置即可實現熔斷回退功能。配置方式如下所示。
- id: user-serviceuri: lb://user-servicepredicates: - Path=/user-service/**filters: - name: Hystrixargs: name: fallbackcmdfallbackUri: forward:/fallback
上面配置了一個 Hystrix 過濾器,該過濾器會使用 Hystrix 熔斷與回退,原理是將請求包裝成 RouteHystrixCommand 執行,RouteHystrixCommand 繼承於 com.netflix.hystrix.HystrixObservableCommand。
fallbackUri 是發生熔斷時回退的 URI 地址,目前只支援 forward 模式的 URI。如果服務被降級,該請求會被轉發到該 URI 中。
在閘道器中建立一個回退的介面,用於熔斷時處理返回給呼叫方的資訊,程式碼如下所示。
@RestControllerpublic class FallbackController { @GetMapping("/fallback") public String fallback() { return "fallback"; } }
跨域實戰
在 Spring Cloud Gateway 中配置跨域有兩種方式,分別是程式碼配置方式和配置檔案方式。
程式碼配置方式配置跨域,具體程式碼如下所示。
@Configurationpublic class CorsConfig { @Bean public WebFilter corsFilter() { return (ServerWebExchange ctx, WebFilterChain chain) -> { ServerHttpRequest request = ctx.getRequest(); if (CorsUtils.isCorsRequest(request)) { HttpHeaders requestHeaders = request.getHeaders(); ServerHttpResponse response = ctx.getResponse(); HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod(); HttpHeaders headers = response.getHeaders(); headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin()); headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders()); if (requestMethod != null) { headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name()); } headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*"); if (request.getMethod() == HttpMethod.OPTIONS) { response.setStatusCode(HttpStatus.OK); return Mono.empty(); } } return chain.filter(ctx); }; } }
配置檔案方式配置跨域:
spring: cloud: gateway: globalcors: corsConfigurations: '[/**]': allowedOrigins: "*" exposedHeaders: - content-type allowedHeaders: - content-type allowCredentials: true allowedMethods: - GET - OPTIONS - PUT - DELETE - POST
統一異常處理
Spring Cloud Gateway 中的全域性異常處理不能直接使用 @ControllerAdvice,可以通過跟蹤異常資訊的丟擲,找到對應的原始碼,自定義一些處理邏輯來匹配業務的需求。
閘道器是給介面做代理轉發的,後端對應的是 REST API,返回資料格式是 JSON。如果不做處理,當發生異常時,Gateway 預設給出的錯誤資訊是頁面,不方便前端進行異常處理。
所以我們需要對異常資訊進行處理,並返回 JSON 格式的資料給客戶端。下面先看實現的程式碼,後面再跟大家講一下需要注意的地方。
自定義異常處理邏輯,程式碼如下所示。
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler { public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) { super(errorAttributes, resourceProperties, errorProperties, applicationContext); } /** * 獲取異常屬性 */ @Override protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { int code = 500; Throwable error = super.getError(request); if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) { code = 404; } return response(code, this.buildMessage(request, error)); } /** * 指定響應處理方法為JSON處理的方法 * * @param errorAttributes */ @Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); } /** * 根據code獲取對應的HttpStatus * * @param errorAttributes */ @Override protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) { int statusCode = (int) errorAttributes.get("code"); return HttpStatus.valueOf(statusCode); } /** * 構建異常資訊 * * @param request * @param ex * @return */ private String buildMessage(ServerRequest request, Throwable ex) { StringBuilder message = new StringBuilder("Failed to handle request ["); message.append(request.methodName()); message.append(" "); message.append(request.uri()); message.append("]"); if (ex != null) { message.append(": "); message.append(ex.getMessage()); } return message.toString(); } /** * 構建返回的JSON資料格式 * * @param status 狀態碼 * @param errorMessage 異常資訊 * @return */ public static Map<String, Object> response(int status, String errorMessage) { Map<String, Object> map = new HashMap<>(); map.put("code", status); map.put("message", errorMessage); map.put("data", null); return map; } }
覆蓋預設的配置,程式碼如下所示。
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })public class ErrorHandlerConfiguration { private final ServerProperties serverProperties; private final ApplicationContext applicationContext; private final ResourceProperties resourceProperties; private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public ErrorHandlerConfiguration(ServerProperties serverProperties, ResourceProperties resourceProperties, ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) { this.serverProperties = serverProperties; this.applicationContext = applicationContext; this.resourceProperties = resourceProperties; this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) { JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes, this.resourceProperties,this.serverProperties.getError(), this.applicationContext); exceptionHandler.setViewResolvers(this.viewResolvers); exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters()); exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders()); return exceptionHandler; } }
異常時如何返回 JSON 而不是 HTML?
在 org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWeb-Exception-Handler 中的 getRoutingFunction() 方法就是控制返回格式的,原始碼如下所示。
@Overrideprotected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(acceptsTextHtml(), this::renderErrorView).andRoute(RequestPredicates.all(), this::renderErrorResponse); }
這裡優先是用 HTML 來顯示的,如果想用 JSON 顯示改動就可以了,具體程式碼如下所示。
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(),this::renderErrorResponse); }
getHttpStatus 需要重寫
原始的方法是通過 status 來獲取對應的 HttpStatus 的,具體程式碼如下所示。
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) { int statusCode = (int) errorAttributes.get("status"); return HttpStatus.valueOf(statusCode); }
如果我們定義的格式中沒有 status 欄位的話,就會報錯,因為找不到對應的響應碼。要麼返回資料格式中增加 status 子段,要麼重寫,在筆者的操作中返回的是 code,所以要重寫,程式碼如下所示。
@Overrideprotected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) { int statusCode = (int) errorAttributes.get("code"); return HttpStatus.valueOf(statusCode); }
重試機制
RetryGatewayFilter 是 Spring Cloud Gateway 對請求重試提供的一個 GatewayFilter Factory。配置方式如下所示。
spring: cloud: gateway: routes: - id: zuul-encrypt-service uri: lb://zuul-encrypt-service predicates: - Path=/data/** filters: - name: Retry args: retries: 3 series: SERVER_ERROR
上述程式碼中具體引數含義如下所示。
retries:重試次數,預設值是 3 次。
series:狀態碼配置(分段),符合某段狀態碼才會進行重試邏輯,預設值是 SERVER_ERROR,值是 5,也就是 5XX(5 開頭的狀態碼),共有 5 個值,程式碼如下所示。
public enum Series { INFORMATIONAL(1), SUCCESSFUL(2), REDIRECTION(3), CLIENT_ERROR(4), SERVER_ERROR(5); }
上述程式碼中具體引數含義如下所示。
statuses:狀態碼配置,和 series 不同的是這裡是具體狀態碼的配置,取值請參考 org.springframework.http.HttpStatus。
methods:指定哪些方法的請求需要進行重試邏輯,預設值是 GET 方法,取值程式碼如下所示。
public enum HttpMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; }
上述程式碼中具體引數含義如下所示。 exceptions:指定哪些異常需要進行重試邏輯。預設值是 java.io.IOException 和 org.springframework.cloud.gateway.support.TimeoutException。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70006413/viewspace-2793938/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- (41)java Spring Cloud企業快速開發架構之SpringCloud-Gateway的常用路由斷言工廠JavaSpringCloud架構GCGateway路由
- java Spring Cloud企業快速開發架構之SpringCloud-Spring Cloud EurekaJavaSpringCloud架構GC
- Spring Cloud Gateway限流實戰SpringCloudGateway
- (2)java Spring Cloud+Spring boot企業快速開發架構之Spring Cloud版本介紹JavaCloudSpring Boot架構
- Spring Cloud Alibaba:Sentinel實現熔斷與限流SpringCloud
- java Spring Cloud企業快速開發架構之Spring Boot Starter的介紹及使用JavaCloud架構Spring Boot
- java Spring Cloud企業快速開發架構之Ribbon結合RestTemplate實現負載均衡JavaSpringCloud架構REST負載
- spring cloud gateway 之限流篇SpringCloudGateway
- (20)java Spring Cloud企業快速開發架構之SpringCloud-Ribbon自定義負載均衡策略JavaSpringCloud架構GC負載
- Spring Cloud實戰系列(四) - 熔斷器HystrixSpringCloud
- Spring Cloud Gateway 限流操作SpringCloudGateway
- 11.Spring Cloud 分散式、微服務、雲架構企業快速開發架構之Linux 磁碟管理SpringCloud分散式微服務架構Linux
- java Spring Cloud企業快速開發架構之SpringCloud-Eureka的REST API及API擴充套件JavaSpringCloud架構GCRESTAPI套件
- Spring Cloud Gateway 整合阿里 Sentinel閘道器限流實戰!SpringCloudGateway阿里
- 7.Spring Cloud 分散式、微服務、雲架構企業快速開發架構之Linux 遠端登入SpringCloud分散式微服務架構Linux
- 8.Spring Cloud 分散式、微服務、雲架構企業快速開發架構之Linux 檔案基本屬性SpringCloud分散式微服務架構Linux
- 9.Spring Cloud 分散式、微服務、雲架構企業快速開發架構之Linux 檔案與目錄管理SpringCloud分散式微服務架構Linux
- Spring Cloud Gateway實戰之五:內建filterSpringCloudGatewayFilter
- 使用springcloud gateway搭建閘道器(分流,限流,熔斷)SpringGCCloudGateway
- Spring cloud(4)-熔斷(Hystrix)SpringCloud
- (1)java Spring Cloud+mybatis企業快速開發架構之微服務是什麼?它的優缺點有哪些?JavaSpringCloudMyBatis架構微服務
- 10.Spring Cloud 分散式、微服務、雲架構企業快速開發架構之Linux 使用者和使用者組SpringCloud分散式微服務架構Linux
- Spring Cloud Gateway 入門案例SpringCloudGateway
- Spring cloud 之GatewaySpringCloudGateway
- Spring Cloud Gateway實戰之一:初探SpringCloudGateway
- 微服務熔斷限流Hystrix之Dashboard微服務
- Spring Cloud Gateway實戰之四:內建predicate小結SpringCloudGateway
- 快速突擊 Spring Cloud GatewaySpringCloudGateway
- 專案實戰|千鋒Java微服務架構之Alibaba篇_Spring Cloud AlibabaJava微服務架構SpringCloud
- Spring Cloud Alibaba 多租戶 saas 設計的企業開發架構SpringCloud架構
- Spring Cloud Gateway + oauth2 跨域配置實現SpringCloudGatewayOAuth跨域
- Spring Cloud Gateway 擴充套件支援動態限流SpringCloudGateway套件
- Envoy熔斷限流實踐(一)基於Rainbond外掛實現熔斷AI
- 整合spring cloud雲架構 - Gateway的基本入門SpringCloud架構Gateway
- 微服務架構 | 5.2 基於 Sentinel 的服務限流及熔斷微服務架構
- 企業快速開發平臺Spring Cloud+Spring Boot+Mybatis之Highcharts 基本餅圖CloudSpring BootMyBatis
- 企業快速開發平臺Spring Cloud+Spring Boot+Mybatis+ElementUICloudSpring BootMyBatisUI
- Spring Cloud Alibaba 分散式微服務+多租戶saas企業開發架構SpringCloud分散式微服務架構