Канал Андрея про бекенд – Telegram
Канал Андрея про бекенд
1.68K subscribers
9 photos
13 links
👨🏻‍🎓 Я Андрей Суховицкий - tech lead и лектор Университета ИТМО, @sukhoa

🔥 Темы в канале: kotlin, java, coroutines, многопоточное программирование, system design, реализация высоконагруженных и надежных систем.

Посты по средам

Присоединяйтесь!
Download Telegram
Все еще в шоке, от того что моя аудитория по большей части не пишет на котлин 😁 Но для тех, кто пишет, продолжу делать посты о нем и о корутинах, очень сильно их люблю.

📝 Напишите в коментах, почему так сложилось, что не используете котлин или корутины в разработке: может быть что-то понравилось / в компании используют другой язык / просто не было возможности попробовать?
14👍5🔥1🥱1🐳1
Observability

🔍 Конечно, мы хотим, чтобы наше приложение было "наблюдаемым" (observable). Что мы вкладываем в это понятие и так ли это нам необходимо?

🔦 Под observability обычно понимается способность системы продюсировать достаточное количество информативных данных о себе, по которым мы можем делать обоснованные выводы о ее состоянии.

📝 Такие данные еще называют телеметрией. Это могут быть метрики приложения, логи, трейсы и другие данные, полезные для:

- Понимания состояния системы и ее здоровья
- Понимания соответствия ее поведения ожидаемому, корректности
- Понимания уровня производительности системы
- Поиска и локализации неисправностей
- Установления хронологии событий при траблшутинге, временных промежутков инцидентов, простоев, отказов
- Установления начальной, корневой причины (root cause) неисправностей
- Установления длительностей взаимодействия между компонентами системы и тд

💡 Любое приложение должно быть в какой-то мере observable, однако понятно, что требования к observability наиболее высоки для систем, которые:

- Не терпят простоя: теряют деньги и лояльность клиентов каждую секунду и минуту, пока недоступны

- Не терпят потерь трафика, задержек в обработке

👁 В таких системах важно почти мгновенно (и желательно автоматически) понять, что проблема есть, в кратчайшие сроки выяснить источник, локализовать проблему и принять меры по ее устранению. Конечно, инструменты observability в даных кейсах это глаза и уши инженеров.

Хочу переодически делать посты про мониторинг и observability приложений. В основном про метрики: что это, какие есть типы, как ими пользоваться и чем они могут нам помочь и тд. Что думаете?

🔥 - тема очень важная и интересная, пиши
👍 - не на 100% понимаю зачем все это, но давай почитаем
🤔 - этим вообще кто-то пользуется?
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥80👍41
📊 Metrics basics - часть 1. Что такое метрики?

🔬 Метрики - числовые данные, отражающие какой-то аспект вашего приложения. Они формируются путем регулярных подсчетов и замеров интересующих параметров сервиса. Это могут быть размеры очередей, количество потоков, количество совершенных http запросов или отправленных / полученных байтов.

📌 У метрики есть название, обычно отражающее предмет измерения (task completed / request served). Кроме того, к метрике могут прикрепляться метаданные, которые представляют из себя набор пар ключ-значение.

💡 Пример: метрика с названием http_requests_served_total.

- Отражает общее количество http запросов, которые были обслужены

- Обычный счетчик ⏱️, который увеличивается каждый раз, когда очередной запрос обслужен. Скажем, в начале их было 0, через минуту 5, еще через минуту 10.

Что можно сказать по этой метрике?
❗️ Можно сказать, сколько запросов было выполнено с момента старта приложения до настоящего времени.

Но что, если я хочу увидеть сколько было успешных запросов (со статусом ответа 200)?
❗️Можно завести новую метрику: http_requests_served_status_200_total. Это сработает. Хуже будет, если вы хотите получить какую-нибудь табличку, где отображается сколько запросов было выполнено с каждым из возможных статусов: 404, 500, 200, 302 и тд, мы ведь никогда не знаем наверняка с какими кодами могут завершиться ваши запросы - придется делать метрики для каждого из них!

