HTTP-вызовы: учитывали ли вы таймауты, повторные попытки и параллелизм?
HTTP-вызовы: учитывали ли вы таймауты, повторные попытки и параллелизм?

image-20230625144625218

Сегодня давайте поговорим о тайм-ауте, повторных попытках, параллелизме и других проблемах, на которые необходимо обращать внимание при совершении HTTP-вызовов.

В отличие от выполнения собственного метода, выполнение HTTP-вызова по сути является сетевым запросом через протокол HTTP. Сетевые запросы должны иметь возможность таймаута, поэтому мы должны учитывать эти три момента:

  • первый,Разумны ли настройки платформы и тайм-аут по умолчанию?
  • Во-вторых,Учитывая, что сеть нестабильна,Повторный запрос после таймаута — хороший выбор.,Но нам нужно подумать, позволяет ли идемпотентный дизайн интерфейса повторить попытку;
  • наконец,Вам необходимо подумать, будет ли фреймворк ограничивать количество одновременных подключений, как это делает браузер.,kFree существует, услуга одновременна, в лучшем случае,HTTP Число вызововодновременно ограничено узким местом.

Spring Cloud — это типичная среда для микросервисной архитектуры Java. Если вы используете Spring Cloud для разработки микросервисов, вы будете использовать Feign для декларативного вызова сервисов. Если вы не используете Spring Cloud, а напрямую используете Spring Boot для разработки микросервисов, вы можете напрямую использовать Apache HttpClient, наиболее часто используемый HTTP-клиент в Java, для выполнения сервисных вызовов.

Далее давайте рассмотрим подводные камни, связанные с тайм-аутом, повторными попытками и параллелизмом, с которыми можно столкнуться при использовании Feign и Apache HttpClient для выполнения вызовов интерфейса HTTP.

1. Знание настройки таймаута соединения и параметров таймаута чтения.

Для вызовов HTTP, хотя уровень приложений использует протокол HTTP, сетевой уровень всегда использует протокол TCP/IP. TCP/IP — это протокол, ориентированный на соединение, который требует установления соединения перед передачей данных. Почти все сетевые платформы предоставляют эти два параметра таймаута:

  • Параметры таймаута соединения ConnectTimeout,Разрешить пользователям ждать максимально долгое время на этапе установления соединения;
  • Чтение параметра таймаута ReadTimeout,используется для управления из Socket Максимальное время ожидания чтения данных.

Эти два параметра кажутся параметрами конфигурации в нижней части сетевого уровня, и их недостаточно, чтобы привлечь внимание студентов-разработчиков. Однако правильное понимание и настройка этих двух параметров особенно важно для бизнес-приложений. В конце концов, время ожидания не является односторонним вопросом. Клиент и сервер должны иметь согласованную оценку времени ожидания и работать вместе, чтобы сбалансировать пропускную способность и частоту ошибок. .

Есть два недоразумения относительно параметров таймаута соединения и таймаута соединения:

  1. Тайм-аут соединения настроен на очень большое значение, например 60 секунд.Вообще говоря,TCP Трехстороннее рукопожатие требует очень короткого времени для установления соединения. Обычно оно занимает максимум от миллисекунд до секунд. Это может занять не более десяти секунд или даже десятков секунд. Если соединение не может быть установлено в течение длительного времени. скорее всего проблема в сети или брандмауэре. В этом случае, если вы не сможете подключиться в течение нескольких секунд, возможно, вы никогда не сможете подключиться. Поэтому нет особого смысла устанавливать особенно большой таймаут соединения, сделайте его короче (например, 1~5 секунды). Если да - это чисто интранетвызовиз,Этот параметр можно установить короче,существуют, когда нисходящая служба находится в автономном режиме и не может быть подключена из-за,Может быстро выйти из строя.
  2. Устранение неполадок, связанных с тайм-аутом соединения,Но я не понимал, где связь.Обычно,Наш сервис будет иметь несколько узлов,Если вы не изклиент подключаете Сервер через технологию балансировки клиентской нагрузки,Затем клиент Сервер установит соединение напрямую.,В это время велика вероятность таймаута соединения.да Серверизвопрос;而если Сервер Что-то вроде Nginx Обратный прокси-сервер для балансировки нагрузки, подключение клиента на самом деле да Nginx, а не да Сервер, если в это время происходит таймаут соединения, вам следует устранить неполадку Nginx。

