在实践中重试HTTP标头
Retry-After是鮮為人知的HTTP響應(yīng)標頭。 讓我引用RFC 2616(HTTP 1.1規(guī)范)的相關(guān)部分:
14.37重試后
Retry-After響應(yīng)標頭字段可與503 ( 服務(wù)不可用 )響應(yīng)一起使用,以指示請求該客戶端的服務(wù)預(yù)計無法使用多長時間。 該字段也可以與任何3xx(重定向)響應(yīng)一起使用,以指示在發(fā)出重定向請求之前,要求用戶代理等待的最短時間。 該字段的值可以是響應(yīng)日期之后的HTTP日期或整數(shù)秒(十進制)。
Retry-After = "Retry-After" ":" ( HTTP-date | delta-seconds )其用法的兩個示例是:
Retry-After: Fri, 31 Dec 1999 23:59:59 GMT Retry-After: 120在后一個示例中,延遲為2分鐘。
盡管具有3xx響應(yīng)的用例很有趣,尤其是在最終一致的系統(tǒng)中(“ 您的資源將在2秒內(nèi)在此鏈接下可用 ),但我們將重點放在錯誤處理上。 通過將Retry-After添加到響應(yīng)服務(wù)器,服務(wù)器可以在客戶端再次可用時提供提示。 有人可能會爭辯說,服務(wù)器幾乎不知道何時將其重新聯(lián)機,但是在幾種有效的用例中,可以通過某種方式推斷出這種知識:
- 計劃的維護–這很明顯,如果您的服務(wù)器在計劃的維護窗口內(nèi)關(guān)閉,則可以從代理發(fā)送Retry-After ,并提供準確的回叫時間。 如果客戶理解并尊重此標頭,客戶當(dāng)然不會更早地重試
- 隊列/線程池已滿-如果您的請求必須由線程池處理且已滿,則可以估計何時可以處理下一個請求。 這需要綁定隊列(請參閱: ExecutorService – 10個技巧和竅門 ,第6點),并粗略估計處理一項任務(wù)需要多長時間。 有了這些知識,您可以估計何時可以在不排隊的情況下為下一個客戶提供服務(wù)。
- 斷路器打開–在Hystrix中,您可以查詢
- 下一個可用令牌/資源/任何
讓我們關(guān)注一個非平凡的用例。 假設(shè)您的Web服務(wù)由Hystrix命令支持:
private static final HystrixCommand.Setter CMD_KEY = HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("REST")).andCommandKey(HystrixCommandKey.Factory.asKey("fetch"));@RequestMapping(value = "/", method = GET) public String fetch() {return fetchCommand().execute(); }private HystrixCommand<String> fetchCommand() {return new HystrixCommand<String>(CMD_KEY) {@Overrideprotected String run() throws Exception {//...}}; }這可以按預(yù)期工作,如果命令失敗,超時或斷路器打開,客戶端將收到503。但是,在斷路器的情況下,我們至少可以估計重新閉合電路需要多長時間。 不幸的是,沒有公共API可以告訴您在發(fā)生災(zāi)難性故障時確切的電路將保持斷開狀態(tài)多長時間。 但是我們知道斷路器默認保持斷開狀態(tài)多長時間,這是一個很好的最大估計值。 當(dāng)然,如果基礎(chǔ)命令不斷失敗,電路可能會保持斷開狀態(tài)。 但是Retry-After不能保證服務(wù)器會在給定的時間運行,這只是客戶端停止嘗試的提示。 以下實現(xiàn)很簡單,但是很糟糕:
@RequestMapping(value = "/", method = GET) public ResponseEntity<String> fetch() {final HystrixCommand<String> command = fetchCommand();if (command.isCircuitBreakerOpen()) {return handleOpenCircuit(command);}return new ResponseEntity<>(command.execute(), HttpStatus.OK); }private ResponseEntity<String> handleOpenCircuit(HystrixCommand<String> command) {final HttpHeaders headers = new HttpHeaders();final Integer retryAfterMillis = command.getProperties().circuitBreakerSleepWindowInMilliseconds().get();headers.set(HttpHeaders.RETRY_AFTER, Integer.toString(retryAfterMillis / 1000));return new ResponseEntity<>(headers, HttpStatus.SERVICE_UNAVAILABLE); }如您所見,我們可以詢問任何命令其斷路器是否斷開。 如果打開,則使用circuitBreakerSleepWindowInMilliseconds值設(shè)置Retry-After標頭。 該解決方案存在一個細微但災(zāi)難性的錯誤:如果某一天電路斷開,我們將再也不會運行命令,因為我們會急于返回503。這意味著Hystrix將永遠不會重試執(zhí)行它,并且電路將永遠保持斷開狀態(tài)。 我們必須嘗試每次調(diào)用命令并捕獲適當(dāng)?shù)漠惓?#xff1a;
@RequestMapping(value = "/", method = GET) public ResponseEntity<String> fetch() {final HystrixCommand<String> command = fetchCommand();try {return new ResponseEntity<>(command.execute(), OK);} catch (HystrixRuntimeException e) {log.warn("Error", e);return handleHystrixException(command);} }private ResponseEntity<String> handleHystrixException(HystrixCommand<String> command) {final HttpHeaders headers = new HttpHeaders();if (command.isCircuitBreakerOpen()) {final Integer retryAfterMillis = command.getProperties().circuitBreakerSleepWindowInMilliseconds().get();headers.set(HttpHeaders.RETRY_AFTER, Integer.toString(retryAfterMillis / 1000));}return new ResponseEntity<>(headers, SERVICE_UNAVAILABLE); }這個很好用。 如果命令拋出異常并且相關(guān)電路斷開,我們將設(shè)置適當(dāng)?shù)臉祟}。 在所有示例中,我們花費毫秒并歸一化為秒。 我不推薦這樣做,但是如果由于某些原因,您更喜歡Retry-After標頭中的絕對日期而不是相對超時,則HTTP日期格式最終是Java的一部分(自JDK 8起):
import java.time.format.DateTimeFormatter;//...final ZonedDateTime after5seconds = ZonedDateTime.now().plusSeconds(5); final String httpDate = DateTimeFormatter.RFC_1123_DATE_TIME.format(after5seconds);關(guān)于自動DDoS的注意事項
如果將相同的時間戳發(fā)送給許多唯一的客戶端,則必須謹慎使用Retry-After標頭。 想象現(xiàn)在是15:30,然后您將Retry-After: Thu, 10 Feb 2015 15:40:00 GMT發(fā)送給周圍的所有人-只是因為您以某種方式估計服務(wù)將在15:40開通。 您持續(xù)發(fā)送相同時間戳的時間越長,尊重Retry-After客戶端所期望的DDoS“攻擊”就越大。 基本上每個人都會在15:40安排重試的時間(很明顯,時鐘未完全對齊,并且網(wǎng)絡(luò)延遲有所變化,但仍然存在),從而使系統(tǒng)充滿了請求。 如果您的系統(tǒng)設(shè)計正確,那么您可能會幸免于難。 但是,您可能會通過發(fā)送另一個固定的Retry-After標頭來緩解這種“攻擊”,此舉實際上是Retry-After重新安排攻擊時間。
話雖這么說,避免將固定的絕對時間戳發(fā)送給多個唯一的客戶端。 即使您確切知道系統(tǒng)何時可用,也可以Retry-After一段時間內(nèi)分散“ Retry-After值。 實際上,您應(yīng)該逐漸讓越來越多的客戶進入,因此請嘗試不同的概率分布。
摘要
Retry-After HTTP響應(yīng)標頭既不是普遍已知的,也不是經(jīng)常適用的。 但是在極少數(shù)情況下可以預(yù)期停機的情況下,請考慮在服務(wù)器端實施停機。 如果客戶端也意識到這一點,則可以在減少系統(tǒng)流量和改善系統(tǒng)吞吐量和響應(yīng)時間的同時,大幅度減少網(wǎng)絡(luò)流量。
翻譯自: https://www.javacodegeeks.com/2015/02/retry-http-header-practice.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的在实践中重试HTTP标头的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人死后几天圆坟(人死后为啥圆坟?怎样圆坟
- 下一篇: 带Lambda表达式的Apache Wi