💡 Тут-то как раз можно использовать возможность прикрепления метаданных. Давайте используем нашу первую метрику http_requests_served_total, но добавим к ней метаданные в виде пары ключ-значение, где есть ключ с названием http_status_code и значение, которое подставляется в зависимости от того, какой код мы получили.

0️⃣ Теперь, при старте приложения наша метрика имеет значение 0:
http_requests_served_total{} 0.0


1️⃣ За первую минуту у нас 3 запроса с кодом 200, два с кодом 500:
http_requests_served_total{"http_status_code"="200"} 3.0
http_requests_served_total{"http_status_code"="500"} 2.0


2️⃣ За первые две минуты у нас 7 запросов с кодом 200, два с кодом 500, один с 302:
http_requests_served_total{"http_status_code"="200"} 7.0
http_requests_served_total{"http_status_code"="500"} 2.0
http_requests_served_total{"http_status_code"="302"} 1.0


🔐 Эти пары ключ-значения называют метками (label) или измерениями (только не measurement, а dimension, тут не путать). Dimension, потому что мы ведь по факту увеличиваем размерность данных. Обратите внимание, чем больше у нас различных кодов ответа, тем больше строк при изменении метрики :)

🖇️ Туда же в мета-информацию можно, например, добавить идентификатор конкретной виртуальной машины или "инстанса" приложения, чтобы мы видели, как ведет себя каждая из них в отдельности. Давайте добавим label instance. Посмотрите, что получилось в конце третьей минуты:

http_requests_served_total{"http_status_code"="200", "instance"="i_1"} 7.0
http_requests_served_total{"http_status_code"="500", "instance"="i_1"} 2.0
http_requests_served_total{"http_status_code"="302", "instance"="i_1"} 1.0
http_requests_served_total{"http_status_code"="200", "instance"="i_2"} 1.0
http_requests_served_total{"http_status_code"="500", "instance"="i_2"} 12.0
http_requests_served_total{"http_status_code"="302", "instance"="i_2"} 2.0


🤯 Строк уже 6! Это не удивительно, ведь у нас два инстанса и каждый отправляет метрики. Смотрите, мы опять увеличили "размерность данных". 2 инстанса и 3 различных http кода дают нам количество строк равное 2*3=6.

💎 Обращайте внимание на размерность метрик. Не стоит делать лэйблами переменные, которые могут принимать значения из большого диапазона. Это сильно увеличит объем собираемых метрик. Если вы добавите к нашей метрике еще метаданные, обозначающие, скажем, некий идентификатор клиента, которых у вас 1000, то наши 6 строк превратятся в 6000. Сохранять и визуализировать такую метрику станет гораздо сложнее.

👀 Кстати, по нашей метрике уже видно, что второй инстанс ведет себя нехорошо, за прошедшее время, он только один раз успешно обслужил запрос :)

#metrics_basics
Please open Telegram to view this post
VIEW IN TELEGRAM
👍28🔥135
📌 Metrics basics - часть 2

🔆 Что такое метрики мы немного обсудили. Но как собираемые нами измерения превращаются в полезные графики?

🦊 Представим, что у нас есть несколько инстансов (экземпляров) одного приложения. Само приложение обслуживает http-запросы от клиентов. И мы хотим где-то видеть график, который показывал бы нам сколько запросов в секунду обслуживает наш сервис.

🧵 Итак, цепочка выглядит следующем образом

1. Каждый инстанс ведет подсчет выполненных им запросов

2. Данные с каждого инстанса помещаются в некое хранилище

3. Мы создаем запрос к хранилищу, который подходит под наши требования (скажем, "сколько запросов в секунду обрабатывало наше приложение последние 3 часа")

4. Выполняя этот запрос в каком-либо инструменте для визуализации данных, получаем соответствующий график.