Есть еще много недоразумений относительно параметров тайм-аута чтения и тайм-аута чтения. Я суммирую их в следующие три.

Первое недоразумение:распознаватьдля Произошел тайм-аут чтения,Выполнение сервериза будет прервано.

Давайте проведем простой тест. Определите клиентский интерфейс и выполните внутренний вызов сервера интерфейса через HttpClient. Тайм-аут чтения клиента составляет 2 секунды, а выполнение интерфейса сервера занимает 5 секунд.

Язык кода:javascript
копировать
@RestController
@RequestMapping("clientreadtimeout")
@Slf4j
public class ClientReadTimeoutController {

    private String getResponse(String url, int connectTimeout, int readTimeout) throws IOException {
        return Request.Get("http://localhost:45678/clientreadtimeout" + url)
                .connectTimeout(connectTimeout)
                .socketTimeout(readTimeout)
                .execute()
                .returnContent()
                .asString();
    }

    
    @GetMapping("client")
    public String client() throws IOException {
        log.info("client1 called");
        //Таймаут сервера5s, таймаут чтения клиента 2 секунды
        return getResponse("/server?timeout=5000", 1000, 2000);
    }

    @GetMapping("server")
    public void server(@RequestParam("timeout") int timeout) throws InterruptedException {

        log.info("server called");
        TimeUnit.MILLISECONDS.sleep(timeout);
        log.info("Done");
    }

}

После вызова клиентского интерфейса из журнала видно, что на клиенте через 2 секунды возникло исключение SocketTimeoutException из-за таймаута чтения, но сервер вообще не пострадал, и выполнение завершилось через 3 секунды.

