在邮件标头中找到无效的字符
Retry-After
是鲜为人知的HTTP响应标头。 让我引用RFC 2616(HTTP 1.1规范)的相关部分:
14.37重试后
Retry-After
响应标头字段可与503
( 服务不可用 )响应一起使用,以指示请求客户端不希望使用服务多长时间。 该字段也可以与任何3xx(重定向)响应一起使用,以指示在发出重定向请求之前,要求用户代理等待的最短时间。 该字段的值可以是响应日期之后的HTTP日期或整数秒(十进制)。
Retry-After = "Retry-After" ":" ( HTTP-date | delta-seconds )
其用法的两个示例是:
Retry-After: Fri, 31 Dec 1999 23:59:59 GMT
Retry-After: 120
在后一个示例中,延迟为2分钟。
尽管具有3xx响应的用例很有趣,尤其是在最终一致的系统中(“ 您的资源将在2秒内在此链接下可用 ),但我们将集中于错误处理。 通过将Retry-After
添加到响应服务器,服务器可以在客户端再次可用时向其提供提示。 有人可能会争辩说,服务器几乎不知道何时将其重新联机,但是有几种有效的用例可以通过某种方式推断出这些知识:
- 计划的维护–这很明显,如果您的服务器在计划的维护窗口内停机,则可以从代理发送
Retry-After
,并提供准确的回叫时间。 如果客户理解并尊重此标头,客户当然不会更早地重试 - 队列/线程池已满-如果您的请求必须由线程池处理且已满,则可以估计何时可以处理下一个请求。 这需要绑定队列(请参阅: ExecutorService – 10个技巧和窍门 ,第6点),并粗略估计处理一项任务需要多长时间。 有了这些知识,您就可以估计何时可以在不排队的情况下为下一个客户提供服务。
- 断路器断开–在Hystrix中,您可以查询
- 下一个可用令牌/资源/任何内容
让我们集中讨论一个非平凡的用例。 假设您的Web服务由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) {
@Override
protected String run() throws Exception {
//...
}
};
}
这可以按预期工作,如果命令失败,超时或断路器断开,客户端将收到503。但是,在断路器的情况下,我们至少可以估计电路再次闭合需要多长时间。 不幸的是,没有公共API可以告诉您在发生灾难性故障时确切的电路将保持断开状态多长时间。 但是我们知道默认情况下断路器保持断开状态多长时间,这是一个很好的最大估计值。 当然,如果基础命令不断失败,电路可能会保持断开状态。 但是Retry-After
不能保证服务器会在给定的时间运行,这只是客户端停止尝试的提示。 以下实现很简单,但是很糟糕:
@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
值设置Retry-After
标头。 该解决方案存在一个微妙但灾难性的错误:如果某一天电路断开,我们将再也不会运行命令,因为我们会急于返回503。这意味着Hystrix将永远不会重试执行它,并且电路将永远保持断开状态。 我们必须尝试每次调用命令并捕获适当的异常:
@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);
}
这个很好用。 如果命令抛出异常并且相关电路断开,我们将设置适当的标题。 在所有示例中,我们花费毫秒并归一化为秒。 我不建议这样做,但是如果由于某些原因,您在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);
关于自动DDoS的注意事项
如果将相同的时间戳发送给许多唯一的客户端,则必须谨慎使用Retry-After
标头。 想象现在是15:30,然后您将Retry-After: Thu, 10 Feb 2015 15:40:00 GMT
发送给周围的所有人-只是因为您以某种方式估计服务将在15:40开通。 您持续发送相同时间戳的时间越长,尊重Retry-After
客户端所期望的DDoS“攻击”就越大。 基本上,每个人都将精确安排在15:40重试(显然,时钟未完全对齐,并且网络延迟有所变化,但仍然存在),从而使系统充满了请求。 如果您的系统设计正确,那么您可能会幸免于难。 但是,您可能会通过发送另一个固定的Retry-After
报头来减轻这种“攻击”,本质上是Retry-After
重新安排攻击。
话虽这么说,避免将固定的绝对时间戳发送给多个唯一的客户端。 即使您确切知道系统何时可用,也可以Retry-After
一段时间内分散“ Retry-After
值。 实际上,您应该逐渐让越来越多的客户进入,因此请尝试不同的概率分布。
摘要
Retry-After
HTTP响应标头既不是普遍已知的,也不是经常适用的。 但是在极少数情况下可以预期停机的情况下,请考虑在服务器端实施停机。 如果客户也意识到这一点,则可以在减少系统流量的同时提高系统吞吐量和响应时间。
翻译自: https://www.javacodegeeks.com/2015/02/retry-http-header-practice.html
在邮件标头中找到无效的字符