image-20230625144625218
Сегодня давайте поговорим о тайм-ауте, повторных попытках, параллелизме и других проблемах, на которые необходимо обращать внимание при совершении HTTP-вызовов.
В отличие от выполнения собственного метода, выполнение HTTP-вызова по сути является сетевым запросом через протокол HTTP. Сетевые запросы должны иметь возможность таймаута, поэтому мы должны учитывать эти три момента:
Spring Cloud — это типичная среда для микросервисной архитектуры Java. Если вы используете Spring Cloud для разработки микросервисов, вы будете использовать Feign для декларативного вызова сервисов. Если вы не используете Spring Cloud, а напрямую используете Spring Boot для разработки микросервисов, вы можете напрямую использовать Apache HttpClient, наиболее часто используемый HTTP-клиент в Java, для выполнения сервисных вызовов.
Далее давайте рассмотрим подводные камни, связанные с тайм-аутом, повторными попытками и параллелизмом, с которыми можно столкнуться при использовании Feign и Apache HttpClient для выполнения вызовов интерфейса HTTP.
Для вызовов HTTP, хотя уровень приложений использует протокол HTTP, сетевой уровень всегда использует протокол TCP/IP. TCP/IP — это протокол, ориентированный на соединение, который требует установления соединения перед передачей данных. Почти все сетевые платформы предоставляют эти два параметра таймаута:
ConnectTimeout
,Разрешить пользователям ждать максимально долгое время на этапе установления соединения; ReadTimeout
,используется для управления из Socket Максимальное время ожидания чтения данных.Эти два параметра кажутся параметрами конфигурации в нижней части сетевого уровня, и их недостаточно, чтобы привлечь внимание студентов-разработчиков. Однако правильное понимание и настройка этих двух параметров особенно важно для бизнес-приложений. В конце концов, время ожидания не является односторонним вопросом. Клиент и сервер должны иметь согласованную оценку времени ожидания и работать вместе, чтобы сбалансировать пропускную способность и частоту ошибок. .
Есть два недоразумения относительно параметров таймаута соединения и таймаута соединения:
Есть еще много недоразумений относительно параметров тайм-аута чтения и тайм-аута чтения. Я суммирую их в следующие три.
Первое недоразумение:распознаватьдля Произошел тайм-аут чтения,Выполнение сервериза будет прервано.
Давайте проведем простой тест. Определите клиентский интерфейс и выполните внутренний вызов сервера интерфейса через HttpClient. Тайм-аут чтения клиента составляет 2 секунды, а выполнение интерфейса сервера занимает 5 секунд.
@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 секунды.
[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 секунды, не будет ли это означать, что мы никогда не получим результат выполнения? Это действительно так, поэтому установка тайм-аута чтения должна основываться на реальной ситуации. Если он слишком велик, джиттер в нисходящем направлении может повлиять на себя, а если он слишком мал, это может повлиять на вероятность успеха. Даже иногда нам приходится устанавливать разные таймауты чтения клиента для разных серверных интерфейсов в зависимости от соглашения об уровне обслуживания нижестоящего сервиса.
Я только что подчеркнул важность настройки тайм-аута соединения и тайм-аута чтения в соответствии с вашими потребностями. Пробовали ли вы настроить параметры тайм-аута для Spring Cloud Feign? Вас смутила различная информация в Интернете?
На мой взгляд, сложность настройки параметров таймаута для Feign заключается в том, что сам Feign имеет два параметра таймаута, а используемый им компонент балансировки нагрузки Ribbon также имеет соответствующие конфигурации. Итак, каков приоритет этих конфигураций и каковы подводные камни? Далее, давайте проведем несколько экспериментов.
Чтобы проверить таймаут сервера, предположим, что есть такой интерфейс сервера, который ничего не делает, а спит в течение 10 минут:
@PostMapping("/server")
public void server() throws InterruptedException {
//Поспать 10 минут
TimeUnit.MINUTES.sleep(10);
}
Сначала определите Feign для вызова этого интерфейса:
@FeignClient(name = "clientsdk")
public interface Client {
@PostMapping("/feignandribbon/server")
void server();
}
Затем выполните вызов интерфейса через Feign Client:
@GetMapping("client")
public void timeout() {
long begin=System.currentTimeMillis();
try{
client.server();
}catch (Exception ex){
log.warn("Время выполнения: {}мс ошибка:{}", System.currentTimeMillis() - begin, ex.getMessage());
}
}
Когда в файле конфигурации указан только адрес сервера:
clientsdk.ribbon.listOfServers=localhost:45678
Результат следующий:
[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:
**
* 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
параметр:
feign.client.config.default.readTimeout=3000
feign.client.config.default.connectTimeout=3000
Видно, что 3-секундный таймаут чтения вступает в силу. Примечание. Здесь есть большая ошибка. Если вы хотите изменить только таймаут чтения, вы можете настроить только эту строку:
feign.client.config.default.readTimeout=3000
Проверьте это, и вы обнаружите, что такая конфигурация не сможет действовать!
Вывод 2,Также подводные камни 2,Если вы хотите настроить Feign из Тайм-аут чтения,Он должен быть подключен одновременно Конфигурация таймаута соединения,вступить в силу。
Открыть FeignClientFactoryBean
Как видите, только одновременные настройки ConnectTimeout
и ReadTimeout
,Request.Options
будет покрыто:
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Request.Options(config.getConnectTimeout(),
config.getReadTimeout()));
}
Иди дальше,если你希望针对单独из Feign Client Чтобы установить период ожидания, вы можете default Заменить на Client из name:
feign.client.config.default.readTimeout=3000
feign.client.config.default.connectTimeout=3000
feign.client.config.clientsdk.readTimeout=2000
feign.client.config.clientsdk.connectTimeout=2000
Можетк Приходите вВывод третий,Отдельные таймауты могут переопределять глобальные таймауты.,Это как и ожидалось,Это не ловушка:
[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 из Конфигурация другая。
ribbon.ReadTimeout=4000
ribbon.ConnectTimeout=4000
Вступление параметров в силу можно подтвердить с помощью журналов:
[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 изпараметр,Кто вступит в силу в конечном итоге??Следующий кодизпараметр Конфигурация:
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:
[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 крышка:
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 Таймаут чтения не вступает в силу:
clientsdk.ribbon.listOfServers=localhost:45678
feign.client.config.default.readTimeout=3000
feign.client.config.clientsdk.readTimeout=2000
ribbon.ReadTimeout=4000
HTTP-клиент «Некоторый», как правило, имеет встроенную стратегию повторных попыток.,Его первоначальное намерение хорошее,В конце концов, потеря пакетов из-за проблем с сетью происходит часто, но кратковременно.,Часто вы можете добиться успеха, попробовав еще раз во второй раз.,Но мы должны быть осторожны, соответствует ли такое самоутверждение нашим ожиданиям.
Я уже встречал дублированное текстовое сообщениеотправлятьизвопрос,Но SMS-сервис извызов абонентской службы,Дважды проверьте, что в коде нет логики повтора. Так откуда же взялась проблема? Давайте воспроизведем этот случай.
Сначала определите Get Запрос на отправку СМС через интерфейс, логики в нем нет, спать 2 Второе время моделирования:
@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 для вызова клиента:
@FeignClient(name = "SmsClient")
public interface SmsClient {
@GetMapping("/ribbonretryissueserver/sms")
void sendSmsWrong(@RequestParam("mobile") String mobile, @RequestParam("message") String message);
}
Компонент Feign Есть один внутри Ribbon отвечает за балансировку клиентской нагрузки, устанавливая свой вызов Сервердля двух узлов через файл Конфигурации:
SmsClient.ribbon.listOfServers=localhost:45679,localhost:45678
Напишите клиентский интерфейс и вызовите сервер через Feign:
@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. 秒后(Обратите внимание на сравнение первого логаи Третий журнал)клиент输出了Тайм-аут чтенияизсообщение об ошибке:
[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 Второй:
[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 Он автоматически повторит попытку:
// 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, отключить сервисный вызов, автоматически повторить попытку следующего узла Сервера после сбоя. Просто добавьте строку в файл существующей Конфигурация:
ribbon.MaxAutoRetriesNextServer=0
Увидев это, вы думаете, что проблема существуетUser Serviceвсе еще А как насчет SMS-сервиса?
существовать Мне кажется,Проблемы есть с обеих сторон. Как я уже говорил,Get Запрос должен быть без сохранения состояния или идемпотентным, а интерфейс SMS может быть спроектирован для поддержки идемпотентных и пользовательских сервисов, если вы в этом заинтересованы; Ribbon Если вы понимаете механизм повторных попыток, вы сможете избежать обходных путей при устранении неполадок.
Помимо тайм-аута и повторной попытки из бокса, руководить вызовом HTTP-запроса и общим вопросом,Производительность программы не может быть увеличена из-за ограничения данных.
Я уже сталкивался с краулерным проектом,Общая эффективность сканирования данных очень низкая.,Увеличение количества пулов потоков тоже не помогает.,Мы можем только накапливать больше машин для распределенных сканеров. Сейчас существуют,Давайте смоделируем этот сценарий,Посмотрите, в чем заключается проблема.
Предположим, вы хотите сканировать из-Серверда, например, такую простую калибровку, спящий режим на 1 секунду, возвращающую число 1:
@GetMapping("server")
public int server() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
return 1;
}
Сканеру необходимо несколько раз просканировать этот интерфейс, чтобы гарантировать, что пул потоков не является даодновременно узким местом, мы используем пул без верхнего предела потока из newCachedThreadPool Создайте задачу сканирования и пул потоков (опять же, если вы не очень четко представляете свои потребности, обычно не используйте пул потоков без верхнего предела количества потоков), а затем используйте HttpClient выполнить HTTP Запросите, отправьте задачу запроса в пул потоков в цикле для обработки и, наконец, дождитесь завершения выполнения всех задач и выведите время выполнения:
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 Затраченное время за раз:
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 Второй:
[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 В то же время очевидно, что значение да по умолчанию слишком мало, что ограничивает эффективность сканера.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 Соглашение требует из, вот отрывок:
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 Метод с использованием нового теста изклиентруководить:
httpClient2 = HttpClients.custom().setMaxConnPerRoute(10).setMaxConnTotal(20).build();
Вывод следующий: 10 次проситьсуществовать 1 Выполнение завершается примерно за секунды. Но я увидел это, потому что для того, чтобы отпустить одного Host 2 С ограничением по умолчанию, равным одновременноиз, эффективность сканера была значительно улучшена:
отправлять 10 запросы, отнимающие время 1023 ms
Сегодня я поделился с вами наиболее распространенными проблемами, возникающими при превышении таймаута HTTP-вызова и повторной попытке иодновременно.
Таймаут соединения означает установление TCP Время соединения и тайм-аут чтения представляют собой время ожидания возврата данных с удаленной стороны, включая время обработки удаленной программы. При решении проблемы тайм-аута соединения нам необходимо выяснить, с кем установлено соединение; при возникновении проблемы тайм-аута чтения мы должны всесторонне учитывать стандарты нисходящего обслуживания и наши собственные стандарты обслуживания и установить соответствующий тайм-аут чтения. Кроме того, существование использует что-то вроде Spring Cloud Feign Обязательно подтвердите, ожидая подключения кадра и Чтение параметра таймаутаиз Конфигурация действительна правильно.
для Попробуйте еще раз,потому чтодля HTTP Признание соглашения для Get Запросите операцию запроса данных, которая не имеет состояния, и учитывая, что потеря пакетов является обычным явлением в сети, есть некоторые HTTP Клиент или прокси-сервер автоматически повторит попытку. Get/Head просить. Если ваш дизайн интерфейса не поддерживает идемпотентность, вам необходимо отключить автоматический повтор. Но, лучшее решение, следуйте HTTP Протокол из рекомендуется использовать надлежащим образом из HTTP метод.
Наконец мы видим, в том числе HttpClient существовать Внутрииз HTTP клиент и браузер,Будет ограничено максимальное количество клиентовизодновременно. Если ваш клиент имеет больший запрос, вызовите одновременный вызов.,Например, изготовление рептилии,Или да играет аналогичную роль агента,Или сама программа да одновременно выше,Такое маленькое значение по умолчанию может легко стать узким местом пропускной способности.,Надо вовремя корректировать.