Язык кода:javascript
копировать
[11:35:11.943] [http-nio-45678-exec-1] [INFO ] [.t.c.c.d.ClientReadTimeoutController:29  ] - client1 called
[11:35:12.032] [http-nio-45678-exec-2] [INFO ] [.t.c.c.d.ClientReadTimeoutController:36  ] - server called
[11:35:14.042] [http-nio-45678-exec-1] [ERROR] [.a.c.c.C.[.[.[/].[dispatcherServlet]:175 ] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
java.net.SocketTimeoutException: Read timed out
  at java.net.SocketInputStream.socketRead0(Native Method)
  ...

[11:35:17.036] [http-nio-45678-exec-2] [INFO ] [.t.c.c.d.ClientReadTimeoutController:38  ] - Done

Мы знаем, что веб-серверы, такие как Tomcat, отправляют запросы на стороне сервера в пул потоков для обработки. Пока сервер получает запрос, тайм-ауты и отключения на уровне сети не влияют на работу сервера. Таким образом, при возникновении тайм-аута чтения вы не можете произвольно предполагать ситуацию обработки на сервере. Вам необходимо подумать о том, как выполнить последующую обработку, исходя из бизнес-состояния.

Второе недоразумение:распознаватьдля Только время ожидания чтенияда Socket Концепция сетевого уровня предполагает максимальное время передачи данных, поэтому оно настроено как очень короткое, например 100 миллисекунда.

Фактически, когда происходит таймаут чтения, сетевой уровень не может отличить, то ли сервер не вернул данные клиенту, то ли данные находились в сети долгое время или пакеты были потеряны.

Однако, поскольку TCP сначала устанавливает соединение, а затем передает данные, для вызовов службы, когда состояние сети не особенно плохое, обычно можно считать, что тайм-аут соединения является проблемой сети или служба не находится в сети, в то время как тайм-аут чтения тайм-аут обработки услуги. Если быть точным, тайм-аут чтения относится к времени ожидания, пока сокет не вернет данные после записи данных в сокет. Время, включенное в него, или большую часть времени, — это время, в течение которого сервер обрабатывает бизнес-логику. .

Третье недоразумение:распознаватьдля Чем дольше тайм-аут, тем выше вероятность успеха интерфейса задачи.,Воля Чтение параметра таймаута Конфигурация слишком длинная.

Выполнение HTTP-запросов обычно требует получения результатов и является синхронным вызовом. Если тайм-аут длинный, во время ожидания возврата данных сервером клиентский поток (обычно поток Tomcat) также ожидает. Когда в нижестоящих службах происходит большое количество тайм-аутов, программа может быть перетянута вниз, чтобы создать большое их количество. потоков и в конечном итоге происходит сбой.

Для запланированных задач или асинхронных задач не составит большого труда настроить больший тайм-аут чтения. Однако запросы на ответы пользователей или короткие и быстрые синхронные вызовы интерфейса микросервисов обычно имеют большой объем параллелизма. Нам следует установить более короткий тайм-аут чтения, чтобы предотвратить замедление со стороны нижестоящих служб. Обычно оно не превышает 30 секунд. Чтение тайм-аута.

Вы можете сказать, что если таймаут чтения установлен на 2 секунды, а интерфейс сервера занимает 3 секунды, не будет ли это означать, что мы никогда не получим результат выполнения? Это действительно так, поэтому установка тайм-аута чтения должна основываться на реальной ситуации. Если он слишком велик, джиттер в нисходящем направлении может повлиять на себя, а если он слишком мал, это может повлиять на вероятность успеха. Даже иногда нам приходится устанавливать разные таймауты чтения клиента для разных серверных интерфейсов в зависимости от соглашения об уровне обслуживания нижестоящего сервиса.

2. Feign и Ribbon используются вместе. Знаете ли вы, как настроить таймаут?

Я только что подчеркнул важность настройки тайм-аута соединения и тайм-аута чтения в соответствии с вашими потребностями. Пробовали ли вы настроить параметры тайм-аута для Spring Cloud Feign? Вас смутила различная информация в Интернете?

На мой взгляд, сложность настройки параметров таймаута для Feign заключается в том, что сам Feign имеет два параметра таймаута, а используемый им компонент балансировки нагрузки Ribbon также имеет соответствующие конфигурации. Итак, каков приоритет этих конфигураций и каковы подводные камни? Далее, давайте проведем несколько экспериментов.

Чтобы проверить таймаут сервера, предположим, что есть такой интерфейс сервера, который ничего не делает, а спит в течение 10 минут:

Язык кода:javascript
копировать
@PostMapping("/server")
public void server() throws InterruptedException {
    //Поспать 10 минут
    TimeUnit.MINUTES.sleep(10);
}

Сначала определите Feign для вызова этого интерфейса:

Язык кода:javascript
копировать
@FeignClient(name = "clientsdk")
public interface Client {
    @PostMapping("/feignandribbon/server")
    void server();
}

Затем выполните вызов интерфейса через Feign Client:

Язык кода:javascript
копировать
@GetMapping("client")
public void timeout() {

    long begin=System.currentTimeMillis();
    try{
        client.server();
    }catch (Exception ex){
        log.warn("Время выполнения: {}мс ошибка:{}", System.currentTimeMillis() - begin, ex.getMessage());
    }
}

Когда в файле конфигурации указан только адрес сервера:

Язык кода:javascript
копировать
clientsdk.ribbon.listOfServers=localhost:45678

Результат следующий:

Язык кода:javascript
копировать
[15:40:16.094] [http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] - Время выполнения: 1007 мс Ошибка: прочитать timed out executing POST http://clientsdk/feignandribbon/server

из этого вывода,Мы можем сделать вывод 1,По умолчанию Притвориться Тайм-аут чтения составляет 1 секунду, такой короткий таймаут чтения — это ловушка.

Давайте проанализируем исходный код. Открыть RibbonClientConfiguration После урока вы увидите DefaultClientConfigImpl После создания,ReadTimeout и ConnectTimeout установлено на 1 s:

Язык кода:javascript
копировать
**
 * Ribbon client default connect timeout.
 */

public static final int DEFAULT_CONNECT_TIMEOUT = 1000;

/**
 * Ribbon client default read timeout.
 */
public static final int DEFAULT_READ_TIMEOUT = 1000;

@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {

   DefaultClientConfigImpl config = new DefaultClientConfigImpl();
   config.loadProperties(this.name);
   config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
   config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
   config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);

   return config;
}

Если вы хотите изменить Feign Для клиента есть два глобальных таймаута по умолчанию, которые вы можете установить. feign.client.config.default.readTimeout и feign.client.config.default.connectTimeout параметр:

Язык кода:javascript
копировать
feign.client.config.default.readTimeout=3000
feign.client.config.default.connectTimeout=3000

Видно, что 3-секундный таймаут чтения вступает в силу. Примечание. Здесь есть большая ошибка. Если вы хотите изменить только таймаут чтения, вы можете настроить только эту строку:

Язык кода:javascript
копировать
feign.client.config.default.readTimeout=3000

Проверьте это, и вы обнаружите, что такая конфигурация не сможет действовать!

Вывод 2,Также подводные камни 2,Если вы хотите настроить Feign из Тайм-аут чтения,Он должен быть подключен одновременно Конфигурация таймаута соединения,вступить в силу

Открыть FeignClientFactoryBean Как видите, только одновременные настройки ConnectTimeout и ReadTimeout,Request.Options будет покрыто:

Язык кода:javascript
копировать
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
   builder.options(new Request.Options(config.getConnectTimeout(),
         config.getReadTimeout()));
}

