Ставь лайк и потом смотри, вырабатывай полезную привычку
предисловие
В сетевых запросах из-за ненадежности сети часто возникают сценарии, в которых запросы не выполняются. В ответ на эту проблему обычной практикой является добавление механизма повторных попыток, повторный запрос после сбоя запроса и попытка обеспечить успех запроса, тем самым повышая стабильность службы.
риск повторной попытки
Но большинство людей не хотят легко повторять попытку, потому что повторная попытка часто сопряжена с большим риском. Например, слишком большое количество повторных попыток вызовет дополнительную нагрузку на вызываемую службу и усугубит первоначальную проблему.
Как показано на рисунке ниже, служба A вызывает службу B, а служба B вызывает службу C и службу D в соответствии с разными данными запроса. В это время служба C дает сбой и становится недоступной, затем все запросы к службе C в службе B истекают по тайм-ауту, но служба D сейчас все еще доступна; однако из-за большого количества повторных попыток в службе A нагрузка на службу B быстро увеличивается.Вскоре нагрузка службы B будет полной (например, пул соединений полон). Теперь запрос на ветвление, вызывающий службу D, также недоступен, поскольку служба B уже заполнена повторными запросами и больше не может обрабатывать запросы.Если сама служба доступна, но в сети наблюдается большая задержка, дрожание или потеря пакетов, из-за чего запрос достигает целевой службы или возвращается для инициирования тайм-аута службы; если клиент инициирует повторную попытку в это время, то для на принимающей стороне весьма вероятно, что получено несколько одинаковых запросов. такСерверу также необходимо добавить идемпотентную обработку, чтобы обеспечить согласованные результаты при нескольких запросах.
Поскольку повторная попытка сопряжена с риском, не стоит ли повторить попытку? Если вы потерпите неудачу, вы потерпите неудачу напрямую, вам все равно?
Неудачные попытки в разное время
Независимо от того, повторять попытку или нет, это должно различать причину текущего сбоя и не может просто и грубо решить, повторять попытку или не повторять. Сеть очень сложная, ссылки очень длинные, а разные типы протоколов имеют разные стратегии принятия решения о повторной попытке.
Повторите попытку по протоколу HTTP
Базовый HTTP-запрос будет включать следующие этапы:
- разрешение DNS
- Трехстороннее рукопожатие TCP
- Отправка и получение одноранговых данных
На этапе разрешения DNS, если имя домена не существует или имя домена не имеет записи DNS, и соответствующий список адресов узлов не может быть разрешен в соответствии с именем домена, то запрос вообще не может быть инициирован. на этот раз повторная попытка бессмысленна, поэтому нет необходимости повторять попытку.
На фазе TCP-рукопожатия, если целевой сервис недоступен, то нет смысла повторять попытку в это время, потому что на первом шаге запроса — рукопожатие неуспешно, велика вероятность того, что хост недоступен.
После прохождения двух этапов DNS и рукопожатия наконец настало время отправлять и получать данные. На этом этапе, как только происходит сбой, есть больше факторов, которые следует учитывать, стоит ли повторять попытку.
В этой ситуации, как показано на рисунке ниже, из-за перегрузки сети и других причин время доставки данных на сервер слишком велико.Но в итоге сервер тоже получил полное сообщение и начал обрабатывать запрос, но в это время клиент отказался от запроса из-за таймаута., то если в это время клиент создаст новое TCP-соединение и инициирует повторную попытку, тоДля сервера он дважды получит одно и то же сообщение запроса и дважды обработает запрос, что может привести к серьезным последствиям.
так этобыл успешно отправленне подходит для повторной попыткиВопрос в том, как я могу узнать, что я отправил его успешно? socket.write успешно выполнен без сообщения об ошибке? После SocketChannel.write успешно, если буфер пуст?
Это не так просто.Запись сокета на прикладном уровне просто записывает данные в буфер SND.Нет гарантии, когда данные в буфере SND будут отправлены в сеть операционной системой. Блокировка и неблокировка предназначены только для операции socket.write.Когда буфер SND заполнен и данные не могут быть записаны в буфер SND ядра, произойдет блокировка.
Но можно грубо считать, что socket.write выполнен успешно и буфер прикладного уровня пуст, то есть успешно отправлен.
Теперь посмотрим на другую ситуацию, когда данные отправляются, пир напрямую закрывает сокет и возвращает первый идентификатор:
Тогда в этом случае очень удобно повторить попытку. Поскольку для сервера он еще не начал обрабатывать этот запрос, поэтому повторная попытка (повторное подключение и повторная отправка запроса) только улучшит доступность и не вызовет никакой нагрузки.
В протоколе HTTP есть еще несколько для метода запроса.семантическая условность:
GET | POST | PUT | DELET |
---|---|---|---|
списокURI и сведения о каждом ресурсе в этой группе ресурсов (последнее является необязательным). | в этой группе ресурсовсоздать/добавитьновый ресурс. Эта операция часто возвращает URL-адрес нового ресурса. | использовать заданный набор ресурсовзаменятьТекущий весь набор ресурсов. | Удалитьвесь набор ресурсов. |
Безопасный (более идемпотентный) | неидемпотентный | идемпотент | идемпотент |
PUT/DELETE — это идемпотентная операция, поэтому даже если будет обработан запрос на одно и то же сообщение, не возникнет таких проблем, как дублирование данных. Но POST — нет, семантика POST — создание/добавление, что является неидемпотентным типом запроса.
Теперь вернемся к проблеме повторной попытки выше, если сообщение запроса было отправлено успешно, но время ожидания ответа истекло, но запрошенный метод API имеет тип DELETE, в этом случае вы можете рассмотреть возможность повторной попытки, поскольку DELETE семантически идемпотентен; GET /PUT то же самое, семантически идемпотентный может рассмотреть повторную попытку.
Но POST не работает, потому что он семантически неидемпотент, и повторная попытка, скорее всего, вызовет повторные запросы на обработку.
Но... так ли все хорошо? Сколько API могут строго придерживаться семантики? Поэтому полагаться только на семантические соглашения очень неустойчиво.Вы должны достаточно знать о том, поддерживает ли интерфейс сервера идемпотентность, прежде чем вы сможете рассмотреть проблему повторных попыток.
Повторите попытку через HTTPS
HTTPS существует уже много лет, и в последние годы он, наконец, стал полностью популярным.Необновленные веб-сайты будут получать сообщения о небезопасности в браузерах.В настоящее время веб-API, которые могут быть доступны в общедоступной сети, в основном HTTPS.
В HTTPS стратегия повторных попыток будет иметь некоторые изменения:Картинка вышеПоток рукопожатия HTTPS, после установления TCP-соединения сначала будет выполнено SSL-подтверждение, будет проверен одноранговый сертификат и сгенерирован временный симметричный ключ.
Если на этапе подтверждения SSL произошел сбой, например, срок действия сертификата истек, сертификат не является доверенным и т. д., повторная попытка вообще не требуется. Поскольку такого рода проблемы не будут кратковременными, однажды возникнув, они будут терпеть неудачу в течение длительного времени, а повторная попытка также будет неудачной.
Механизмы повторных попыток в основных сетевых библиотеках и платформах RPC
После введения рассмотрения повторных попыток в протоколе HTTP (S) давайте посмотрим, как основная сетевая библиотека обрабатывает повторные попытки, и посмотрим, является ли механизм обработки в этом основном проекте с открытым исходным кодом «разумным».
Механизм повтора для Apache HttpClient (v4.x)
Apache HttpClient — это наиболее распространенная библиотека инструментов HTTP в Java (внутреннее направление).Хотя JDK также предоставляет базовый HTTP SDK, он слишком прост, чтобы его можно было использовать напрямую. Apache HttpClient (сокращенно Apache HC) компенсирует этот недостаток, предоставляя сверхмощный HTTP SDK, который является мощным, простым в использовании, и все компоненты которого можно настраивать.
Класс политики повторных попыток по умолчанию для Apache HC находится вorg.apache.http.impl.client.DefaultHttpRequestRetryHandler
, давайте сначала посмотрим на реализацию (некоторый неважный код опущен):
//返回true,代表需要重试,false不重试
@Override
public boolean retryRequest(
final IOException exception,
final int executionCount,
final HttpContext context) {
//判断重试次数是否达到上线
if (executionCount > this.retryCount) {
// Do not retry if over max retry count
return false;
}
//判断哪些异常不用重试
if (this.nonRetriableClasses.contains(exception.getClass())) {
return false;
}
//判断是否是幂等请求
if (handleAsIdempotent(request)) {
// Retry if the request is considered idempotent
return 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;
}
Кратко резюмируем стратегию повторных попыток Apache HC:
- Определите, превысило ли количество попыток максимальное количество раз (по умолчанию 3 раза), и не будет повторять попытку, если оно превышает
- Определите, какие исключения не требуют повторной попытки
- UnknownHostException - хост не найден
- ConnectException - не удалось рукопожатие TCP
- SSLException - рукопожатие SSL не удалось
- InterruptedIOException(ConnectTimeoutException/SocketTimeoutException) - тайм-аут рукопожатия, тайм-аут чтения сокета (также примерно считается тайм-аутом ответа)
- Определите, является ли запрос идемпотентным, и можно ли повторить идемпотентный запрос.
- Определите, было ли отправлено сообщение запроса, и повторите попытку, если оно не было отправлено.
- Повторный запрос непосредственно при повторной попытке, без интервала
Таким образом, стратегия повторных попыток по умолчанию в Apache HC точно такая же, как «разумная» стратегия повторных попыток, которую мы представили в предыдущем разделе. Видно, что этот основной проект с открытым исходным кодом действительно превосходен, качество очень высокое, все проекты соответствуют стандарту, и более эффективно использовать исходный код такого рода проектов в качестве учебных материалов.
Механизм повторных попыток Dubbo (v2.6.x)
Код механизма повторных попыток в Dubbo находится вcom.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker
(После 2.7 имя пакета обновляется до org.apache.dubbo)
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
//获取配置的重试次数,默认1即不重试
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
Set<String> providers = new HashSet<String>(len);
for (int i = 0; i < len; i++) {
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + invocation.getMethodName()
+ " in the service " + getInterface().getName()
+ " was successful by the provider " + invoker.getUrl().getAddress()
+ ", but there have been failed providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion() + ". Last error is: "
+ le.getMessage(), le);
}
return result;
} catch (RpcException e) {
//Biz类型的异常,会抛出异常,不进行不重试,非Biz类的RpcException都会进行重试
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
}
Как видно из кода, толькоRpcException не типа Biz, вызовет повторную попытку. Продолжайте анализировать код, чтобы увидеть, какие сценарии вызовут повторную попытку... Забудьте об этом, не публикуйте код, просто перейдите к ответу!
Кратко обобщим стратегию повторных попыток в Dubbo:
- Количество попыток по умолчанию — 3 (включая первый запрос)., повторная попытка будет запущена только в том случае, если конфигурация больше 1
- По умолчанию используется стратегия Failover, поэтому повторная попытка не будет повторять текущий узел, а будет повторяться только следующий узел (после доступных узлов -> балансировка нагрузки -> маршрутизация).
- Тайм-аут рукопожатия TCP вызывает повторную попытку
- Тайм-аут ответа вызывает повторную попытку
- Ошибки сообщения или другие ошибки приводят к тому, что соответствующий запрос не может быть найден, и это также приводит к истечению времени ожидания Future, и время ожидания будет повторено.
- Для исключения, возвращенного сервером (например, брошенного провайдером), вызов выполнен успешно и не будет повторен.
Стратегия повторных попыток Dubbo по-прежнему несколько агрессивна и не такая осторожная, как Apache HC... Поэтому при использовании Dubbo стратегия повторных попыток должна быть осторожной, чтобы избежать повторных попыток для некоторых служб, которые не поддерживают идемпотентность. Если ваш провайдер не поддерживает идемпотентность, лучше всего настроить количество повторных попыток на 0
Механизм повторных попыток Feign (v11.1)
Feign — это Http-клиент, использующий простую Java и рекомендованную инфраструктуру RPC в Spring Cloud. Хотя Feign также является Http-клиентом, он сильно отличается от таких библиотек, как Apache HC.
На приведенной ниже схеме структуры ядра Feign, как видно из рисунка, клиентская часть Feign, помимо поддержки встроенного в JDK Http Client, также поддерживает Apache HC, Google Http, OK Http и другие библиотеки Http.И в нем также упоминается абстракция кодировщиков/декодеров... Итак, кажется, что это не базовый клиент Http, должен ли он называться «инструментом Http»? Или назвать это базовой абстракцией RPC?
Итак, какова стратегия повторных попыток в Feign? На этот вопрос действительно сложно ответить, потому что есть много ситуаций, которые нужно различать, и стратегии повторных попыток различны для разных клиентов Feign.
Во-первых, Feign имеет встроенную стратегию повторных попыток Как показано на рисунке ниже, повторная попытка Feign не связана с вызовом HttpClient, и перед каждой повторной попыткой существует определенный интервал.
В конфигурации по умолчанию максимальное количество повторных попыток (включая первый раз) составляет 5 раз, и перед каждой повторной попыткой будет определенный интервал времени (сон), и этот интервал времени увеличивается по мере увеличения количества повторных попыток, а количество повторных попыток интервал рассчитывается по формуле:
Как показано на рисунке ниже, чем больше количество повторных попыток, тем больше будет интервал между повторными попытками.
Но это повторная попытка вне вызова HttpClient, если вы просто используете встроенный по умолчанию HTTP-клиент Feign JDK, проблем не возникнет, поскольку HTTP-клиент JDK очень прост и не имеет механизма повторных попыток.Достаточно только механизма повторных попыток Feign.
Однако при работе со сторонним Http-клиентом (например, Apache HC) ситуация иная, поскольку сторонний Http-клиент часто имеет внутри механизм повторных попыток.
Если трехсторонний HttpClient повторяет попытку, а Feign повторяет попытку снова, это эквивалентно двум уровням повторных попыток, и количество повторных попыток становится равным N * N.
Например, в Apache HC, согласно предыдущему введению, количество повторных попыток по умолчанию составляет 3 раза, а Feign — 5 попыток по умолчанию, поэтому в худшем случае количество повторных попыток достигает 15 раз.
И это только базовый механизм повторных попыток Feign. Если он используется с балансировщиком нагрузки, таким как Ribbon в Spring Cloud, ситуация будет сложнее. В этой статье не будет слишком многого. Если вам интересно, взгляните на Spring Cloud Конфигурация Feign
Суммировать
Хотя повторная попытка выглядит просто, есть много факторов, которые следует учитывать, если вы хотите безопасно и стабильно повторить попытку. Необходимо объединить текущий бизнес-сценарий и контекстную информацию, чтобы всесторонне рассмотреть вопрос о том, следует ли предпринимать повторные попытки, и количество повторных попыток каждый раз; вместо того, чтобы устанавливать механизм повторных попыток одним махом, насильственные повторные попытки часто только усугубляют проблему и приводят к новым последствиям. серьезные проблемы.в результате.
Если вы не уверены, что повторная попытка безопасна, не предпринимайте повторных попыток, отключите повторные попытки для этих платформ, а Failfast лучше, чем масштабирование проблемы.
Ссылаться на
- Как изящно повторить попытку — InfoQ
- Apache Dubbo - Github
- Feign - Github
- Apache HttpClient - Github
Нелегко быть оригинальным, и несанкционированная перепечатка запрещена. Если моя статья полезна для вас, пожалуйста, поставьте лайк/добавьте в избранное/подпишитесь, чтобы поддержать и поддержать ее ❤❤❤❤❤❤