Причина проблемы
Некоторое время назад я исследовал nacos и использовал его для замены zookeeper в качестве регистрационного центра dubbo, используя версию 1.1.4 nacos. Он также использует nacosSync, инструмент миграции, предоставляемый nacos, который может синхронизировать услуги в общих центрах регистрации с nacos. Это отстой, по крайней мере, не инструмент производственного уровня. Но это не имеет отношения к данной статье, я напишу статью позже, чтобы представить преимущества и недостатки этого инструмента синхронизации, а также какие изменения необходимо внести на уровне производства. Когда я начал тестировать, всегда были сервисы, которые отключались без видимой причины, и я не мог найти причину. Позже, в процессе исследования, nacos выпустила версию 1.2.0-beta.0, поэтому я зашел на github, чтобы прочитать примечание к выпуску 1.2.0-beat.0. Просмотрите исправленные ошибки один за другим и объедините важные в исследовательскую версию Одно из исправлений привлекло мое внимание.
Java-клиент nacos использует для запроса http-интерфейс rest. Это исправление говорит
Когда dubbo использует реестр nacos, на стороне потребителя dubbo появляется большое количество соединений в состоянии TIME_WAIT, занимающих большое количество портов, и каждый запрос/пульс представляет собой новое соединение, а общего соединения нет. Из javadoc проблема может заключаться в использовании HttpURLConnection, Disconnect вызывается для каждого запроса на закрытие соединения.
Перейдите на сервер nacosSync (по сути клиент nacos), чтобы проверить статус соединения (на сцене нет резервирования, это моделируется позже)
Затем прочитайте журнал ошибок
java.net.ConnectException: Can't assign requested address (connect failed)
Почти наверняка эта ошибка вызывает серьезные проблемы, скорее всего, виновником частых падений службы.
Обработка проблем и анализ
Слейте код этой проблемы в исследовательскую версию, переупакуйте и перезапустите nacosSync, и, конечно же, количество TIME_WAIT уменьшилось.После нескольких дней тестирования служба nacos больше не будет отключена без причины. Решение проблемы простое, но почему это происходит? Проверьте код исправления
Документ Java говорит это
Each HttpURLConnection instance is used to make a single request but the underlying network connection to the HTTP server may be transparently shared by other instances. Calling the close() methods on the InputStream or OutputStream of an HttpURLConnection after a request may free network resources associated with this instance but has no effect on any shared persistent connection. Calling the disconnect() method may close the underlying socket if a persistent connection is otherwise idle at that time.
Вызов разъединения() закроет соединение, а закрытие соединения приведет к большему количеству соединений в состоянии TIME_WAIT. Кажется, нам нужно просмотреть базовые знания о tcp. Далее мы представим трехстороннее рукопожатие tcp для установления соединения и четырехстороннюю волну отключения. Следующий контент в основном взят из «Компьютерной сети» Се Сирена. (7-е издание) и статьи в Интернете.
трехстороннее рукопожатие TCP
Как показано на рисунке, процесс трехэтапного рукопожатия для TCP для установления соединения выглядит следующим образом:
(1) В начале A закрыт, B находится в состоянии LISTEN, A инициирует запрос на установление соединения, отправляет сегмент с SYN=1, начальным порядковым номером seq=x и входит в состояние SYN-SENT. состояние
(2) После получения сообщения запроса B соглашается установить соединение, отправляет сегмент сообщения с SYN=1, ACK=1, подтверждает, что порядковый номер равен ack=x+1, а начальный порядковый номер B равен seq. =y, B Войти в состояние SYN-RCVD
(3) Когда A получает подтверждение от B, он также должен дать подтверждение B, ACK=1, seq=x+1, ack=y+1, затем A и B переходят в состояние ESTABLISHED.
Однако протокол tcp должен больше учитывать нештатную ситуацию.
(Исключение A) Если A теряет пакеты, когда A отправляет сообщение, а B не получает его в (1), A повторяет попытку и по истечении времени повторной попытки переходит в состояние CLOSED;
(Исключение B) Если (2) запрос A получен, но на него нет ответа или ответное сообщение потеряно, это (ненормальный A) для A, если сообщение, возвращенное B, потеряно, то есть A не может получить подтверждающее сообщение (3) не произойдет, и B в это время повторит попытку и закроет соединение по истечении тайм-аута;
(Исключение C) Последний пакет подтверждения от A потерян. В это время A перешел в состояние ESTABLISHED и может отправлять данные. B все еще находится в состоянии SYN-RCVD. В это время, если B получает пакет данных от A первым, он также войти в состояние ESTABLISHED; если A не отправляет последний пакет подтверждения и не отправляет данные, B находится в состоянии «полуподключено» и закроет соединение после нескольких повторных попыток (2). Это атака «SYN FLOOD». ".
tcp волна четыре раза
(1) В начале и A, и B находятся в состоянии ESTABLISHED, A активно завершает работу, отправляет сообщение FIN=1, seq=u и переходит в состояние FIN-WAIT-1;
(2) После того, как B получает сигнал закрытия, он отвечает ACK = 1, seq = v, ack = u + 1, и B переходит в состояние CLOSE-WAIT. В это время B также может отправлять данные в A, и A переходит после получения ответа от Б. статус FIN-WAIT-2;
(3) После того, как данные B отправлены, отправьте сигнал A, который может быть закрыт, FIN=1, ACK=1, seq=w, ack=u+1, и B переходит в состояние LAST-ACK;
(4) После того, как A получает сигнал закрытия от B, он отвечает на подтверждение, ACK=1, seq=u+1, ack=w+1, A переходит в состояние TIME-WAIT, ожидает 2MSL (т.е. 120 секунд) а затем переходит в состояние ЗАКРЫТО, В переходит в состояние ЗАКРЫТО после получения подтверждения от А.
нештатная ситуация:
(Исключение A) A инициирует сигнал закрытия для входа в FIN-WAIT-1. Если B не отвечает, он будет повторять попытку, пока не истечет время ожидания. После тайм-аута A напрямую закрывает соединение;
(Исключение B) B входит в состояние CLOSE-WAIT после ответа на A, но не отправляет следующее сообщение FIN, тогда B всегда находится в состоянии CLOSE-WAIT;
(Исключение C) A переходит в состояние FIN-WAIT-2 после получения ACK от B и ожидает закрытия B. В это время данные B все еще могут быть получены, теоретически FIN-WAIT-2 не получит запрос на закрытие от B до тех пор, пока он не будет закрыт.Это необходимо для поддержания этого состояния, но фактическая реализация имеет период ожидания.Linux по умолчанию составляет 180 секунд, и соединение сразу закрывается после истечения времени ожидания;
(Исключение D) После того, как B отправляет сообщение о закрытии соединения FIN, он переходит в состояние LAST-ACK, но не получает ответа.B будет повторно отправлять запрос на закрытие, пока не истечет время ожидания, и закроет соединение по истечении времени ожидания.
Из трехстороннего рукопожатия и четырехсторонней волны tcp можно сделать следующие выводы:
Причина, по которой протокол tcp должен пройти четыре волны, заключается в том, что соединение tcp является полнодуплексным, первые две волны обеспечивают закрытие соединения A с B, а последние два гарантируют закрытие соединения B с A;
Установка TIME_WAIT на стороне клиента должна гарантировать, что последний ACK может быть доставлен B с высокой вероятностью.Если соединение закрывается напрямую, не дожидаясь 2MSL, и ACK также теряется, то повторная передача запроса на закрытие B невозможна. будет обработано, и B, скорее всего, останется в статусе LAST.-ACK;
При отсутствии атаки состояния CLOSE-WAIT и TIME-WAIT подвержены проблемам; CLOSE-WAIT означает, что сервер не закрывает соединение, обычно код забыл закрыть соединение; TIME-WAIT появляется обычно на клиентская сторона. Клиент инициирует слишком много подключений за короткий промежуток времени, и проблема может быть решена путем мультиплексирования подключений.
Если есть много других промежуточных состояний, вы можете проанализировать его по приведенному выше рисунку и рассмотреть, есть ли атака.
Суммировать
При обработке запрашивающего клиента следует учитывать, что использование коротких ссылок может привести к чрезмерному TIME_WAIT;
При написании кода обращайте внимание на возможные исключения, а неиспользуемые ресурсы (включая, помимо прочего, соединения) необходимо вовремя освобождать;
Для использования продуктов с открытым исходным кодом нужно читать больше выпусков на github и пытаться предсказать возможные проблемы заранее, а когда выйдет следующая версия, нужно обратить внимание на исправленные ошибки и введенные новые функции.
Рекомендуются другие статьи, связанные с нако
Отсканируйте QR-код, чтобы следить за поиском ошибок