Иди дальше,если你希望针对单独из Feign Client Чтобы установить период ожидания, вы можете default Заменить на Client из name

Язык кода:javascript
копировать
feign.client.config.default.readTimeout=3000
feign.client.config.default.connectTimeout=3000
feign.client.config.clientsdk.readTimeout=2000
feign.client.config.clientsdk.connectTimeout=2000

Можетк Приходите вВывод третий,Отдельные таймауты могут переопределять глобальные таймауты.,Это как и ожидалось,Это не ловушка:

Язык кода:javascript
копировать
[15:45:51.708] [http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] - Время выполнения: 2006 мс Ошибка: прочитать timed out executing POST http://clientsdk/feignandribbon/server

Вывод четвертый: помимо возможности настройки Притвориться, также можно настроить Ribbon Компонент параметров для изменения двух таймаутов. Подводного камня здесь три, первая буква параметра должна быть заглавной, и Feign из Конфигурация другая

Язык кода:javascript
копировать
ribbon.ReadTimeout=4000
ribbon.ConnectTimeout=4000

Вступление параметров в силу можно подтвердить с помощью журналов:

Язык кода:javascript
копировать
[15:55:18.019] [http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] - Время выполнения: 4003 мс Ошибка: прочитать timed out executing POST http://clientsdk/feignandribbon/server

Наконец, давайте посмотрим на одновременную настройку Feign и Ribbon изпараметр,Кто вступит в силу в конечном итоге??Следующий кодизпараметр Конфигурация:

Язык кода:javascript
копировать
clientsdk.ribbon.listOfServers=localhost:45678
feign.client.config.default.readTimeout=3000
feign.client.config.default.connectTimeout=3000
ribbon.ReadTimeout=4000
ribbon.ConnectTimeout=4000

Вывод журнала доказывает, что он наконец вступил в силуизда Feign изTimeout:

Язык кода:javascript
копировать
[16:01:19.972] [http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] - Время выполнения: 3006 мс Ошибка: прочитать timed out executing POST http://clientsdk/feignandribbon/server

Вывод пятый, настраиваем заодно Feign и Ribbon изтаймаут, к Feign дляпозволять。Это немного нелогично,Поскольку Ribbon находится на более низком уровне, вы можете подумать, что последняя настройка будет эффективной.,Но это не так.

существовать LoadBalancerFeignClient Вы можете увидеть это в исходном коде, если Request.Options не является значением по умолчанию, a FeignOptionsClientConfig заменить оригинал Ribbon из DefaultClientConfigImpl, в результате чего Ribbon из Конфигурация Feign крышка:

Язык кода:javascript
копировать
IClientConfig getClientConfig(Request.Options options, String clientName) {
   IClientConfig requestConfig;
   if (options == DEFAULT_OPTIONS) {
      requestConfig = this.clientFactory.getClientConfig(clientName);
   } else {
      requestConfig = new FeignOptionsClientConfig(options);
   }
   return requestConfig;
}

Но если так Конфигурация наконец вступает в силу извсе еще Ribbon изTimeout (4 секунд), людям легко производить Ribbon покрытый Feign иллюзия, на самом деле это все еще вызвано для ямы два, отдельно Конфигурация Feign Таймаут чтения не вступает в силу:

Язык кода:javascript
копировать
clientsdk.ribbon.listOfServers=localhost:45678
feign.client.config.default.readTimeout=3000
feign.client.config.clientsdk.readTimeout=2000
ribbon.ReadTimeout=4000

3. Риббон ​​автоматически повторит запрос, вы понимаете?

HTTP-клиент «Некоторый», как правило, имеет встроенную стратегию повторных попыток.,Его первоначальное намерение хорошее,В конце концов, потеря пакетов из-за проблем с сетью происходит часто, но кратковременно.,Часто вы можете добиться успеха, попробовав еще раз во второй раз.,Но мы должны быть осторожны, соответствует ли такое самоутверждение нашим ожиданиям.

Я уже встречал дублированное текстовое сообщениеотправлятьизвопрос,Но SMS-сервис извызов абонентской службы,Дважды проверьте, что в коде нет логики повтора. Так откуда же взялась проблема? Давайте воспроизведем этот случай.

Сначала определите Get Запрос на отправку СМС через интерфейс, логики в нем нет, спать 2 Второе время моделирования:

Язык кода:javascript
копировать
@RestController
@RequestMapping("ribbonretryissueserver")
@Slf4j
public class RibbonRetryIssueServerController {

    @GetMapping("sms")
    public void sendSmsWrong(@RequestParam("mobile") String mobile, @RequestParam("message") String message, HttpServletRequest request) throws InterruptedException {
        //Засыпаем на 2 секунды после вывода параметров вызова.
        log.info("{} is called, {}=>{}", request.getRequestURL().toString(), mobile, message);
        TimeUnit.SECONDS.sleep(2);
    }
}

Настройте Feign для вызова клиента:

Язык кода:javascript
копировать
@FeignClient(name = "SmsClient")
public interface SmsClient {
    @GetMapping("/ribbonretryissueserver/sms")
    void sendSmsWrong(@RequestParam("mobile") String mobile, @RequestParam("message") String message);
}

Компонент Feign Есть один внутри Ribbon отвечает за балансировку клиентской нагрузки, устанавливая свой вызов Сервердля двух узлов через файл Конфигурации:

Язык кода:javascript
копировать
SmsClient.ribbon.listOfServers=localhost:45679,localhost:45678

Напишите клиентский интерфейс и вызовите сервер через Feign:

Язык кода:javascript
копировать
@RestController
@RequestMapping("ribbonretryissueclient")
@Slf4j
public class RibbonRetryIssueClientController {
    @Autowired
    private SmsClient smsClient;
    @GetMapping("wrong")
    public String wrong() {
        log.info("client is called");
        try{
            //Отправка SMS через интерфейс Feignвызов
            smsClient.sendSmsWrong("13600000000", UUID.randomUUID().toString());
        } catch (Exception ex) {
            //Фиксируем возможные сетевые ошибки
            log.error("send sms failed : {}", ex.getMessage());
        }
        return "done";
    }
}

существовать 45678 и 45679 Запустите сервер на двух портах соответственно, а затем получите доступ 45678 изклиент интерфейса руководил тестом. Поскольку дляклиенти Сервер существует в качестве контроллера одного приложения, поэтому к 45678 Также играл роль клиенти Сервериз.

существовать 45678 Как видно из лога, 29 Через несколько секунд клиент получает запрос и начинает вызывать интерфейс сервера для отправки текстовых сообщений. В то же время сервер получает запрос 2. 秒后(Обратите внимание на сравнение первого логаи Третий журнал)клиент输出了Тайм-аут чтенияизсообщение об ошибке:

Язык кода:javascript
копировать
[12:49:29.020] [http-nio-45678-exec-4] [INFO ] [c.d.RibbonRetryIssueClientController:23  ] - client is called
[12:49:29.026] [http-nio-45678-exec-5] [INFO ] [c.d.RibbonRetryIssueServerController:16  ] - http://localhost:45678/ribbonretryissueserver/sms is called, 13600000000=>a2aa1b32-a044-40e9-8950-7f0189582418
[12:49:31.029] [http-nio-45678-exec-4] [ERROR] [c.d.RibbonRetryIssueClientController:27  ] - send sms failed : Read timed out executing GET http://SmsClient/ribbonretryissueserver/sms?mobile=13600000000&message=a2aa1b32-a044-40e9-8950-7f0189582418