Конечно, реализация каждого пункта зависит от конкретных технологий, которые вы используете для сбора, хранения и визуализации метрик. Мы будем рассматривать все на примере Prometheus (сбор, хранение, запросы и агрегации) и соответствующих клиентских библиотек для JVM: Prometheus Java client и часто используемого со Spring Framework фасада для сбора метрик Micrometer.

🟢 Давайте в этом посте посмотрим на первый пункт нашего плана, как наш сервис будет подсчитывать и хранить значения метрики локально.

1️⃣ Для начала нам требуется создать некое локальное "хранилище" изменений. Это просто in-memory cache, где будут временно находится измеренные вами значения метрик (некоторые пока приложение не остановится, некоторые пока значения не будут отправлены в хранилище). Буду называть его Registry, как оно и зовется в наших библиотеках.

В клиенте Prometheus для Java есть реджистри по-умолчанию, но вы можете создать его и сами:
PrometheusRegistry myRegistry = new PrometheusRegistry();


Micrometer же является фасадом, библиотека не привязана к конкретной реализации хранилища метрик, поэтому в ее составе есть целый ряд реализаций Registry, каждое из которых является адаптером для какой-то из систем сбора/хранения метрик. Т. к. нас интересует прометей, то будем использовать соответствующую реализацию:
val prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);


Вообще, если вы используете Spring-boot, то при наличии необходимой зависимости в classpath реджистри создадут и за вас :)

2️⃣ Окей, теперь у нас есть локальное хранилище для метрик и мы готовы их собирать. Для этого нам вполне подойдет тип метрики, который называется Counter, - обычный монотонно увеличивающийся счетчик выполненных http-запросов. Создаем:
Counter reqTotal = Counter.builder()
.name("http_requests_served")
.help("Total number of served http requests")
.labelNames("status_code")
.register();


Заметьте, что для создания метрики я указал ее имя, справочную информацию, а также label, метаинформацию, о чем мы говорили в прошлом посте. Дело за малым, нужно использовать созданную переменную-счетчик, увеличивая ее после каждого выполненного http-запроса:

reqTotal.labelValues(statusCode).inc()


Для Micrometer все чуточку сложнее, потому что он требует передавать ему значения метаинформации в момент создания, когда они еще не всегда известны. Поэтому напишем такую функцию:

fun increaseRequestCounter(status: String) = Counter
.builder("http_requests_served")
.denoscription("Total number of served http requests")
.tags("status_code", status)
.register(registry).increase();

increaseRequestCounter(statusCode)


☄️ Вот так достаточно просто начать производить замеры на стороне приложения. Расскажите в комментариях, какие вы используете библиотеки и хранилища для метрик.

Продолжаем обсуждать основы метрик? - 🔥

#metrics_basics
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥25👍8
🟢 Metrics basics - часть 3

🔼 В прошлом посте под вторым пунктом плана значилось "Данные с каждого инстанса помещаются в некое хранилище".

Но как именно метрики "помещаются" в хранилище? Есть два основных пути, по которому идут разработчики:

1️⃣ Клиент (ваш сервис, с которого вы хотите собирать метрики) устанавливает соединение с хранилищем и отправляет ему собранные данные.

2️⃣ Сервис выстявляет (expose) наружу API, с помощью которого можно получить собранные на текущий момент данные, а хранилище умеет опрашивать (poll) сервисы с некой регулярностью, "забирать" данные и сохранять у себя.

💡 Первый способ является стандартным для общения с базами данных, вероятно, поэтому большинство хранилищ работают именно по этому принципу. Но Prometheus не таков, он идет по второму пути. По этой ссылке можно найти классификацию метрик-систем, в том числе и по способу "публикации" метрик.

📂 Возвращаясь к нашему Прометею: процесс сбора метрик в его терминологии назваыется скрейпом (scrape), а собственно сервер, с которого собираются метрики, обозначается, как "цель" (target). В конфигурационном файле можно определить:

