Мы живем в эпоху микросервисов. У тех есть Много общения происходит внутри их экосистем. Возможно, вы видели так называемые звезды смерти микросервисов:
изображение Исследователи Корнельского университета
Каждый щелчок в современном Интернете вызывает множество сетевых вызовов, и, как вы, вероятно, знаете, сеть ненадежна . Это одна из причин вы должны устанавливать тайм-ауты запроса при получении чего-либо по проводу. Лучше также включить некоторые другие методы, но это тема для другой статьи (и даже целой книги).
Зачем беспокоиться о тайм-аутах?
Их настройка очень важна, потому что сеть может выйти из строя, ваш экземпляр может замедлиться или выйти из строя. Вы не хотите, чтобы пользователи ждали бесконечно только потому, что 1 из ваших 1000 серверов внезапно отключился. Люди ненавидят ждать, это влияет на их счастье и, следовательно, ваш доход . Если сетевой вызов застрянет, вы должны отказаться от него, повторить попытку и, если у вашего кластера нет проблем, он, скорее всего, завершится успешно.
Давайте посмотрим, как мы можем реализовать это на Java и вперед.
Общий подход Java
Самый популярный http-клиент в java – это Apache HttpClient . Вот как будет выглядеть настройка тайм-аутов запроса:
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(100)
.setConnectionRequestTimeout(100)
.setSocketTimeout(300)
.build();
Эти 3 все разные и независимые. Самая большая проблема, с которой мы сталкиваемся здесь, заключается в том, как представить 500 мс в виде 3 разных тайм-аутов, должно ли это быть 100 + 100 + 300 или 50 + 50 + 400? И, что более важно, волнует ли нас вообще, займет ли время ожидания подключения 200 мс? Представьте, что сервер завершит запрос за 50 мс, поэтому общее время ответа в большинстве ситуаций составит 250 мс тебе все равно, все в полном порядке! С другой стороны, вы не можете устанавливать тайм-ауты намного больше, потому что это приведет к более длительным запросам. Кроме того, время ожидания сокета – это просто время ожидания между любыми последовательными пакетами, считываемыми из сокета, не весь ответ отправляется вам обратно.
Тем не менее, это все, что мы можем сделать, используя Apache HttpClient. Давайте взглянем на Го.
Перейти в стандартную библиотеку
Go, однако, поддерживает тайм-аут всего вызова клиента:
callTimeoutMs := 2000
httpClient := http.Client{Timeout: time.Duration(callTimeoutMs) * time.Millisecond}
_, err := httpClient.Get("http://httpbin.org/delay/1") // this waits 1 second
if err != nil {
log.Fatal(err)
}
Здесь мы устанавливаем время ожидания вызова на 2 секунды и вызываем httpbin , который будет имитировать работу в течение 1 секунды и вернет ответ после. Запуск этого приведет к успешному вызову.
Если мы установим время ожидания вызова равным 1 секунде, он завершится ошибкой с сообщением, аналогичным приведенному, что именно то, что мы ищем!
2019/11/25 19:06:09 Get http://httpbin.org/delay/1: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
Напротив, настройка только времени ожидания вызова не всегда является лучшим, чего мы можем достичь. Представьте, что у вас длинный и тяжелый запрос, выполнение которого занимает 10 секунд. Было бы неприятно ждать ответа 10 секунд и выяснять, что серверы все это время пытались установить соединение, и никакой реальной работы не было сделано.
Решение этой проблемы? Время ожидания подключения. Да, тот, кого мы вроде как обвиняли раньше. В сочетании с таймаутом вызова это обеспечивает надежную защиту ваших сетевых взаимодействий:
callTimeoutMs := 10000
connectTimeoutMs := 25
httpClient := http.Client{
Timeout: time.Duration(callTimeoutMs) * time.Millisecond,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: time.Duration(connectTimeoutMs) * time.Millisecond,
}).DialContext,
},
}
_, err := httpClient.Get("http://httpbin.org/delay/5") // this waits for 5 seconds
if err != nil {
log.Fatal(err)
}
Превышение времени ожидания подключения даст нам желаемое поведение: 2019/11/25 19:12:25 Получить http://httpbin.org/delay/5: наберите tcp 54.172.95.6:80: время ожидания ввода-вывода/| Go предоставляет вам большую гибкость с помощью стандартного HTTP-клиента, а библиотека поддерживается поставщиком - идеальное сочетание, вот почему мне это очень нравится! Теперь давайте еще раз вернемся в мир Java.
Итак, Ява обречена?
Нет . Существуют менее популярные альтернативы, такие как JDK11 http-клиент и OkHttp , которые поддерживают функции тайм-аута вызова. К сожалению, это не лучшие результаты при поиске в Google, поэтому люди менее осведомлены о них или не хотят начинать их использовать.
JDK 11
Это современный http-клиент, поставляемый со стандартной библиотекой, которая поддерживает HTTP/1.1, HTTP/2, асинхронные вызовы через CompletableFuture и предоставляет удобный api для работы. Давайте объединим с ним оба тайм-аута:
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(50))
.version(HttpClient.Version.HTTP_1_1)
.build();
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("http://httpbin.org/delay/1"))
.timeout(Duration.ofMillis(2000))
.build();
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
Этот клиент предоставит приятные и понятные сообщения об ошибках (особенно по сравнению с Go) для тайм-аутов подключения и вызова соответственно:
Exception in thread "main" java.net.http.HttpConnectTimeoutException: HTTP connect timed out Exception in thread "main" java.net.http.HttpTimeoutException: request timed out
ОХТТП
OkHttp также поддерживает тайм-ауты вызовов и подключений. Оба могут быть настроены на уровне абстракции клиента, что немного удобнее по сравнению с реализацией JDK11:
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(Duration.ofMillis(100))
.callTimeout(Duration.ofMillis(1000))
.build();
В любом случае, как выбрать значения тайм-аута?
Последнее, что я хотел бы упомянуть, – это как получить эти 2 числа. Тайм-аут вызова больше связан с SLA/SLO, которые у вас есть с другими службами, а тайм-аут подключения – с ожиданиями от базовой сети. Например, если вы отправляете запросы в один и тот же центр обработки данных, то 100 мс будет достаточно (хотя он должен устанавливать соединение быстрее, чем 5 мс), но для работы в мобильных сетях (которые более подвержены ошибкам) потребуется большее время ожидания подключения.
Подведение итогов
В этой статье мы обсудили, почему важны тайм-ауты, почему “классические” тайм-ауты не соответствуют современным требованиям и какие инструменты использовать для их принудительного применения. Надеюсь, вы нашли в нем что-то полезное.
Оригинал: “https://dev.to/igorperikov/why-i-like-go-http-client-as-a-java-developer-32hp”