Start Debugging

Kestrel отказывается от исключений в HTTP/1.1-парсере в .NET 11

Парсер HTTP/1.1-запросов Kestrel в .NET 11 заменяет BadHttpRequestException на структуру результата, сокращая накладные расходы на некорректные запросы до 40%.

Каждый некорректный HTTP/1.1-запрос, попадавший в Kestrel, бросал BadHttpRequestException. Это исключение выделяло трассировку стека, разматывало стек вызовов и ловилось где-то выше — всё ради запроса, который никогда не дал бы валидный ответ. В .NET 11 парсер переключается на путь без выбрасывания исключений, и разница измерима: на 20-40 % выше пропускная способность в сценариях с частым некорректным трафиком.

Почему исключения были дорогими

Бросить исключение в .NET не бесплатно. Среда выполнения захватывает трассировку стека, обходит стек вызовов в поисках соответствующего catch и выделяет объект исключения на куче. Для корректного запроса это никогда не срабатывает, поэтому вы не замечаете. Но сканеры портов, неправильно настроенные клиенты и злонамеренный трафик могут продавливать тысячи плохих запросов в секунду. Каждый платил полный налог на исключение.

// Before (.NET 10 and earlier): every parse failure threw
try
{
    ParseRequestLine(buffer);
}
catch (BadHttpRequestException ex)
{
    Log.ConnectionBadRequest(logger, ex);
    return;
}

В горячих путях try/catch с частыми бросками становится узким местом пропускной способности.

Подход с использованием структуры результата

Парсер .NET 11 вместо этого возвращает лёгкую структуру результата:

// After (.NET 11): no exception on parse failure
var result = ParseRequestLine(buffer);

if (result.Status == ParseStatus.Error)
{
    Log.ConnectionBadRequest(logger, result.ErrorReason);
    return;
}

Структура несёт поле Status (Success, Incomplete или Error) и строку причины ошибки, когда это уместно. Никакого выделения на куче, никакого разматывания стека, никаких накладных расходов на блок catch. Валидные запросы не видят изменений, потому что они уже шли по успешному пути.

Когда это важно

Если ваш сервер сидит за балансировщиком нагрузки, который делает health-check сырым TCP, или если вы выставляете Kestrel напрямую в интернет, вас постоянно бьют некорректные запросы. Развёртывания-приманки, API-шлюзы, обрабатывающие смешанные протоколы, и любой сервис, выставленный под сканирование портов, выигрывают.

Улучшение полностью внутреннее для Kestrel. Нет изменения API, нет флага конфигурации, нет opt-in. Обновитесь до .NET 11 и парсер по умолчанию быстрее.

Другие победы производительности в .NET 11

Это не единственное сокращение выделений в .NET 11 Preview. Middleware HTTP-логгирования теперь пулит свои экземпляры ResponseBufferingStream, сокращая выделения на запрос, когда включено логгирование тела ответа. В сочетании с изменением парсера, .NET 11 продолжает шаблон команды среды выполнения превращать тяжёлые на исключениях горячие пути в потоки результатов на основе структур.

Если вы хотите увидеть влияние на свою рабочую нагрузку, запустите бенчмарк до/после с Bombardier или wrk, инжектируя процент некорректных запросов. Изменение парсера прозрачно, но цифры должны говорить сами за себя.

< Назад