И существовать другой Сервер 45679 Вы также можете увидеть запрос в журнале, 30 Секунды, когда получен запрос, это вызов интерфейса даклиента. 1 Второй:

Язык кода:javascript
копировать
[12:49:30.029] [http-nio-45679-exec-2] [INFO ] [c.d.RibbonRetryIssueServerController:16  ] - http://localhost:45679/ribbonretryissueserver/sms is called, 13600000000=>a2aa1b32-a044-40e9-8950-7f0189582418

Клиентский интерфейс выводился в журнал вызовов только один раз, а журнал Сервериз выводился дважды. Хотя Feign по умолчанию тайм-аут чтения да 1 секунды, но клиент 2 Ошибка тайм-аута возникает через несколько секунд. очевидно,Это показываетКлиент повторил попытку по собственной инициативе, в результате чего текстовое сообщение было отправлено повторно.

Просмотреть Ribbon из Исходный код можно найти, MaxAutoRetriesNextServer Параметры по умолчанию 1, то есть Get Когда возникает проблема (например, тайм-аут чтения) при запросе определенного узла Сервер, Ribbon Он автоматически повторит попытку:

Язык кода:javascript
копировать
// DefaultClientConfigImpl
public static final int DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER = 1;
public static final int DEFAULT_MAX_AUTO_RETRIES = 0;

// RibbonLoadBalancedRetryPolicy
public boolean canRetry(LoadBalancedRetryContext context) {

   HttpMethod method = context.getRequest().getMethod();
   return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
}

@Override
public boolean canRetrySameServer(LoadBalancedRetryContext context) {

   return sameServerCount < lbContext.getRetryHandler().getMaxRetriesOnSameServer()
         && canRetry(context);

}

@Override
public boolean canRetryNextServer(LoadBalancedRetryContext context) {

   // this will be called after a failure occurs and we increment the counter
   // so we check that the count is less than or equals to too make sure
   // we try the next server the right number of times
   return nextServerCount <= lbContext.getRetryHandler().getMaxRetriesOnNextServer()
         && canRetry(context);
}

Есть два решения:

Сначала измените интерфейс обмена текстовыми сообщениями с Get Изменить на Почта. Вообще-то здесь есть еще один API Проблемы проектирования, статус из API Интерфейсы не следует определять как Получать. в соответствии с HTTP Протокол спецификации, Получить Запросы используются для запросов данных, а Post Только после этого данные можно будет отправить на сервер для изменения или дополнения. выбирать Get все еще Post из На основании того, что следует API из ХОРОШОдля,Вместодапараметр大小。Здесь недоразумение, Получите Запрос из параметра содержит существование Url QueryString , будет ограничено длиной браузера, поэтому к Некоторые учащиеся захотят использовать JSON к Post Чтобы отправить большие параметры, используйте Get Отправьте небольшие параметры.

Во-вторых, это будет MaxAutoRetriesNextServer Параметры настроены как 0, отключить сервисный вызов, автоматически повторить попытку следующего узла Сервера после сбоя. Просто добавьте строку в файл существующей Конфигурация:

Язык кода:javascript
копировать
ribbon.MaxAutoRetriesNextServer=0

Увидев это, вы думаете, что проблема существуетUser Serviceвсе еще А как насчет SMS-сервиса?

существовать Мне кажется,Проблемы есть с обеих сторон. Как я уже говорил,Get Запрос должен быть без сохранения состояния или идемпотентным, а интерфейс SMS может быть спроектирован для поддержки идемпотентных и пользовательских сервисов, если вы в этом заинтересованы; Ribbon Если вы понимаете механизм повторных попыток, вы сможете избежать обходных путей при устранении неполадок.

4. Параллелизм ограничивает возможности сканирования

Помимо тайм-аута и повторной попытки из бокса, руководить вызовом HTTP-запроса и общим вопросом,Производительность программы не может быть увеличена из-за ограничения данных.