✏️ Параметры самой сборки (например, как часто ее производить / таймаут на процесс сборки). Тут же можно определить параметр metrics_path - http-путь, по которому прометей сможет получить метрики, по-умолчанию это /metrics. Если вы используете Spring Actuator, то по-умолчанию значение будет /actuator/prometheus. Перейдя по данному пути вы сможете увидеть список метрик и их текущие значения, которые будут собираться прометеем:
# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{area="heap",id="G1 Survivor Space",} 3382248.0
jvm_memory_used_bytes{area="heap",id="G1 Old Gen",} 3.2279552E7
jvm_memory_used_bytes{area="nonheap",id="Metaspace",} 4.8777184E7
jvm_memory_used_bytes{area="nonheap",id="CodeCache",} 1.1440768E7
jvm_memory_used_bytes{area="heap",id="G1 Eden Space",} 2.097152E7
jvm_memory_used_bytes{area="nonheap",id="Compressed Class Space",} 6788528.0
# HELP process_cpu_usage The "recent cpu usage" for the Java Virtual Machine process
# TYPE process_cpu_usage gauge
process_cpu_usage 0.008264797399683058


✍️ Какие сервера подлежат сборке (можно использовать статический набор серверов или использовать интеграцию с множеством service-discovery механизмов, которые будут возвращать вам набор код для скрейпа)

🏷️ Какие метки (labels) добавить/убрать/изменить в готовых метриках (например, на этом этапе очень удобно автоматически добавлять метку конкретного инстанса/адрес-порт, чтобы визуализировать метрики по каждому экземпляру сервиса индивидуально, видеть, где присутствует какая-то проблема и тд)

💡 В одном из следующих постов подумаем, каким образом визуализировать нашу метрику http-запросов, напишем запрос к прометею с помощью языка PromQL (Prometheus Query Language) и познакомимся с его полезными функциями.

#metrics_basics
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍94
🚰 Разбавлю немного череду постов про метрики.

💪 Может быть, вам будет интересно посмотреть доклад Андрея Паньгина о своем детище: профайлере для JVM-based приложений Async-profiler.

🔥 Это не какой-то пет-проект, это развитый тул с большим количеством пользователей. Он много раз помогал нашей команде находить узкие места в рабочих проектах. Дополнительный плюс в том, что async-profiler потребляет сравнительно немного ресурсов. В общем, крутая штука
👍10🔥75
Батчинг (пакетная обработка). Часть 1.

🏛 Ситуация: в моменты повышенной нагрузки время выполнения запросов к БД начинает расти и сказываться на производительности. Профайлер показывает вам, что значительную часть длительности операции занимает ожидание получения JDBC соединения. У вашего приложения уже 20 подключений к базе и больше подключений вам выделять не хотят. Что делать?

🏕 Осознаем природу проблемы:

🎼 Паттерн работы с JDBC подключением следующий
1. Запрашиваем свободное подключение из пула
2. Посылаем в него команду
3. Ожидаем, когда придет ответ из этого же подключения
4. Возвращаем его в пул

🛑 То есть коннекция блокируется на все время выполнения одного конкретного запроса. Давайте представим, что время на доставку запроса до базы по сети составляет 40 ms, еще 40 ms занимает доставка ответа от базы. И только 20 ms занимает сам запрос. Получается из 100 ms мы тратим 80% на сетевое взаимодействие. Именно эти расходы мы и будем оптимизировать.

📦 Батчинг (пакетная обработка) - процесс выполнения нескольких запросов/команд в рамках одного сетевого запроса.

Давайте отправлять в коннекцию не один запрос, а объединять запросы на вставку/обновление данных в “пачки” или “батчи” по 10 и отправлять вместе. Сетевые расходы на один “пакет” остаются более-менее неизменными, зато теперь вместо 10 сетевых обменов мы будем иметь всего один. Считаем: без батчинга 100 ms * 10 = 1000 ms, с батчингом 40 ms + 20 ms * 10 + 40 ms = 280 ms. То есть вы сэкономили уже 72%!

Батчинг позволяет вам увеличить пропускную способность приложения. Однако, не стоит забывать и о том, что он может в худшую сторону повлиять на время выполнения отдельно взятой операции, ведь в нее будет входить время, затраченное на формирование пакета. Время формирование батча - один из важнейших параметров при реализации подхода наряду с размерами формируемых пакетов.

🚩 Пример выше не является чем-то каноническим. Применимость батчинга довольно ограничена, не для всех типов операций его легко реализовать. Но для insert/update операций он реализуется достаточно просто.

🧠 Буду переодически в постах рассматривать детали этого подхода, границы его применимости, связанные метрики и нюансы реализации.

#highload #architecture
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥317👍5👌1
Батчинг (пакетная обработка). Часть 2.

🌐 Аналогично батчинг применяют для уменьшения сетевых расходов при HTTP взаимодействии (aka REST API). Проектируя свои сервисы и имея высокие требования по пропускной способности/времени отклика, вы можете предусмотреть использование батчинга. Скажем, у вас есть API для обновления статуса заказа по его id:

POST /orders/{orderId}/status
Content-Type: application/json

{
"status": "delivered",
"timestamp": "2021-08-01T00:00:00Z"
}


🌪 Давайте изменим API таким образом, чтобы можно было обновить статусы сразу для нескольких заказов в рамках одного запроса.

POST /orders/status
Content-Type: application/json

{
"statuses": [
{
"orderId": "1",
"status": "delivered",
"timestamp": "2021-08-01T00:00:00Z"
},
….
]
}


🍴 Это изменение позволяет не только сократить расходы на сетевое взаимодействие, но и использовать возможности параллелизма на стороне сервера. Например, если в предыдущем примере на сетевой обмен мы тратили 80 ms, а на выполнение 20 ms, то для десяти отдельных запросов время выполнения составит (80 ms + 20 ms) * 10 = 1000 ms. А если объединим в батч размером 10 и выполним параллельно на стороне сервера, то время составит (80 ms + 20 ms) = 100 ms, что в 10!!! раз меньше, чем без батчинга. ❗️Здесь стоит упомянуть, что, существует предел прироста производительности при параллелизации кода. Но в посте мы рассматриваем идеальную ситуацию. Если вам интересна эта тема, то можете прочитать пост про закон Амдала.

✍️ Важно отметить, что, поскольку батчинг является способом экономить именно на сетевых задержках, то его использование не даст сколько-нибудь значительный прирост при использовании HTTP2 и других протоколов, умеющих в мультиплексирование. Хотя и здесь нельзя совсем отметать экономию, например, на объеме метаданных.

🚨 При самостоятельной реализации батчинга, я бы советовал опираться на два параметра:
1. Максимальный размер батча, который должен быть отправлен. Пример: скопилось 50 команд - отправляем.
2. Максимальное время накопления батча. Например: прошло 30ms, за это время скопилось только 20 запросов - все равно отправляем.

⚖️ Нужно найти баланс между этими двумя параметрами. Чем больше батч, тем больше экономия на сети. Однако у слишком большого сетевого запроса тоже может возрастать время передачи в том числе из-за большей вероятности повторной пересылки пакетов. Второй параметр абсолютно необходим, чтобы не случалось ситуаций вечного накопления батча. Кроме того, он устанавливает максимальный оверхед для каждой отдельной операции при использовании батчинга.

☀️ Тема батчинга кажется мне настолько важной и базовой, что в одном из заданий моего курса по устойчивым системам студенты-выпускники ИТМО должны обнаружить потенциальное узкое место для его применения, и реализовать собственный батчер. Работаем над тем, чтобы запустить курс на широкую аудиторию💪

#highload #architecture
Please open Telegram to view this post
VIEW IN TELEGRAM
👍19🔥6👌31🤔1
Ждать вечно не лучший выбор

☠️ Помните задачку про перевод денег с одного аккаунта на другой? Одно из проблемных мест там - возможность взаимной блокировки (deadlock), когда первый поток выполняет перевод с аккаунта с id = 1 на аккаунт с id = 2, а второй поток переводит наоборот со второго на первый аккаунт. Соответственно, может возникнуть ситуация захвата ресурса “крест-накрест”. Один поток захватил блокировку на аккаунт 1 и ждет аккаунта 2, второй поток захватил второй аккаунт и ждет первого.

📌 На собеседовании могут спросить, как разрешить данную ситуацию. Каноничным ответом будет: “использовать иерархическую блокировку”. Суть подхода заключается в том, чтобы всегда захватывать и освобождать блокировки в одинаковом порядке. В данном случае, мы могли бы всегда сначала захватывать блокировку для аккаунта с меньшим ID, потом с большим. Это гарантирует отсутствие взаимной блокировки. Можете почитать про эту технику в интернете. Знать про иерархическую блокировку, конечно, стоит. Однако на практике я не видел ее использование в промышленном (не библиотечном) коде.

Есть более простая техника, которая по моему мнению должна применяться разработчиками гораздо чаще. Суть техники - не блокировать поток навечно в ожидании мьютекса. Большинство примитивов синхронизации включают в свой API методы, которые ограничивают максимальное время ожидания.

Пример 1: интерфейс java.util.concurrent.locks.Lock. Метод tryLock позволяет ограничивать максимальное время, за которое должна быть получена блокировка. Если этого не происходит, то метод возвращает false и поток может продолжать свое выполнение.

boolean tryLock(long timeout, TimeUnit unit)


Аналогичный метод выставляет и java.util.concurrent.Semaphore.

Пример 2: интерфейс java.util.concurrent.BlockingQueue. Методы offer и poll позволяют ограничить максимальное время ожидания при помещении и изъятии элемента из очереди соответственно.

boolean offer(E e, long timeout, TimeUnit unit)
boolean poll(long timeout, TimeUnit unit)


Само по себе использование блокировок с ограниченным временем ожидания уже поможет вам избежать deadlock. И все, что для этого придется сделать - внести минимальное изменение в код, добавив желаемый таймаут.

Может возникнуть логичный вопрос: “Но мне ведь нужен лок, а тут метод вернет false и что дальше?”. Если вам нужен лок, то вы можете пытаться захватить его в цикле (spin lock), делая несколько попыток. Таймаут даст вам возможность подсветить неудачные попытки захвата лока (логировать, писать метрики, сколько в среднем времени проходит до его успешного захвата). Иногда проблема заключается не в deadlock, а в банальном скоплении очереди на использование лока.

⚡️ Если проблема будет подсвечена, вы сможете перейти к ее решению более дорогостоящими, но и более эффективными методами. Например, реализовывать иерархическую блокировку.

🐕 Мой совет: поток - не Хатико, он не должен ждать вечно. Всегда используйте возможность ограничения максимального времени ожидания при использовании блокирующих 🔥 вызовов.

#java #kotlin #threads
5👍24🔥11💯42
К посту выше 📤

Вспомнил давний случай из практики, когда моя любовь к таймаутам на взятие лока вышла мне боком.

🚦 Понадобился мне для чего-то семафор. Напомню, semaphore - это такой мьютекс, который позволяет получать доступ к критической секции одновременно нескольким (N) потокам.

Поскольку проект на Kotlin coroutines, я использовал специальный корутиновый семафор и обнаружил, что в его API отсутствует метод, который позволяет передать таймаут и не блокироваться вечно на acquire(). Остановить меня таким невозможно, поэтому я накидал какого-то крокодила-декоратора семафора, где метод acquire выглядел похоже на:
suspend fun acquire() {
while (true) {
try {
withTimeout(1000) {
semaphoreDelegate.acquire()
}
return
} catch (e: TimeoutCancellationException) {
tracer.warn("Bla-bla, long wait...")
}
}
}


🛑 Мысль понятная - в цикле вызываем обычный блокирующий корутину acquire , но ограничиваем его withTimeout, чтобы хотя бы зарепортить в логи о долгом ожидании. Непонятно тут другое, как я, имея немалый опыт программирования на корутинах, попался в эту ловушку: через какое-то время мой код намертво заблокировался и вообще перестал подавать признаки жизни.

⬇️ Кто хочет - подумайте сами, почему так случилось, дальше спойлеры.

⏱️ Функция withTimeout - пример кооперативныймногозадачности. Одна корутинка выставила условный флаг "cancelled", вторая в какой-то момент времени это заметила, да и завершилась. В моем случае, к сожалению, исключение TimeoutCancellationException иногда выбрасывалось ужетогда, когда семафор был захвачен, но мы все равно шли на второй круг захватывать его снова. В итоге все слоты семафора с течением времени были заблокированы и никем не освобождались..

🪤 Не один я попадал в эту ловушку,
вот тред, где Роман Елизаров объясняет,что это ожидаемое поведение функции withTimeout и надо учитывать это при программировании на корутинах.
Please open Telegram to view this post
VIEW IN TELEGRAM
5🔥13👍85
Head of line blocking

🔒 На собеседовании вас могут спросить, чем протокол HTTP2 лучше HTTP1. В этом посте я опишу проблему Head of line blocking (блокировка головы очереди), которая снижает потенциальную производительность HTTP1 и которую эффективно решает протокол следующей версии.

🐢🚶🏼‍♂️🚶🏻‍♀️🚶🏻 Опишем суть явления. Скажем, у вас есть очередь задач, которые должны выполняться последовательно друг за другом. Тогда, любая задача, требующая много времени на выполнение задерживает (на это же время) все последующие задачи в очереди. Это кажется очевидным? Но как это связано с HTTP и вообще, зачем это знать?

HTTP1 HOL (head of line) blocking

🚥 Запросы в HTTP1 обрабатываются в порядке FIFO: записываем запрос в TCP подключение, ждем, пока придет ответ и только получив его записываем следующий запрос. Можно сказать, что подключение “блокируется” на все время выполнения запроса.

🐢 При этом "среднее" время обработки запросов со стороны сервера может быть приемлемым, но любые "выбросы" требующие длительного времени на выполнение будут задерживать и увеличивать время выполнения всех кто стоит в очереди за ними. Конечно, это сильно снижает пропускную способность.

🏄🏻‍♀️ Есть оптимизации, например, pipelining, однако она никак не влияет на порядок обслуживания, а значит не сильно помогает в глобальном смысле, оптимизирует только транспортные расходы.

TCP HOL blocking

💡 Проблема не специфична именно для HTTP1, ее можно продемонстрировать и на примере TCP. Протокол TCP был создан, чтобы “ненадежная” сеть, которая может терять сетевые пакеты, умела становиться надежной. Для этого протокол умеет:

1. На стороне отправителя “нарезать” один большой массив данных на части и присваивать каждому из них свой порядковый номер.

2. На стороне получателя TCP “собирать” эти отдельные пакеты в единой целое в соответствии с “номерами” пакетов. Если на стороне отправителя у вас были пакеты 1, 2, 3, 4, 5, то и на стороне получателя все должно быть аналогично.

📡 Если TCP на стороне получателя “недосчитался” какого-то пакета, то он ждет, пока нужный пакет не будет передоставлен. Вот тут-то на сцене и появляется HOL blocking. Представьте, все пакеты от 2 до 5 пришли, а 1 (то есть голова очереди) потерялся. Все остальные пакеты будут ждать, пока первый будет переправлен. Конечно, время обработки пакетов 2-5 увеличится на время переотправки пакета 1.

⬇️ В следующем посте опишу, как HTTP2 решает проблему HOL на прикладном уровне и какой технический прием для этого используется.

#theory #interview
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍36🔥155🆒1