задний план
Онлайн-сервисы полагаются на сторонние сервисы, а время отклика интерфейса RT очень велико при большом объеме трафика. Поскольку каждый параметр тайм-аута установлен на 5 с.
Пример
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
// 连接池最大的连接数
connManager.setMaxTotal(6000);
// 单个路由的最大连接数,例如:www.baidu.com,图片最大高峰并发量520
connManager.setDefaultMaxPerRoute(500);
RequestConfig.Builder custom = RequestConfig.custom();
// 连接超时时间 建立连接时间 此前设置3000
custom.setConnectTimeout(800);
// 从connect Manager获取Connection 超时时间(连接池获取连接超时时间)
custom.setConnectionRequestTimeout(500);
// (获取response的返回时间)读取返回时间 此前设置40000,官方建议40s
custom.setSocketTimeout(1000);
RequestConfig config = custom.build();
// DefaultHttpRequestRetryHandler 重试策略(可自定义)
httpClient = HttpClients.custom().setRetryHandler(new DefaultHttpRequestRetryHandler(1, true)).setConnectionManager(connManager)
.setDefaultRequestConfig(config).build();
Построить httpClient
С помощью отладки можно узнать, что после выполнения метода сборки получается объект InternalHttpClient. Если цепочка выполнения не указана, используется исполнитель RetryExec. Стратегия повтора по умолчанию — DefaultHttpRequestRetryHandler.
public CloseableHttpClient build() {
... 省略部分代码
// Add request retry executor, if not disabled
if (!automaticRetriesDisabled) {
HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
if (retryHandlerCopy == null) {
retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
}
execChain = new RetryExec(execChain, retryHandlerCopy);
}
... 省略部分代码
}
параметр тайм-аута
Три аспекта настроек тайм-аута
- время ожидания подключения setConnectTimeout
- тайм-аут чтения setSocketTimeout
- Получить время ожидания соединения из пула соединений setConnectionRequestTimeout
Влияние стратегии повторных попыток на бизнес
По умолчанию существует политика повторных попыток, или вы можете вручную изменить или переопределить политику повторных попыток, но в некоторых случаях вы можете повторить попытку, а в некоторых случаях повторная попытка недействительна.
Повторить анализ
Для получения и публикации в нашем сценарии приложения это можно резюмировать следующим образом:
Повторная попытка произойдет только тогда, когда произойдет IOExecetion InterruptedIOException, UnknownHostException, ConnectException, SSLException, не повторяйте попытку при возникновении этих 4 исключений Метод get можно повторить 3 раза, а метод post можно повторить 3 раза, если выходной поток, соответствующий сокету, не записан и сброс выполнен успешно.
Исключения не повторялись:
- InterruptedIOException, исключение прерывания потока
- UnknownHostException, соответствующий хост не найден
- ConnectException, хост найден, соединение не установлено
- SSLException, исключение аутентификации https
Стратегия повторных попыток DefaultHttpRequestRetryHandler
@Override
public boolean retryRequest(
final IOException exception,
final int executionCount,
final HttpContext context) {
Args.notNull(exception, "Exception parameter");
Args.notNull(context, "HTTP context");
if (executionCount > this.retryCount) {
// Do not retry if over max retry count
return false;
}
// 异常类是否在禁止重试中
if (this.nonRetriableClasses.contains(exception.getClass())) {
return false;
} else {
for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) {
if (rejectException.isInstance(exception)) {
return false;
}
}
}
final HttpClientContext clientContext = HttpClientContext.adapt(context);
final HttpRequest request = clientContext.getRequest();
if(requestIsAborted(request)){
return false;
}
if (handleAsIdempotent(request)) {
// Retry if the request is considered idempotent
return true;
}
// 根据上下文判断请求是否发送成功了,或者根据状态为是否永远可以重复发送(默认的是否) requestSentRetryEnabled 参数可以在构建httpCLient对象设置重试策略指定此属性
// isRequestSent 是在HttpRequestExecutor中doSendRequest方法中flush成功设置context.setAttribute("http.request_sent", Boolean.TRUE);
if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) {
// Retry if the request has not been sent fully or
// if it's OK to retry methods that have been sent
return true;
}
// otherwise do not retry
return false;
}
// 几种类型的异常不重试,同时判断是否与异常类型的子类,是子类的话,也不重试
Кроме того, есть два тайм-аута, тайм-аут подключения и тайм-аут чтения: java.net.SocketTimeoutException: время чтения истекло java.net.SocketTimeoutException: время ожидания подключения истекло Оба этих тайм-аута являются SocketTimeoutException, унаследованными от InterruptedIOException, которые относятся к первому исключению прерывания потока выше и не будут повторяться.
При каких обстоятельствах будет повторная попытка
Когда почтовый запрос записывает и сбрасывает выходной поток, какие IOExecetions, кроме InterruptedIOException, UnknownHostException, ConnectException и SSLException, произойдут.
Проблема может заключаться в шаге HttpClientConnection.flush().Если вы проследите, то сможете узнать, что объектом его операции является SocketOutputStream, а сброс этого класса является пустой реализацией, поэтому вам нужно только посмотреть метод вирте.
Повторить параметр requestSentRetryEnabled http.request_sent
//根据上下文判断请求是否发送成功了,或者根据状态为是否永远可以重复发送(默认的是否)
if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) {
return true;
}
Отключить повтор
httpclient по умолчанию предоставляет стратегию повторных попыток. В некоторых сценариях мы можем вручную отключить стратегию повторных попыток. В HttpClientBuilder причина выбора исполнителя RetryExec в его методе build() имеет предусловия, то есть отсутствует ручной запрет.
// Add request retry executor, if not disabled
if (!automaticRetriesDisabled) {
HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
if (retryHandlerCopy == null) {
retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
}
execChain = new RetryExec(execChain, retryHandlerCopy);
}