前言
想要源碼地址的可以加上此微信:Lemon877164954
前面給大家介紹了Spring Cloud Gateway的入門教程,這篇給大家探討下Spring Cloud Gateway的一些其他功能。
Spring Cloud Gateway中的重試
我們知道Spring Cloud Gateway中的大多數操作都是使用過濾器模式實現的,該模式是Spring Framework的一種實現GatewayFilter。在這里,我們可以在發送下游請求之前或之后修改傳入請求和傳出響應。 與我之前關於Spring Cloud Gateway的前兩篇文章中描述的示例相同,我們將構建JUnit測試類。它利用TestcontainersMockServer運行模擬暴露的REST端點。 在運行測試之前,我們需要准備一個包含名為Retry過濾器的示例路由。在定義此類類型時,GatewayFilter我們可以設置多個參數。通常,您將使用以下三個:
-
retries 單個傳入請求應嘗試的重試次數。該屬性的默認值為3
-
statuses–應該重試的HTTP狀態代碼列表,使用org.springframework.http.HttpStatus枚舉名稱表示。
-
backoff–用於在后續重試嘗試之間計算Spring Cloud Gateway超時的策略。默認情況下,此屬性是禁用的。
讓我們從最簡單的場景開始-使用參數的默認值。在這種情況下,我們只需要GatewayFilter為路線設置一個名稱Retry的filter就可以。
@ClassRule public static MockServerContainer mockServer = new MockServerContainer(); @BeforeClass public static void init() { // 設置系統參數
System.setProperty("spring.cloud.gateway.routes[0].id", "account-service"); System.setProperty("spring.cloud.gateway.routes[0].uri", "http://192.168.99.100:" + mockServer.getServerPort()); System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**"); System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?<path>.*), /$\\{path}"); System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "Retry"); // 使用mockServer容器
MockServerClient client = new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort()); // 請求url:/1 生效次數:Times.exactly(3)
client.when(HttpRequest.request() .withPath("/1"), Times.exactly(3)) .respond(response() .withStatusCode(500) .withBody("{\"errorCode\":\"5.01\"}") .withHeader("Content-Type", "application/json")); // 請求第4次后走這邊配置的/1
client.when(HttpRequest.request() .withPath("/1")) .respond(response() .withBody("{\"id\":1,\"number\":\"1234567891\"}") .withHeader("Content-Type", "application/json")); }
我們的測試方法非常簡單。它只是使用Spring FrameworkTestRestTemplate來執行對測試端點的單個調用。
@Autowired TestRestTemplate template; @Test public void testAccountService() { LOGGER.info("Sending /1..."); ResponseEntity r = template.exchange("/account/{id}", HttpMethod.GET, null, Account.class, 1); LOGGER.info("Received: status->{}, payload->{}", r.getStatusCodeValue(), r.getBody()); Assert.assertEquals(200, r.getStatusCodeValue()); }
在運行測試之前,我們要設置一下Spring Cloud Gateway日志的日志記錄級別,方便我們可以查看有關重試過程的信息。
logging.level.org.springframework.cloud.gateway.filter.factory: TRACE
由於我們未設置任何退避策略,因此立即嘗試了后續嘗試。如下圖所示,默認重試次數為3,並且過濾器嘗試重試所有返回500的請求)。
15:00:49.049 --- [ main] : Started GatewayRetryTest in 3.18 seconds (JVM running for 10.757) 15:00:49.252 --- [ main] : Sending /1... 15:00:49.382 --- [ctor-http-nio-2] : Entering retry-filter 15:00:49.520 --- [ctor-http-nio-3] : setting new iteration in attr 0
15:00:49.522 --- [ctor-http-nio-3] : exceedsMaxIterations false, iteration 0, configured retries 3
15:00:49.523 --- [ctor-http-nio-3] : retryableStatusCode: true, statusCode 500 INTERNAL_SERVER_ERROR, configured statuses [500 INTERNAL_SERVER_ERROR], configured series [SERVER_ERROR] 15:00:49.523 --- [ctor-http-nio-3] : retryableMethod: true, httpMethod GET, configured methods [GET] 15:00:49.571 --- [ctor-http-nio-3] : setting new iteration in attr 1
15:00:49.571 --- [ctor-http-nio-3] : exceedsMaxIterations false, iteration 1, configured retries 3
15:00:49.571 --- [ctor-http-nio-3] : retryableStatusCode: true, statusCode 500 INTERNAL_SERVER_ERROR, configured statuses [500 INTERNAL_SERVER_ERROR], configured series [SERVER_ERROR] 15:00:49.571 --- [ctor-http-nio-3] : retryableMethod: true, httpMethod GET, configured methods [GET] 15:00:49.584 --- [ctor-http-nio-3] : setting new iteration in attr 2
15:00:49.584 --- [ctor-http-nio-3] : exceedsMaxIterations false, iteration 2, configured retries 3
15:00:49.584 --- [ctor-http-nio-3] : retryableStatusCode: true, statusCode 500 INTERNAL_SERVER_ERROR, configured statuses [500 INTERNAL_SERVER_ERROR], configured series [SERVER_ERROR] 15:00:49.584 --- [ctor-http-nio-3] : retryableMethod: true, httpMethod GET, configured methods [GET] 15:00:49.633 --- [ctor-http-nio-3] : setting new iteration in attr 3
15:00:49.634 --- [ctor-http-nio-3] : exceedsMaxIterations true, iteration 3, configured retries 3
15:00:49.646 --- [ main] : Received: status->404, payload->null java.lang.AssertionError: Expected :200 Actual :404
<Click to see difference>
現在,讓我們設置一些更高級的配置。我們可以更改重試次數,並設置要重試的確切HTTP狀態代碼,而不是一系列代碼。在我們的例子中,重試的狀態代碼是HTTP 500,因為它是由我們的模擬端點返回的。
默認觸發重試的計算公式為:
backoff.firstBackoff <= prevBackoff * factor < =backoff.maxBackoff
- backoff.firstBackoff 開始重試的時間
- backoff.maxBackoff 最大重試的時間
- backoff.factor 重試因子
- basedOnPreviousValue
- 當設置為false時候 計算重試值變成: firstBackoff * (factor ^ n)
- 當設置為true時候 計算重試值變成: prevBackoff * factor
@ClassRule public static MockServerContainer mockServer = new MockServerContainer(); @BeforeClass public static void init() { System.setProperty("spring.cloud.gateway.routes[0].id", "account-service"); System.setProperty("spring.cloud.gateway.routes[0].uri", "http://192.168.99.100:" + mockServer.getServerPort()); System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**"); System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?<path>.*), /$\\{path}"); System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "Retry"); System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.retries", "10"); System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.statuses", "INTERNAL_SERVER_ERROR"); System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.backoff.firstBackoff", "50ms"); System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.backoff.maxBackoff", "500ms"); System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.backoff.factor", "2"); System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.backoff.basedOnPreviousValue", "true"); MockServerClient client = new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort()); client.when(HttpRequest.request() .withPath("/1"), Times.exactly(3)) .respond(response() .withStatusCode(500) .withBody("{\"errorCode\":\"5.01\"}") .withHeader("Content-Type", "application/json")); client.when(HttpRequest.request() .withPath("/1")) .respond(response() .withBody("{\"id\":1,\"number\":\"1234567891\"}") .withHeader("Content-Type", "application/json")); // OTHER RULES
}
如果使用新配置再運行一次相同的測試,則日志看起來會有些不同。我在下面的圖片中強調了最重要的區別。如您所見,僅HTTP 500狀態的當前重試次數為10。設置重試策略后,第一次重試嘗試在50毫秒后執行,第二次重試在100毫秒后進行,第三次重試在200毫秒后進行,依此類推。(日志記錄時間會有一點延遲,大家可以改變factor進行驗證)
15:24:01.133 --- [ main] : Sending /1... 15:24:01.288 --- [ctor-http-nio-2] : Entering retry-filter ## 第一次請求時間 419
15:24:01.419 --- [ctor-http-nio-3] : setting new iteration in attr 0
15:24:01.421 --- [ctor-http-nio-3] : exceedsMaxIterations false, iteration 0, configured retries 3
15:24:01.422 --- [ctor-http-nio-3] : retryableStatusCode: true, statusCode 500 INTERNAL_SERVER_ERROR, configured statuses [500 INTERNAL_SERVER_ERROR], configured series [SERVER_ERROR] 15:24:01.423 --- [ctor-http-nio-3] : retryableMethod: true, httpMethod GET, configured methods [GET] ## 第一次重試時間 494 重試時間大概在 494 -419 = 75
15:24:01.494 --- [ctor-http-nio-3] : setting new iteration in attr 1
15:24:01.494 --- [ctor-http-nio-3] : exceedsMaxIterations false, iteration 1, configured retries 3
15:24:01.494 --- [ctor-http-nio-3] : retryableStatusCode: true, statusCode 500 INTERNAL_SERVER_ERROR, configured statuses [500 INTERNAL_SERVER_ERROR], configured series [SERVER_ERROR] 15:24:01.494 --- [ctor-http-nio-3] : retryableMethod: true, httpMethod GET, configured methods [GET] ## 第二次重試時間 623 重試時間大概在 623 -494 = 129
15:24:01.623 --- [ctor-http-nio-3] : setting new iteration in attr 2
15:24:01.623 --- [ctor-http-nio-3] : exceedsMaxIterations false, iteration 2, configured retries 3
15:24:01.623 --- [ctor-http-nio-3] : retryableStatusCode: true, statusCode 500 INTERNAL_SERVER_ERROR, configured statuses [500 INTERNAL_SERVER_ERROR], configured series [SERVER_ERROR] 15:24:01.623 --- [ctor-http-nio-3] : retryableMethod: true, httpMethod GET, configured methods [GET] ## 第二次重試時間 623 重試時間大概在 852 -623 = 230
15:24:01.852 --- [ctor-http-nio-3] : setting new iteration in attr 3
15:24:01.852 --- [ctor-http-nio-3] : exceedsMaxIterations true, iteration 3, configured retries 3
15:24:01.878 --- [ main] : Received: status->200, payload->Account(id=1, number=1234567891)
Spring Cloud Gateway中的超時
超時是請求路由的另一個重要方面。使用Spring Cloud Gateway,我們可以輕松設置全局讀取和連接超時。另外,我們也可以為每個路線分別定義它們。讓我們在測試路由定義中添加以下屬性全局超時設置為100ms。現在,我們的測試路由包含一個測試Retry過濾器,其新添加的全局讀取超時為100ms。
System.setProperty("spring.cloud.gateway.httpclient.response-timeout", "100ms");
另外,我們可以設置每條路由的超時時間。如果我們在這里更喜歡這樣的解決方案,則應該在示例測試中添加一行。
System.setProperty("spring.cloud.gateway.routes[1].metadata.response-timeout", "100");
然后,我們定義一個請求路徑為/2具有200ms延遲的另一個測試端點。我們當前的測試方法與前一種方法非常相似,不同之處在於,我們期望使用HTTP 504作為結果。
@Test public void testAccountServiceFail() { LOGGER.info("Sending /2..."); ResponseEntity<Account> r = template.exchange("/account/{id}", HttpMethod.GET, null, Account.class, 2); LOGGER.info("Received: status->{}, payload->{}", r.getStatusCodeValue(), r.getBody()); Assert.assertEquals(504, r.getStatusCodeValue()); }
讓我們進行測試。結果在下面的圖片中可見。日志中請求在幾次失敗重試后,由於設置最大的重試時間為500ms。Retry過濾器會打印出IOException和TimeoutException。
15:41:15.814 --- [ main] : Sending /2... 15:41:15.940 --- [ctor-http-nio-2] : Entering retry-filter 15:41:16.077 --- [ parallel-1] : setting new iteration in attr 0
15:41:16.078 --- [ parallel-1] : exceedsMaxIterations false, iteration 0, configured retries 3
15:41:16.079 --- [ parallel-1] : exception or its cause is retryable org.springframework.web.server.ResponseStatusException{cause=org.springframework.cloud.gateway.support.TimeoutException}, configured exceptions [class java.io.IOException, class org.springframework.cloud.gateway.support.TimeoutException] 15:41:16.239 --- [ parallel-3] : setting new iteration in attr 1
15:41:16.240 --- [ parallel-3] : exceedsMaxIterations false, iteration 1, configured retries 3
15:41:16.240 --- [ parallel-3] : exception or its cause is retryable org.springframework.web.server.ResponseStatusException{cause=org.springframework.cloud.gateway.support.TimeoutException}, configured exceptions [class java.io.IOException, class org.springframework.cloud.gateway.support.TimeoutException] 15:41:16.500 --- [ parallel-5] : setting new iteration in attr 2
15:41:16.500 --- [ parallel-5] : exceedsMaxIterations false, iteration 2, configured retries 3
15:41:16.500 --- [ parallel-5] : exception or its cause is retryable org.springframework.web.server.ResponseStatusException{cause=org.springframework.cloud.gateway.support.TimeoutException}, configured exceptions [class java.io.IOException, class org.springframework.cloud.gateway.support.TimeoutException] 15:41:17.058 --- [ parallel-7] : setting new iteration in attr 3
15:41:17.059 --- [ parallel-7] : exceedsMaxIterations true, iteration 3, configured retries 3
15:41:17.128 --- [ main] : Received: status->504, payload->Account(id=null, number=null)
END
下篇給大家介紹Spring Cloud Gateway的限流
歡迎關注公眾號! 公眾號回復:
入群
,掃碼加入我們交流群