Я уже сталкивался с краулерным проектом,Общая эффективность сканирования данных очень низкая.,Увеличение количества пулов потоков тоже не помогает.,Мы можем только накапливать больше машин для распределенных сканеров. Сейчас существуют,Давайте смоделируем этот сценарий,Посмотрите, в чем заключается проблема.

Предположим, вы хотите сканировать из-Серверда, например, такую ​​простую калибровку, спящий режим на 1 секунду, возвращающую число 1:

Язык кода:javascript
копировать
@GetMapping("server")
public int server() throws InterruptedException {
    TimeUnit.SECONDS.sleep(1);
    return 1;
}

Сканеру необходимо несколько раз просканировать этот интерфейс, чтобы гарантировать, что пул потоков не является даодновременно узким местом, мы используем пул без верхнего предела потока из newCachedThreadPool Создайте задачу сканирования и пул потоков (опять же, если вы не очень четко представляете свои потребности, обычно не используйте пул потоков без верхнего предела количества потоков), а затем используйте HttpClient выполнить HTTP Запросите, отправьте задачу запроса в пул потоков в цикле для обработки и, наконец, дождитесь завершения выполнения всех задач и выведите время выполнения:

Язык кода:javascript
копировать
private int sendRequest(int count, Supplier<CloseableHttpClient> client) throws InterruptedException {

    //Используется для подсчета количества отправленных запросов
    AtomicInteger atomicInteger = new AtomicInteger();

    //Используем HttpClient для запроса данных из интерфейса сервера и отправки задачи в пул потоков для параллельной обработки
    ExecutorService threadPool = Executors.newCachedThreadPool();

    long begin = System.currentTimeMillis();
    IntStream.rangeClosed(1, count).forEach(i -> {
        threadPool.execute(() -> {
            try (CloseableHttpResponse response = client.get().execute(new HttpGet("http://127.0.0.1:45678/routelimit/server"))) {
                atomicInteger.addAndGet(Integer.parseInt(EntityUtils.toString(response.getEntity())));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        });
    });

    //Подождем, пока все задачи подсчета будут выполнены
    threadPool.shutdown();
    threadPool.awaitTermination(1, TimeUnit.HOURS);
    log.info("Отправить {} запросы, отнимающие время {} ms", atomicInteger.get(), System.currentTimeMillis() - begin);
    return atomicInteger.get();
}

Сначала используйте значение по умолчанию PoolingHttpClientConnectionManager Структура из CloseableHttpClient, тестовое сканирование 10 Затраченное время за раз:

Язык кода:javascript
копировать
static CloseableHttpClient httpClient1;

static {
    httpClient1 = HttpClients.custom().setConnectionManager(new PoolingHttpClientConnectionManager()).build();
}

@GetMapping("wrong")
public int wrong(@RequestParam(value = "count", defaultValue = "10") int count) throws InterruptedException {
    return sendRequest(count, () -> httpClient1);
}

Хотя запрос требует 1 Выполнение завершается за секунды, но наш пул потоков можно расширить, чтобы использовать любое количество потоков. Логично говоря, 10 Время обработки каждого запроса в основном эквивалентно 1 время обработки запроса, то есть да 1 секунд, но журнал показывает фактическое время, затраченное 5 Второй:

Язык кода:javascript
копировать
[12:48:48.122] [http-nio-45678-exec-1] [INFO ] [o.g.t.c.h.r.RouteLimitController        :54  ] - отправлять 10 запросы, отнимающие время 5265 ms

Проверять исходный код PoolingHttpClientConnectionManager можно отметить, что есть две важные вещи:

  • defaultMaxPerRoute=2,То естьда同一个主机 / Максимальное количество запросов на доменное имя изодновременнодля 2. Наши потребности в сканере 10 В то же время очевидно, что значение да по умолчанию слишком мало, что ограничивает эффективность сканера.
  • maxTotal=20,То есть общее максимальное количество всех хостов равно 20., это тоже да HttpClient Общая степень изодновременности. В настоящее время мы запрашиваем номер да 10 最大одновременнода 10,20 Это не станет узким местом. Чтобы привести пример, используйте тот же HttpClient доступ 10 доменные имена, defaultMaxPerRoute Настройки 10. для гарантирует, что каждое доменное имя может достичь 10 одновременно, нужно поставить maxTotal Настройки 100。
Язык кода:javascript
копировать
public PoolingHttpClientConnectionManager(

    final HttpClientConnectionOperator httpClientConnectionOperator,
    final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
    final long timeToLive, final TimeUnit timeUnit) {

    ...    
    this.pool = new CPool(new InternalConnectionFactory(
            this.configData, connFactory), 2, 20, timeToLive, timeUnit);
   ...
} 

public CPool(

        final ConnFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
        final int defaultMaxPerRoute, final int maxTotal,
        final long timeToLive, final TimeUnit timeUnit) {

    ...

}}

