Что такое Keep-Alive
Это слово выглядит немного знакомым, и, кажется, его видели во многих местах.
KeepAlive для TCP, KeepAlive для Http, а теперь даже некоторые интерфейсные фреймворки имеют что-то похожее на KeepAlive (например, VUE.js, сохраняйте маршрутизацию).
В этой статье представлен механизм KeepAlive в HTTP и TCP, а другие аспекты выходят за рамки этой статьи.
Keep-Alive в Http
Постоянное HTTP-соединение (также известное как HTTP keep-alive или повторное использование HTTP-соединения, что можно перевести как keep-alive или повторное использование соединения) — это использование одного и того же TCP-соединения для отправки и получения нескольких HTTP-запросов/ответов, а не способ открывать новое соединение для каждого нового запроса/ответа.
Протокол HTTP использует режим "запрос-ответ". При использовании обычного режима, т. е. режима без поддержания активности, каждый клиент и сервер запроса/ответа должны создавать новое соединение и отключаться сразу после завершения (протокол HTTP является без установления соединения) каждый запрос будет проходить через три рукопожатия и четыре волны, что неэффективно; при использованииKeep-Alive
В режиме соединение между клиентом и сервером не будет разорвано, и при последующем запросе к серверу клиент будет повторно использовать установленное соединение.
На следующем рисунке показана разница между каждым новым соединением и мультиплексированием соединения в модели связи:
В HTTP 1.0,Keep-Alive
Официальной поддержки нет, но есть и некоторая серверная поддержка, которая давно не рассматривается.
После HTTP1.1,Keep-Alive
Он уже поддерживается и включен по умолчанию. Когда клиент (включая, помимо прочего, браузер) отправляет запрос, заголовок запроса будет добавлен к заголовку.Connection: Keep-Alive
, когда сервер получаетConnection: Keep-Alive
запросы, Keep-Alive также добавляется в заголовок ответа. Таким образом, HTTP-соединение между клиентом и сервером будет поддерживаться и не будет разорвано (способы отключения описаны ниже), а установленное соединение можно будет использовать повторно при отправке клиентом другого запроса.
Текущий протокол Http в основном представляет собой версию Http 1.1, и нет необходимости рассматривать совместимость с 1.0.
Действительно ли Keep-Alive настолько совершенен?
Конечно нет, Keep-Alive также имеет свои преимущества и недостатки, применимые не во всех сценариях.
преимущество
- Экономит использование ЦП и памяти на стороне сервера
- Уменьшенный контроль перегрузки (меньше TCP-соединений)
- Уменьшена задержка для последующих запросов (больше нет рукопожатия)
недостаток
Для некоторых ресурсов/сервисов, к которым обращаются нечасто, например, к непопулярным серверам изображений, это происходит всего несколько раз в год, и поддерживать соединение каждый раз бесполезно (этот сценарий не очень уместен). Keep-Alive может быть очень требовательным к производительности, поскольку он сохраняет ненужное соединение еще долго после запроса файла, занимая дополнительные соединения на сервере.
Что произойдет после повторного использования соединения
Когда нет мультиплексирования соединения, принимающая сторона Http (обратите внимание, что это принимающая сторона, а не конкретно клиент/сервер, поскольку клиент/сервер являются и отправляющей стороной, и принимающей стороной) должна только прочитать все данные в Socket. , для решения проблемы «распаковки», но после мультиплексирования соединения границу одного Http-сообщения различить невозможно, поэтому необходимо дополнительно заниматься проблемой границы сообщения. Конечно, это можно решить, прочитав поле длины заголовка в Http по запросу.
Для ознакомления с наклеиванием и распаковкой обратитесь к другой статье.Объясните подробно вклейку и распаковку в Netty
Обработка проблем с границами пакетов после мультиплексирования соединения Http
Из-за наличия заголовка в Http проблема границы пакета может быть легко решена путем определения некоторых полей заголовка длины сообщения.
В Http есть два способа решения проблемы границы пакетов:
Content-Length обрабатывает границы пакетов
Это наиболее распространенный метод обработки.Когда принимающая сторона обрабатывает сообщение, она сначала считывает полный заголовок (Header), а затем передает заголовок в заголовке.Content-Length
Чтобы подтвердить размер сообщения, вы можете прочитать сообщение в соответствии с этой длиной при чтении сообщения.Сообщение, превышающее длину («липкий пакет»), не будет прочитано, а сообщение, которое недостаточно долго, буферизуется для дальнейшее чтение ("распаковка").
Chunked обрабатывает границы пакетов
В ситуациях, когда общий размер пакета не может быть подтвержден, сообщение может быть подвергнуто блочной передаче, и размер пакета помечен в каждом блоке. Например, Nginx после открытия Gzip-сжатия включает метод передачи chunked.
Перехватывая пакеты через Wireshark, вы можете интуитивно увидеть принцип Chunked:
Обратите внимание, что пакет фрагмента здесь не совпадает с сегментом tcp, фрагмент является просто подпакетом прикладного уровня, а сегмент tcp предназначен для повторной группировки пакетов прикладного уровня.
Перед каждым сообщением чанка оно будет содержать размер текущего чанка.
Как отключиться после мультиплексирования HTTP-соединения
Мультиплексирование соединения было достигнуто с помощью Keep-Alive, но когда соединение будет отключено после мультиплексирования, иначе соединение останется подключенным, что приведет к пустой трате ресурсов.
Протокол Http определяет два способа закрытия мультиплексных соединений:
Идентифицируется тайм-аутом Keep-Alive
Если установлен заголовок ответа сервераKeep-Alive:timeout={timeout}
, клиент сохранит это время ожидания соединения (в секундах) и закроет соединение по истечении времени ожидания.
Теперь установите заголовки ответа на стороне сервера:
Keep-Alive:timeout=15
Давайте посмотрим на эффект настройки тайм-аута через Wireshark:
Как видно из приведенного выше рисунка, после отправки клиентом запроса соединение не разрывается в течение 15S (время на рисунке не отражено, поэтому возьмем 15S), а разрыв соединения происходит после 4 взмахов руками после тайм-аут.
Но если запрос будет сделан снова в течение 15 секунд, соединение можно будет использовать повторно, и трехстороннее рукопожатие не будет повторяться.
На следующем рисунке показан результат повторного запроса в течение 15 секунд:
Идентифицируется по закрытию соединения
Другой способ — добавить принимающую сторону в заголовок ответа.Connection close
Идентификатор, чтобы активно сообщать отправителю, что соединение было разорвано и не может быть использовано повторно; после того, как клиент получит этот идентификатор, он разрушит соединение и восстановит соединение при повторном запросе.
Примечание: После того, как конфигурация закрытия настроена, это не означает, что каждый раз создается новое соединение, но согласовано, что соединение может использоваться несколько раз.При достижении максимального количества раз принимающая сторона вернет флаг закрытия (метод настройки сервера будет представлен ниже)
Проверим эффект, клиент отправляет два запроса:
На скриншотах wireshark видно, что после настройки Connection:close (сервер устанавливает запрос на использование только один раз, поэтому после завершения запроса соединение уничтожается) соединение восстанавливается для обоих запросов.
Установите Keep-Alive в Nginx (сервер)
Конфигурация тайм-аута Keep-Alive:
Syntax: keepalive_timeout timeout [header_timeout];
Default: keepalive_timeout 75s;
Context: http, server, location
第一个参数设置一个超时,在此期间保持活动的客户机连接将在服务器端保持打开状态。如果为0则禁用保Keep-Alive。第二个可选参数在“Keep-Alive: timeout=time”响应头字段中设置一个值。
“Keep-Alive: timeout=time”报头字段被Mozilla和Konqueror识别。MSIE在大约60秒内自动关闭保持连接。
Конфигурация запросов Keep-Alive:
Syntax: keepalive_requests number;
Default: keepalive_requests 100;
Context: http, server, location
设置通过一个保持活动连接可以服务的请求的最大数量。在发出最大数量的请求之后,连接关闭。
Установите Keep-Alive в Tomcat (сервер)
существует<Connector>
Свойства конфигурации в теге:
Конфигурация тайм-аута Keep-Alive:
keepAliveTimeout="超时时间"
, по умолчанию используется значение, установленное для свойства connectionTimeout. Значение -1 означает отсутствие таймаута (т.е. бесконечное).
Конфигурация запросов Keep-Alive:
maxKeepAliveRequests="连接可用次数"
, -1 никогда не является недопустимым. Если не указано, по умолчанию 100.
Например:
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
keepAliveTimeout="超时时间(单位秒)"
maxKeepAliveRequests="连接可用次数" />
Установите Keep-Alive в Spring Boot Tomcat для встраивания
Эта версия основана на springboot 2.0.2.release, пожалуйста, протестируйте другие версии самостоятельно.
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(){
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
tomcatServletWebServerFactory.addConnectorCustomizers((connector)->{
ProtocolHandler protocolHandler = connector.getProtocolHandler();
if(protocolHandler instanceof Http11NioProtocol){
Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler;
http11NioProtocol.setKeepAliveTimeout(60000);//millisecond
}
});
return tomcatServletWebServerFactory;
}
Эта версия основана на springboot 1.5.6.release, пожалуйста, протестируйте другие версии самостоятельно.
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory(){
TomcatEmbeddedServletContainerFactory tomcatServletWebServerFactory = new TomcatEmbeddedServletContainerFactory();
tomcatServletWebServerFactory.addConnectorCustomizers((connector)->{
ProtocolHandler protocolHandler = connector.getProtocolHandler();
if(protocolHandler instanceof Http11NioProtocol){
Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler;
http11NioProtocol.setKeepAliveTimeout(60000);//millisecond
}
});
return tomcatServletWebServerFactory;
}
Методы конфигурации двух версий перечислены выше, теоретически, пока соответствующий класс может быть найден, его можно использовать. Только некоторые классы были удалены из-за обновлений версии
Гипотеза обратного прокси-сервера Nginx для tomcat
По сути, обратный прокси Nginx — это не что иное, как добавление узла
client<->nginx<->tomcat
Для клиента Nginx — это сервер, а для Tomcat Nginx — это клиент.
Nginx устанавливает соединение с клиентом, а также устанавливает соединение со стороной Tomcat.
Так что же произойдет, если Keep-Alive настроен и на Nginx, и на Tomcat?
Тут посложнее, надо добавить...
Настройка Apache HttpClient Keep-Alive (клиент)
Apache HttpClient — это самый сильный HttpClient в Java, а также самый распространенный (внутреннее направление) с мощными функциями.
Apache HttpClient обеспечивает гибкость в работе с KeepAlive и предоставляет настраиваемый интерфейс.Пользователи могут использовать стандартные стратегии Http или настраивать свои собственные стратегии.
HttpClients.custom()
//连接是否复用策略,通过此策略返回是否复用
//DefaultClientConnectionReuseStrategy是默认的Http策略,不设置也可以
.setConnectionReuseStrategy(new DefaultClientConnectionReuseStrategy())
//连接复用后有效期(持久时间)策略,复用后通过此策略判断复用超时时间
//DefaultConnectionKeepAliveStrategy是默认的判断超时时间策略,读取的是Keep-Alive:timeout=超时时间
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
.build();
Кстати, я расскажу об использовании Apache HttpClient, в надежде помочь нуждающимся. (версия Apache HttpClient 4.x)
//创建客户端,此客户端最好保持单例,这是个线程安全的类,并发下也没有问题。
//HttpClient中的连接池等组件都包含在内,如果每次都新建的话,
//效率低,占用资源大,连接复用当然也不会生效了。
HttpClients.custom()
//禁用自动重试,默认有3次的重试策略
.disableAutomaticRetries()
//不用默认的重试策略,自定义
.setRetryHandler()
//设置默认请求配置,这里可以配置一些超时时间等参数
.setDefaultRequestConfig(requestConfig())
//全局Header,每次请求都会携带
.setDefaultHeaders()
//当Https证书不受信任的时候,记得自定义此项
.setSSLHostnameVerifier()
//设置UA
.setUserAgent()
//设置代理
.setProxy()
//...还有很多配置,可以自行查阅文档
.build();
Поддержание активности в TCP
KeepAlive в TCP и Keep-Alive в Http — это не одно и то же: в HTTP используется мультиплексирование соединений, а KeepAlive в TCP — это «мониторинг сердцебиения», который регулярно отправляет пустой TCP-сегмент, чтобы проверить, живо ли соединение. Вот несколько способов установить TCP KeepAive в Java.
Установите Keep-Alive в Netty
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
Установите Keep-Alive в NIO (новая библиотека сетевого ввода-вывода)
channel.setOption(StandardSocketOptions.SO_KEEPALIVE,true);
Установите Keep-Alive в BIO
Socket socket = serverSocket.accept();
socket.setKeepAlive(true);