Комментарии 10
Уважаемый автор, нижайшепрошу прощения, но мне кажется, что вы проиграли пошли не туда ещё на этапе "Подготовка приложения".
Дело в том, что вам стоило задуматься и (вполне закономерно) осознать, что встреченная вами ситуация не так проблематична, как кажется на первый взгляд.
Видите ли, ее можно решить написав небольшую обёртку с DI (мы же хотим красиво), поставив лок против race condition, и все красиво запаковать в минимальное количество абстракций.
В таком виде бизнес-код можно будет писать хоть прямо в этих самых "хендлерах", обернув их, разве что, в делегат.
Наверняка можно и так, но проблему долгого запуска при холодном старте это всё равно не решит.
А так - да, тоже одно из решений. В целом я уверен, что есть и другие решения, которые можно применить. Я описал одно из них. В чём проигрыш - не понял, ну ладно, Вам виднее.
По поводу "мы же хотим красиво" - не знаю, как по мне так было бы красиво, если бы Яндекс Функции позволяли не писать такой костыль, как Вы предложили, а поддерживали бы stateless из коробки.
Решить конечно можно, но чтобы решить надо знать, в то время как ASP .NET нас приучил, что оно stateless из коробки и каждый запрос выполняется в своем изолированном scope и общие моменты надо явно проговаривать как singleton, а тут получается наоборот и это контринтуитивно.
Признаться, яннп, как должно выглядеть ваше решение и причём тут race condition. Но если вы пришлёте работающий Hello, world — это будет действительно полезный комментарий. Уверен, многие скажут за него спасибо. Да и я тогда в статью ваше решение добавлю. С указанием авторства, разумеется. Тем более раз обёртка небольшая - не должно занять много времени
Учитывая ваш текст из статьи, описываемый вами .NET хост просто подключает вашу библиотеку к себе в аппдомен и держит его там, дёргая ваши методы через рефлексию (или кодген). Благодаря этому наблюдению у меня сложилась картина, как можно это использовать без какой-либо боли, описанной вами в статье (но я мог что-то пропустить, так что жду обратной связи)
Вот микропример того, что я имел ввиду: https://pastebin.com/bG6DyJxn
Что такой подход даёт:
App.Run
можно вызывать из любых потоков, в т.ч. из нескольких одновременно.В
BuildContainer
инициализируете ваш DI, этот код будет запущен лишь один раз на весь хостовый процесс/аппдомен.При любом вызове вы получаете ссылку на
IServiceProvider
, который находится в скоупе, который будет успешно закрыт после выполнения вышего делегата.
Как использовать: рисуете свой хендлер, внутри оборачиваете свой бизнес-код в делегат:
public class Handler
{
public string FunctionHandler(int arg)
{
return App.Run(provider =>
{
// Здесь бизнес-код, все сервисы доступны через provider.
return $"Hello world_{arg}";
});
}
}
Приветствую! Спасибо за ответ, идею понял.
На мой взгляд, добавление такой абстракции и регистрация сервисов в serverless-приложениях делает их слишком тяжеловесными. Основная идея таких приложений - быть вызванными, один раз быстро отработать и умереть до следующего вызова. Идея же регистрации сервисов внутри приложения предполагает, что приложение будет долгоживущим. В Яндекс Функциях такой вариант возможен при очень высокой нагрузке на функцию, когда вызовы происходят настолько часто, что подготовленный экземпляр существует всегда. Но в таком случае разработчик скорее всего захочет максимально оптимизировать время работы функции и будет наоборот отказываться от любых дополнительных абстракций.
В любом случае я протестировал ваше решение в Яндекс Функциях. Я добавил весь пример из https://pastebin.com/bG6DyJxn, а ещё добавил класс:
internal class SomeClassFromLibrary
{
internal static int Counter { get; set; }
}
Это воспроизводит ситуацию, когда какой-то класс в нашем приложении или подключенных библиотеках сохраняет информацию из предыдущего запроса. Затем я добавил обращение к этому классу в ваш пример:
namespace StatelessTest;
public class Handler
{
public string FunctionHandler(int arg)
{
return App.Run(provider =>
{
// Здесь бизнес-код, все сервисы доступны через provider.
SomeClassFromLibrary.Counter += arg;
return $"Counter: {SomeClassFromLibrary.Counter}";
});
}
}
И ожидаемо получил тот же результат, что и при использовании встроенного .NET-окружения Яндекс Функций. При частых обращениях, когда вызовы обрабатывает подготовленный экземпляр, значение Counter будет накапливаться, т.е. stateless не гарантируется.
Более того, среднее время работы функции при холодном старте увеличилось до 317 ms против 216 ms при использовании встроенного .NET-окружения. И это мы ещё не зарегистрировали и не использовали ни одного сервиса, просто добавили для них обёртку.
Итог тестирования: проблема stateless для подготовленного экземпляра сохраняется, время холодного старта ухудшилось.
Но я очень благодарен за проявляемый интерес, если будут ещё варианты решения описанных проблем - готов и их протестировать и сравнить с имеющимися решениями!
Боюсь вас снова разочаровать, но вы неправильно протестировали. Моё решение не модифицирует магически поведение статичных классов. И не предполагает, что вы будете их использовать (так же, как бы вы не использовали их в обычной современной архитектуре любого приложения).
На мой взгляд, добавление такой абстракции и регистрация сервисов в serverless-приложениях делает их слишком тяжеловесными. И это мы ещё не зарегистрировали и не использовали ни одного сервиса, просто добавили для них обёртку.
Если вам не нужны сервисы, то вы их просто не регистрируйте. Но если (в контексте вашеих задач) они вам нужны, то вам в любом случае от них никуда не деться, с учетом того, что выше приложение постоянно выключают. Т.е. этот аргумент не играет ни в чью пользу.
когда вызовы происходят настолько часто, что подготовленный экземпляр существует всегда. Но в таком случае разработчик скорее всего захочет максимально оптимизировать время работы функции и будет наоборот отказываться от любых дополнительных абстракций.
Не понял. Если у вас долгоживущее приложение (из-за частых запросов), то регистрация сервисов (=стартап) как раз будет происходить лишь единожды.
Интересно сравнить с другими, аналогичными, сервисами. Может, там так же работает?
Ценное замечание, спасибо!
Действительно, как минимум в некоторых других популярных решениях с .NET похожая ситуация. По крайней мере так было раньше. Я осознанно не включал это в разбор, чтобы не сводить дискуссию до уровня "Если сосед обосрался, то и мне можно". Цель публикации — подсветить конкретные проблемы с .NET на конкретной платформе. Если мои решения помогут при работе с другими платформами — так даже лучше.
А вообще было бы интересно посмотреть на сравнительный анализ запуска .NET-приложений в разных сервисах, согласен.
Что не так с .NET в Yandex Cloud Functions