HttpClient да Java Очень часто используется HTTP Клиент, эта проблема возникает часто. Вы можете спросить, почему значение по умолчанию настолько ограничено.

На самом деле, это не совсем вина HttpClient, многие ранние браузеры также ограничивали два одновременных запроса на одно и то же доменное имя. для одного и того же доменного имени одновременно подключение из-за ограничения, фактически да HTTP 1.1 Соглашение требует из, вот отрывок:

Язык кода:javascript
копировать
Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.

HTTP 1.1 Соглашение да 20 Создан много лет назад, сейчас существует HTTP Сервер гораздо более функционален, поэтому некоторые новые браузеры не полностью ему соответствуют. 2 Ограничение параллелизма, отпустите счетчик параллелизма 8 Еще больше. Если вам нужно пройти HTTP клиент инициирует большое количество одновременных запросов,независимо от того, какой клиент использовал,Обязательно подтвердите, соответствует ли клиентизациявыполненияизодновременнода по умолчанию вашим потребностям.

Теперь, когда мы знаем, что проблема существует, давайте попробуем заявить о новом HttpClient Снимите соответствующие ограничения и установите maxPerRoute для 50、maxTotal для 100, а затем измените его прямо сейчас из wrong Метод с использованием нового теста изклиентруководить:

Язык кода:javascript
копировать
httpClient2 = HttpClients.custom().setMaxConnPerRoute(10).setMaxConnTotal(20).build();

Вывод следующий: 10 次проситьсуществовать 1 Выполнение завершается примерно за секунды. Но я увидел это, потому что для того, чтобы отпустить одного Host 2 С ограничением по умолчанию, равным одновременноиз, эффективность сканера была значительно улучшена:

Язык кода:javascript
копировать
отправлять 10 запросы, отнимающие время 1023 ms

5. Резюме

Сегодня я поделился с вами наиболее распространенными проблемами, возникающими при превышении таймаута HTTP-вызова и повторной попытке иодновременно.

Таймаут соединения означает установление TCP Время соединения и тайм-аут чтения представляют собой время ожидания возврата данных с удаленной стороны, включая время обработки удаленной программы. При решении проблемы тайм-аута соединения нам необходимо выяснить, с кем установлено соединение; при возникновении проблемы тайм-аута чтения мы должны всесторонне учитывать стандарты нисходящего обслуживания и наши собственные стандарты обслуживания и установить соответствующий тайм-аут чтения. Кроме того, существование использует что-то вроде Spring Cloud Feign Обязательно подтвердите, ожидая подключения кадра и Чтение параметра таймаутаиз Конфигурация действительна правильно.

для Попробуйте еще раз,потому чтодля HTTP Признание соглашения для Get Запросите операцию запроса данных, которая не имеет состояния, и учитывая, что потеря пакетов является обычным явлением в сети, есть некоторые HTTP Клиент или прокси-сервер автоматически повторит попытку. Get/Head просить. Если ваш дизайн интерфейса не поддерживает идемпотентность, вам необходимо отключить автоматический повтор. Но, лучшее решение, следуйте HTTP Протокол из рекомендуется использовать надлежащим образом из HTTP метод.

Наконец мы видим, в том числе HttpClient существовать Внутрииз HTTP клиент и браузер,Будет ограничено максимальное количество клиентовизодновременно. Если ваш клиент имеет больший запрос, вызовите одновременный вызов.,Например, изготовление рептилии,Или да играет аналогичную роль агента,Или сама программа да одновременно выше,Такое маленькое значение по умолчанию может легко стать узким местом пропускной способности.,Надо вовремя корректировать.

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose