Немного про CancellationToken.
Основная её цель - иметь источник уведомлений о том, что результат работы которую выполняет ваш API или метод больше никто не ждет. Грустно, но такое бывает.
Начну чуть издалека :)
Существует миф что сеть стабильна. Но это не так. К тому же сейчас очень многие применяют микросервисную архитектуру, которая намного более интенсивно использует сеть. А значит больше точек отказа.
Еще обычно всё хостится в k8s, что еще больше усложняет топологию и перед вашим микросервисом может быть 5-6 балансировщиков и прокси, а то и больше :)
Типичный маршрут запроса в микросервис который расположен допустим в Yandex Cloud Managed k8s:
Yandex Network LoadBalancer -> k8s gateway / ingress -> k8s service -> ApiGateway -> k8s service -> ваш микросервис
Перед всем этим можно еще подключить защиту от DDoS (в виде еще одного прокси).
Часто еще делают AuthN/AuthZ прокси перед ApiGateway.. или еще Service Mesh.
Помимо этого в компании могут быть еще общие внешние балансировщики и вполне вероятно что даже и не один.
Так вот цепочек сетевых вызовов много, сеть нестабильна и на любом вызове может произойти ошибка, таймауты или просто пользователь закрыл вкладку браузера или перешел на другую страницу.
Во всех этих случаях, о результатах работы сервера уже никто не узнает и не ждет.
Как HTTP отменяет запросы?
HTTP/1.x - клиент просто закрывает TCP соединение
HTTP/2 - отправляет фрейм RST_STREAM, чтобы отменить запрос (стрим), но не закрывать соединение.
HTTP/3 - аналогично HTTP/2
Если погрузиться в исходники Kestrel, можно найти обработку этих ситуаций и вызов CancellationTokenSource.Cancel(),
CancellationToken которого пробрасывается в HttpContext.RequestAborted или в параметр экшена контроллера.
Нам надо только пробросить его дальше в наши методы и обязательно туда где есть IO операции или тяжелые CPU bound задачи.
Например HTTP, запросы в бд, чтение файлов. Чтобы если такая ситуация произойдет, мы могли отменить тяжелые операции и быстро освободить ресурсы на что-нибудь полезное.
Помимо экономии ресурсов, отмена запросов позволяет в некоторых случаях сделать систему более консистентной.
Бывало ли у Вас такое, что вы отправляете во внешнюю систему POST запрос на создание ресурса, ловите таймаут, делаете ретрай с помощью Polly, а потом внезапно обнаруживаете, что создалось 2 (или более) ресурсов, вместо одного.
Такое часто возникает когда в API не предусмотрели отмену запроса. Или если запрос был прерван по другой причине и Вы не успели прочитать успешный ответ от сервера и сделали повторный запрос.
Эта проблема решается реализацией идемпотентности, о котором я расскажу в следующий раз.
Так вот я провел бенчмарк, на простых HTTP эндпойнтах, которые просто ждут 1 секунду и отдают 200 OK. В одном используется CancellationToken, а в другом нет.
Сетап бенчмарка простой - 250VUs, 1 сек sleep между итерациями.
Каждую итерацию отправляется запрос и отменяется спустя случайное кол-во мс (от 1 до 600).
Так как тестируемый эндпойнт выполняется минимум 1000 мс, то все запросы гарантированно завершаются ошибочным ответом (499).
Результаты бенчмарков с точки зрения клиента не имеют особого смысла, так как запрос отправляется и отменяется достаточно быстро и результаты будут одинаковыми.
'https://t.me/sh_dotnet/103?comment=334' rel='nofollow'>Первый - эндпойнт который корректно отменяет запрос
'https://t.me/sh_dotnet/103?comment=335' rel='nofollow'>Второй - эндпойнт который не отменяет запрос
Но больше всего интересно не с точки зрения клиента, а с точки зрения сервера.
В проекте BookLibrary у меня настроена сборка метрик с помощью OpenTelemetry, поэтому можно посмотреть как долго в итоге эти запросы отрабатывались, а значит тратили процессорное время.
Смотрим на 'https://t.me/sh_dotnet/103?comment=336' rel='nofollow'>http_server_request_duration_seconds_sum, это суммарное кол-во секунд которое затратил сервер в разбивке по эндпойнтам.
Первый - 596s / 2006 = 298 ms на один запрос
Второй - 2094s / 2000 =1047ms на один запрос т.е. здесь тратятся ресурсы значительно больше
Бенчмарки и 'https://t.me/sh_dotnet/103?comment=333' rel='nofollow'>доп. материал в комментариях.
Основная её цель - иметь источник уведомлений о том, что результат работы которую выполняет ваш API или метод больше никто не ждет. Грустно, но такое бывает.
Начну чуть издалека :)
Существует миф что сеть стабильна. Но это не так. К тому же сейчас очень многие применяют микросервисную архитектуру, которая намного более интенсивно использует сеть. А значит больше точек отказа.
Еще обычно всё хостится в k8s, что еще больше усложняет топологию и перед вашим микросервисом может быть 5-6 балансировщиков и прокси, а то и больше :)
Типичный маршрут запроса в микросервис который расположен допустим в Yandex Cloud Managed k8s:
Yandex Network LoadBalancer -> k8s gateway / ingress -> k8s service -> ApiGateway -> k8s service -> ваш микросервис
Перед всем этим можно еще подключить защиту от DDoS (в виде еще одного прокси).
Часто еще делают AuthN/AuthZ прокси перед ApiGateway.. или еще Service Mesh.
Помимо этого в компании могут быть еще общие внешние балансировщики и вполне вероятно что даже и не один.
Так вот цепочек сетевых вызовов много, сеть нестабильна и на любом вызове может произойти ошибка, таймауты или просто пользователь закрыл вкладку браузера или перешел на другую страницу.
Во всех этих случаях, о результатах работы сервера уже никто не узнает и не ждет.
Как HTTP отменяет запросы?
HTTP/1.x - клиент просто закрывает TCP соединение
HTTP/2 - отправляет фрейм RST_STREAM, чтобы отменить запрос (стрим), но не закрывать соединение.
HTTP/3 - аналогично HTTP/2
Если погрузиться в исходники Kestrel, можно найти обработку этих ситуаций и вызов CancellationTokenSource.Cancel(),
CancellationToken которого пробрасывается в HttpContext.RequestAborted или в параметр экшена контроллера.
Нам надо только пробросить его дальше в наши методы и обязательно туда где есть IO операции или тяжелые CPU bound задачи.
Например HTTP, запросы в бд, чтение файлов. Чтобы если такая ситуация произойдет, мы могли отменить тяжелые операции и быстро освободить ресурсы на что-нибудь полезное.
Помимо экономии ресурсов, отмена запросов позволяет в некоторых случаях сделать систему более консистентной.
Бывало ли у Вас такое, что вы отправляете во внешнюю систему POST запрос на создание ресурса, ловите таймаут, делаете ретрай с помощью Polly, а потом внезапно обнаруживаете, что создалось 2 (или более) ресурсов, вместо одного.
Такое часто возникает когда в API не предусмотрели отмену запроса. Или если запрос был прерван по другой причине и Вы не успели прочитать успешный ответ от сервера и сделали повторный запрос.
Эта проблема решается реализацией идемпотентности, о котором я расскажу в следующий раз.
Так вот я провел бенчмарк, на простых HTTP эндпойнтах, которые просто ждут 1 секунду и отдают 200 OK. В одном используется CancellationToken, а в другом нет.
Сетап бенчмарка простой - 250VUs, 1 сек sleep между итерациями.
Каждую итерацию отправляется запрос и отменяется спустя случайное кол-во мс (от 1 до 600).
Так как тестируемый эндпойнт выполняется минимум 1000 мс, то все запросы гарантированно завершаются ошибочным ответом (499).
Результаты бенчмарков с точки зрения клиента не имеют особого смысла, так как запрос отправляется и отменяется достаточно быстро и результаты будут одинаковыми.
'https://t.me/sh_dotnet/103?comment=334' rel='nofollow'>Первый - эндпойнт который корректно отменяет запрос
'https://t.me/sh_dotnet/103?comment=335' rel='nofollow'>Второй - эндпойнт который не отменяет запрос
Но больше всего интересно не с точки зрения клиента, а с точки зрения сервера.
В проекте BookLibrary у меня настроена сборка метрик с помощью OpenTelemetry, поэтому можно посмотреть как долго в итоге эти запросы отрабатывались, а значит тратили процессорное время.
Смотрим на 'https://t.me/sh_dotnet/103?comment=336' rel='nofollow'>http_server_request_duration_seconds_sum, это суммарное кол-во секунд которое затратил сервер в разбивке по эндпойнтам.
Первый - 596s / 2006 = 298 ms на один запрос
Второй - 2094s / 2000 =1047ms на один запрос т.е. здесь тратятся ресурсы значительно больше
Бенчмарки и 'https://t.me/sh_dotnet/103?comment=333' rel='nofollow'>доп. материал в комментариях.