1. Зачем использовать пул HTTP-соединений
1. Уменьшить задержку: если пул соединений не используется, TCP-соединение будет переустанавливаться каждый раз, когда соединение инициирует HTTP-запрос (после 3 рукопожатий), и соединение будет закрыто (4 взмаха рук), когда оно будет израсходован.Если пул соединений используется, он будет уменьшен.Эта часть потери времени, не недооценивайте эти рукопожатия, я обнаружил в ходе тестирования, что временная задержка в основном в 3 раза
2. Поддержка большего параллелизма: если пул соединений не используется, порт будет открыт для каждого соединения.В случае большого параллелизма ресурсы портов системы будут быстро израсходованы, что приведет к невозможности установления новых соединений. .
2. Простой диспетчер соединений
BasicHttpClientConnectionManager
это простой менеджер соединений, который может управлять только одним соединением за раз. Хотя этот класс является потокобезопасным, он может использоваться только одним потоком одновременно.BasicHttpClientConnectionManager
Будет пытаться повторно использовать старое соединение для отправки последующих запросов и использовать тот же маршрут. Если маршрут для последующих запросов не совпадает с маршрутом в старом соединении,BasicHttpClientConnectionManager
Текущее соединение закрывается, и соединение восстанавливается с использованием маршрута в запросе. Выдает, если текущее соединение используетсяjava.lang.IllegalStateException
аномальный.
3. Диспетчер пула соединений
относительноBasicHttpClientConnectionManager
Сказать,PoolingHttpClientConnectionManager
это более сложный класс, который управляет пулом соединений, который может быть предоставлен для многих потоков одновременно.httpзапрос на подключение. Соединения объединяются в пул для каждого маршрута.Когда запрашивается новое соединение, если в пуле соединений есть постоянные соединения, диспетчер соединений будет использовать одно из них вместо создания нового.
PoolingHttpClientConnectionManager
Количество поддерживаемых соединений ограничено для каждой маршрутизации и в целом. По умолчанию количество подключений на маршрут не может превышать 2, а общее количество подключений не может превышать 20. В практических приложениях это ограничение может быть слишком маленьким, особенно когда сервер также использует протокол Http.
В следующем примере показано, как настроить параметры пула соединений:
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 将最大连接数增加到200
cm.setMaxTotal(200);
// 将每个路由基础的连接增加到20
cm.setDefaultMaxPerRoute(20);
//将目标主机的最大连接数增加到50
HttpHost localhost = new HttpHost("www.yeetrack.com", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
4. Закройте диспетчер соединений.
Когда экземпляр HttpClient не используется или вышел за его пределы, нам нужно закрыть его диспетчер соединений, чтобы закрыть все соединения и освободить системные ресурсы, занятые этими соединениями.
CloseableHttpClient httpClient = <...>
httpClient.close();
5. Стратегия повторного использования соединения
Основным недостатком классической модели блокирующего ввода-вывода является то, что сокеты могут реагировать на события ввода-вывода только тогда, когда происходит ввод-вывод на стороне группы. Когда диспетчер восстанавливает соединение, соединение остается активным, но нет возможности отслеживать состояние сокета и давать обратную связь о событиях ввода-вывода. Если соединение закрыто сервером, клиент не может отслеживать изменение состояния соединения (и не может закрыть локальный сокет в соответствии с изменением состояния соединения).
Чтобы смягчить влияние этой проблемы, HttpClient будет проверять, не устарело ли соединение, прежде чем использовать его.Если сервер закроет соединение, соединение станет недействительным. Эта проверка устаревания не эффективна на 100% и добавляет от 10 до 30 миллисекунд накладных расходов на каждый запрос. Единственное возможное решение, которое не использует модель одного потока на сокет для бездействующих соединений, — это создать поток мониторинга, который специально повторно использует соединения, которые определены как недействительные из-за длительного бездействия. Этот поток мониторинга можно периодически вызыватьClientConnectionManager
КатегорияcloseExpiredConnections()
метод для закрытия просроченных подключений и повторного использования закрытых подключений из пула подключений. Его также можно дополнительно назватьClientConnectionManager
КатегорияcloseIdleConnections()
метод закрытия соединений, которые были неактивны в течение определенного периода времени.
public static class IdleConnectionMonitorThread extends Thread {
private final HttpClientConnectionManager connMgr;
private volatile boolean shutdown;
public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
super();
this.connMgr = connMgr;
}
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(5000);
// 关闭失效的连接
connMgr.closeExpiredConnections();
// 可选的, 关闭30秒内不活动的连接
connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
}
}
} catch (InterruptedException ex) {
// terminate
}
}
public void shutdown() {
shutdown = true;
synchronized (this) {
notifyAll();
}
}
}
6. Политика выживания соединения
Спецификация HTTP не указывает, как долго должно поддерживаться постоянное соединение. Некоторые Http-серверы используют нестандартныеKeep-Alive
Заголовочное сообщение взаимодействует с клиентом, а сервер поддерживает соединение в течение нескольких секунд. HttpClient также использует это заголовочное сообщение. Если ответ, возвращаемый сервером, не содержитKeep-Alive
сообщение заголовка, HttpClient будет думать, что это соединение можно сохранить навсегда. Однако многие серверы экономят ресурсы сервера, закрывая неактивные соединения на определенный период времени без уведомления клиента. В некоторых случаях стратегия по умолчанию слишком оптимистична, нам может потребоваться настроить стратегию выживания соединения.
ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
// Honor 'keep-alive' header
HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
try {
return Long.parseLong(value) * 1000;
} catch(NumberFormatException ignore) {
}
}
}
HttpHost target = (HttpHost) context.getAttribute(
HttpClientContext.HTTP_TARGET_HOST);
if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
// Keep alive for 5 seconds only
return 5 * 1000;
} else {
// otherwise keep alive for 30 seconds
return 30 * 1000;
}
}
};
CloseableHttpClient client = HttpClients.custom()
.setKeepAliveStrategy(myStrategy)
.build();