Безкоштовна онлайн-конференцiя з Python від fwdays. 14 грудня. Реєструйся!
×Закрыть

Разработка highload-системы на .NET Core: задачи и их решения

Меня зовут Андрей Губский. На данный момент я являюсь Software Architect в компании Video Intelligence, где занимаюсь проектом vi stories. Также я — CTO проекта Торф ТВ и создатель //devdigest. Коммерческой разработкой занимаюсь примерно с 2008 года. Окончил КПИ, кандидат технических наук. Являюсь Microsoft MVP в номинации Developer Technologies.

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

Немного про наш продукт: vi stories

vi stories представляет собой систему контекстной рекомендации видео. Суть ее заключается в следующем: на сайт устанавливается небольшой скрипт, который позволяет находить наиболее актуальное и релевантное видео для контента текущей страницы. Как это работает? Пользователь заходит на сайт, где установлен скрипт. В момент, когда скрипт загружается, отправляется запрос на сервис, отвечающий за подбор видео для текущей просматриваемой страницы. Информацию о самом пользователе мы при этом не собираем, так как она не важна для наших алгоритмов, да и с GDPR лучше не связываться.

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

Подобная технология позволяет увеличить количество полезного контента на странице и дать больше актуальной информации посетителю.

Что такое высоконагруженная система

Перед тем как перейти, собственно, к рассмотрению проблем высоконагруженных систем, сделаем маленькое лирическое отступление, в котором рассмотрим, что именно мы понимаем под понятием «высоконагруженная система».

У многих разработчиков уже довольно давно сложился стереотип высоконагруженной системы, которая работает с миллионами запросов в секунду, состоит из многих сотен серверов с десятками ядер и терабайтами оперативной памяти. Да, в каких-то случаях так и есть. Однако следует понимать, что высокие нагрузки начинаются не с миллионов RPS, а с того момента, когда ваша система начинает испытывать сложности с обработкой определенного количества RPS (не всегда феноменально большого). И количество это может быть любым: от десяти запросов в секунду до десяти тысяч запросов в секунду и выше. Серверные ресурсы у вас при этом тоже могут быть абсолютно любыми. Это может быть пару виртуалок или целый кластер в Kubernetes. Определяющим в высоконагруженной системе является тот факт, что горизонтальное масштабирование становится более выгодным, чем вертикальное.

Когда я начал описывать задачи, которые нам нужно было решать на разных этапах работы над проектом, я долго думал, каким образом упорядочить темы: по значимости, в хронологическом порядке или сгруппировав по технологиям. Но в итоге, спустя полдня, я пришел к наилучшему варианту упорядочивания. Все темы отсортированы в строгом соответствии с рандомом.

Задача № 0. Как из джавистов сделать дотнетчиков так, чтобы они этого не заметили

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

Существовал, правда, небольшой нюанс: тот проект был на Java, и, соответственно, основными инструментами коллег были Ubuntu, macOS и IntelliJ IDEA. К счастью для ребят, часть проекта уже была переведена на .NET Core и им не пришлось изучать нюансы установки Windows через Boot Camp. Поскольку IntelliJ IDEA от Rider на расстоянии больше метра вряд ли кто сможет отличить, а C# один в один похож на Java, какой она будет примерно в 2045 году — новые члены нашей команды почти не почувствовали подмены.

Интересно, что в какой-то момент в нашей команде использовались практически все существующие IDE для работы с .NET: Visual Studio, Rider, Visual Studio Code и Visual Studio for Mac.

Что касается миграции на .NET Core, проект мы переводили постепенно. Сначала в .NET Standard мы сконвертировали проекты, которые отвечали за описание базовых классов предметной области и всю ту часть, которая могла быть покрыта юнит-тестами. Затем постепенно стали переводиться отдельные сервисы. Из проектов типа Windows Services получились очень компактные и удобные консольные приложения, которые затем были аккуратно перенесены на Linux.

Итоги и выводы

Благодаря своевременному переходу на .NET Core нам удалось в очень сжатые сроки организовать процесс работы таким образом, что новые члены команды практически не почувствовали дискомфорта при смене технологического стека. При этом мы смогли в полной мере воспользоваться преимуществами контейнеризации и кросс-платформенности, не потеряв при этом ни одного из преимуществ платформы .NET.

Задача № 1. Выбор стека и определение приоритетов разработки

С самого начала, когда мы только начали разрабатывать прототип системы, возник вопрос, какой стек выбрать. С одной стороны, я на тот момент уже был готов стартовать на .NET Core, однако команда, в которой я работал, и СТО компании еще не были готовы к тому, чтобы начинать разработку на достаточно молодой (на их взгляд) платформе. Поэтому мы начали работу с теми технологиями, которые были хорошо знакомы команде и использовались компанией в предыдущих проектах.

Оригинальный стек выглядел примерно так:

  • Windows Server;
  • Microsoft .NET 4.6.2;
  • Couchbase;
  • IIS;
  • MSMQ;
  • MySQL;
  • Elasticsearch;
  • хостинг — Amazon EC 2.

Это довольно серьезно отличается от того, с чем мы работаем сейчас:

  • Linux;
  • .NET Core 2.2;
  • Redis;
  • nginx;
  • Rabbit MQ;
  • MariaDB;
  • Elasticsearch;
  • развернуто почти все в Kubernеtes.

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

Через некоторое время после того, как мы переехали на .NET Core, от новых участников нашей команды (о них — чуть ниже) поступило предложение переехать в Kubernetes. Это был как раз тот момент, когда технология, которую мы планировали применить, была команде не знакома, но бенефиты перевесили страх нового.

Что мы получили, переехав в K8S?

  • Независимость от облачного провайдера. Сейчас практически любой из крупных провайдеров предлагает Kubernetes as a service: Azure, AWS, Digital Ocean, Google Cloud.
  • Возможность быстрого деплоя. Так как все построено на докер-контейнерах, то откатить версию или развернуть на время какую-то специфическую версию — дело нескольких секунд.
  • Вся инфраструктура описана в скриптах. Эти описания могут храниться в системе контроля версий.

Условный минус — практически всем участникам команды пришлось изучить основы того, как работает Kubernetes, узнать, что такое Helm и Terraform.

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

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

  • Отказались от ручного администрирования Elasticsearch, перейдя на сервис от AWS. Стабильность и возможности масштабирования Amazon Elasticsearch Service полностью нас устраивали, поэтому мы смогли освободить команду от необходимости ручного управления и обслуживания кластера.
  • Мы также отказались от ручного обслуживания кластера Couchbase, перейдя на Amazon ElastiCache, с которым работаем по Redis-совместимому протоколу.
  • Убрали in-house библиотеку для логирования, которая была разработана в компании и использовалась до этого в ряде проектов. Ее заменили NLog.
  • Отказались от различных реализаций (как собственных, так и сторонних) библиотек для периодического выполнения задач, полностью переложив эти задачи на cron. Чем также сделали архитектуру многих сервисов проще. А чем проще решение, тем меньше в нем потенциальных точек нестабильности.

Итоги и выводы

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

Задача № 2. Минимизировать побочные эффекты при работе над проектом (почему иммутабельность — это хорошо)

Итак, почему? Вот несколько основных причин:

  1. Нет неожиданных побочных эффектов по ходу выполнения кода — никто не изменит то, что не должно быть изменено. Не нужно держать в голове куски системы, в которых объект мог быть изменен.
  2. Иммутабельные коллекции потокобезопасны.
  3. Концепция иммутабельности является одной из концепций ФП и позволяет более четко описывать логику процессов и проще тестировать код.

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

В .NET Core (да и .NET Framework) уже есть набор иммутабельных коллекций из коробки:

  • ImmutableArray
  • ImmutableArray<T>
  • ImmutableDictionary
  • ImmutableDictionary<TKey,TValue>
  • ImmutableHashSet
  • ImmutableHashSet<T>
  • ImmutableList
  • ImmutableList<T>
  • ImmutableQueue
  • ImmutableQueue<T>
  • ImmutableSortedDictionary
  • ImmutableSortedDictionary<TKey,TValue>
  • ImmutableSortedSet
  • ImmutableSortedSet<T>
  • ImmutableStack
  • ImmutableStack<T>

Чтобы использовать эти коллекции, достаточно подключить NuGet-пакет System.Collections.Immutable.

Итоги и выводы

На данный момент при работе со списками практически все методы в нашей системе возвращают и принимают иммутабельные объекты. Тип возвращаемого объекты обычно IReadOnlyCollection<T>. Это позволило значительно упростить понимание работы и взаимодействия модулей системы между собой. Разработчики всегда понимают, какие данные в каком состоянии.

Задача № 3. Работа с пиковыми нагрузками

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

В своих проектах подобную проблему я решал довольно давно. Еще со времен учебы в университете у меня была небольшая библиотека, написанная на .NET 4.0, из которой я время от времени брал небольшие части кода для других проектов. Я давно планировал провести рефакторинг этого кода, почистить его и оформить в виде отдельного мини-фреймворка, позволяющего красиво и с наименьшими затратами ресурсов решать задачу наблюдения за состоянием системы. Потратив несколько вечеров и пару выходных, я таки привел этот код в порядок и выложил его на GitHub. В данном случае я не буду подробно останавливаться на реализации этой библиотеки, так как она уже описана в другой статье. Опишу лишь базовые принципы.

Для наглядности определим ряд сущностей, которыми будем оперировать:

  • Probe — отвечает за проверку состояния одного из показателей системы.
  • Spectator — опрашивает один или несколько датчиков. Изменяет свое состояние в зависимости от текущих показаний датчиков.
  • State calculator — на основе журнала метрик вычисляет текущее состояние.
  • State journal — набор показателей каждого из датчиков с указанием времени опроса.

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

Каждый датчик может быть закреплен за одним или несколькими наблюдателями. Каждый наблюдатель может работать с одним или несколькими датчиками. Наблюдатель должен реализовывать интерфейс ISpectator и генерировать события в случае изменения состояния или опроса датчиков.

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

Итоги и выводы

Использование этой библиотеки помогло нам значительно увеличить устойчивость системы к внезапным пиковым нагрузкам. При этом имеющийся вариант реализации отлично подходит при внедрении в системы, построенной на базе микросервисной архитектуры. Оптимальный вариант интеграции — через использование принципа внедрения зависимостей, когда процесс внедрения датчиков реализуется с помощью IoC-контейнера, а наблюдатели представлены в виде синглтонов, где единственный экземпляр каждого из наблюдателей может быть доступен различным классам модулей и сервиса.

Задача № 4. Отладка и логирование

Наверное, нет ни одной более-менее крупной системы, где бы перед разработчиками не стояла задача эффективного логирования работы системы. В нашем проекте мы используем довольно типичную связку для работы с логами — ELK (Elasticsearch, Logstash, Kibana). Не буду вдаваться в детали настройки самой инфраструктуры логирования — на эту тему написано громадное количество руководств. Остановлюсь только на одной из главных проблем, с которой мы столкнулись при отладке. Когда система состоит из множества микросервисов, иногда бывает довольно трудно отследить, как, когда и какое именно действие повлияло на состояние системы. Когда количество самих записей превышает сотни тысяч и иногда доходит до миллионов, разобраться в таком массиве информации становится еще сложнее.

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

Работает это следующим образом: для каждого запроса к API формируется TransactionId, представляющий собой GUID. Далее, все операции внутри API, относящиеся к обработке этого запроса, используют данный идентификатор при записи логов. Таким образом, всегда можно понять, какие операции к какому конкретно запросу относятся. Однако это не все.

Этот идентификатор используется не только в рамках отдельного сервиса, но в целом в системе. При генерации команд, отправляемых в очередь (в нашем случае, как я уже писал выше, мы используем Rabbit MQ), каждая команда также содержит TransactionId. Более того, каждая добавленная в кэш запись также помечается TransactionId. Это позволяет отследить не только путь обработки каждого из отдельных запросов, но также понять, что текущий запрос отдал вам данные, которые были закэшированы в результате обработки другого запроса.

Итоги и выводы

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

Если в своем проекте вы используете или рассматриваете вариант использования Serilog, обратите внимание на этот раздел документации. Также будет интересен этот проект.

Задача № 5. Минимизация технического долга

Технический долг — одна из самых типичных проблем любого проекта. К сожалению, вред от него не всегда очевиден. В большинстве случаев его рассматривают только в разрезе устаревших технологий и необходимости тратить время на их поддержку. Но у технического долга есть и еще одна сторона, как ни странно, техническая. Это зависимость от устаревших пакетов и сторонних ресурсов. Да, большинство современных репозиториев пакетов (таких как NuGet в .NET, NPM в Node.js и других) обещают вам, что будут хранить все версии всех пакетов, которые когда-либо туда попали. Однако бывают и исключения. Кроме того, в скриптах сборки некоторых пакетов могут быть зависимости на внешние хранилища, которые и вовсе не гарантируют «вечную» сохранность файлов.

Итоги и выводы

С техническим долгом приходится бороться постоянно. В принципе, победить эту проблему раз и навсегда невозможно. Но если вы поддерживаете свои зависимости в актуальном состоянии — вы уже на верном пути. Главное — осознавать, что в живом проекте нет модуля, написанного «раз и навсегда». Нужно регулярно осматривать систему и быть готовым провести небольшое техническое обслуживание, чтобы избежать решения авральных проблем в будущем.

В завершение

Что в завершении всего этого хочется сказать? Кто бы что ни говорил, .NET Core уже полностью готов к использованию на реальных проектах, в том числе на проектах с высокой нагрузкой. Это, кстати, в точности совпадает с мои прогнозом из статьи 2016 года .

.NET Core удалось сохранить все преимущества классического .NET Framework и при этом обзавестись всеми плюсами кросс-платформенности. Думаю, это один из самых удачных примеров экспансии от Microsoft в новый для себя сегмент разработки. За короткое время платформа смогла пройти все болезни роста и сейчас надежно закрепилась на рынке.

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

LinkedIn

49 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

И какой у вас SLA на время ответа?

Спасибо, интересный опыт. И за devdigest тоже спасибо, регулярно читаю его :)

Кто бы что ни говорил, .NET Core уже полностью готов к использованию на реальных проектах, в том числе на проектах с высокой нагрузкой.

Я бы даже сказал, что как раз .NET Framework не очень подходит для проектов с высокой нагрузкой. Похоже что netcore это как раз шаг во взрослый мир распределенных и нагруженных ситсем, за пределы энтерпрайз решений и сайтов-поделок на asp.net.

devdigest тоже спасибо, регулярно

Очень рад!

Что касается .NET Core и .NET Framework, то в следующей версии .NET 5 обе платформы будут объеденены. Причем объединение произойдет как раз на базе .NET Core

следующей версии .NET 5 обе платформы будут объеденены.

Не назвав би .NET 5 обєдненням .Net Framework i .Net Core, це скоріше наступна версія .Net Core з ребрендингом🤔

к слову .Net 5 это еще не финальное название и это вполне может быть .Net Core 5. .Net комманда еще обсуждает как он должен называться

Я бы даже сказал, что как раз .NET Framework не очень подходит для проектов с высокой нагрузкой

подходит, в 2014 писал сервис для обработки 100к реквестов в секунду, который работал на 2гб ОЗУ и 1ггерце (самый дешёвый инстанс в ажуре на тот момент). Насколько я помню можно было удвоить производительность, но это кроме меня никого уже не интересовало

Было бы интересно узнать побольше про то, как вы это делали и как это все работало

как бы по компактнее... в общем это стейтлесс сервис для форекса, котировки по валютам от сервера на клиенты — это основной поток трафика, по типу это дробное число с точностью 5 или 6 знаков. валютных пар было под сто шутк. каждую секунду 4 обновления каждой валютной пары. клиент может управлять какие валютные пары ему нужны какие нет, клиентов на каждый инстанс 5к. если просто передавать это как децималы и ключ в виде инта (8+8+4=20 байт), то умножив на 4 раза в сек и 100 пар, то выходит 8000байт, за сутки получается 660 мб каждому клиенту, умножаем на кол-во клиентов получаем ~3.2тб в день исходящего трафика. мое решение было на основе сокетов т.к. вариантов больше нет, свой транспортный алгоритм и алгоритм сжатия потому что даже 100гб трафика в день на то время стоил очень дорого, я улучшил расклад на порядок и общий размер трафика точно был менее 10гб (сколько точно я не помню). ну и пачка достаточно простых оптимизаций вроде чтобы сложность алгоритма была везде минимальной, полное отсутствие локов и подобных штук, только атомарные операции где нужно синхронизироваться несколькими потоками, где-то структуры вместо классов чтобы съекономить на оверхеде класса и тому подобные мелочи.

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

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

P.S.: ах да.... откуда 100к реквестов в секунду забыл написать :) ведь просто посылая апдейт по всем валютным парам раз в 250мс получится 4шт * 5000клиентов = 20к реквестов. я в момент написания PoC (ну и как сервис был готов это подтвердилось) увидел что смогу стабильно держать в каждом сервисе 25к клиентов, ну либо чаще слать апдейты, абы не превышать порог в 100к реквестов. а за этим порогом были уже новые проблемы, но я уверен что тоже решаемые

мое решение было на основе сокетов т.к. вариантов больше нет, свой транспортный алгоритм и алгоритм сжатия

.net framework тебе определенно сильно помог с твоей задачей.

вообще это слабо влияло на производительность, просто даже сейчас 80тб трафика влетит в копеечку, что говорить про то время когда клауды были не так популярны и стоили дороже

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

аглоритм сжатия на все языках выглядел бы одинаково, алгоритмы они такие...

на самом деле совсем не одинаково. на таком низком уровне уже речь идет о микрооптимизациях специфичных для возможности языка, платформы и даже железа.
не припомню что бы в C#/.NET Framework экземпляра 2014 года, была хоть какая-то неэкспериментальная поддержка SIMD или неаллоцирующий апи для работы с буферами, сериализацией, возможность работать со стеком прямо в managed коде и тому подобное, похоже тебе, что либо из этого, кроме мелких оптимизацией с пулом обьектов и «структура вместо класса» не понадобилось. странно, что это работало быстрее чем стандартные решения в .net, которые написаны все на плюсах.

на самом деле совсем не одинаково. на таком низком уровне уже речь идет о микрооптимизациях специфичных для возможности языка, платформы и даже железа.
не припомню что бы в C#/.NET Framework экземпляра 2014 года, была хоть какая-то неэкспериментальная поддержка SIMD или неаллоцирующий апи для работы с буферами, сериализацией, возможность работать со стеком прямо в managed коде и тому подобное

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

похоже тебе, что либо из этого, кроме мелких оптимизацией с пулом обьектов и «структура вместо класса» не понадобилось. странно, что это работало быстрее чем стандартные решения в .net, которые написаны все на плюсах.

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

любые улучшения перформанса делаются даже без ансейф кода

видно разработчики Microsoft c этим не особо согласны, раз добавили в язык поддержку конструкций и типов для работы с памятью, что минимально задействуют традиционный способ работы с аллокатором и переписали на это весь BCL.

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

Стеб над подделкой зашел хоть?

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

он очень быстрый, просто пишут такое что быстро не работает, тоже самое и на с++, ничего такого в нем нет сильно уступающего по скорости, в пример вечяно дают чистку памяти, но нет никакой разницы будешь делать это сам сразу или гк раз в какое-то время

Все хорошо, но зря велосипед городили с метриками. Есть прекрасный стандарт openmetrics и куча систем его поддерживает. Лучше бы взяли prometheus или elastic APM.

Я так понимаю, этот комментарий касается пункта «Задача № 3. Работа с пиковыми нагрузками»? Prometheus мы уже используем (метрики отдаем через www.app-metrics.io). Но он больше подходит для сбора и анализа метрик «снаружи системы», в данном же случае решалась задача, когда система должна внутри реагировать на изменение собственного состояния.

Я на Промитиусе замечательно реализовывал сбор и внутренних метрик.

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

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

Сколько RPS держите, как load тестирование делаете, и как рекоммендации строите на запросе(бизнес логика)?

Итоги и выводы
На данный момент при работе со списками практически все методы в нашей системе возвращают и принимают иммутабельные объекты. Тип возвращаемого объекты обычно IReadOnlyCollection. Это позволило значительно упростить понимание работы и взаимодействия модулей системы между собой. Разработчики всегда понимают, какие данные в каком состоянии.

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

Нагрузочное тестирование проводили, используя как самописные инструменты, так и Gatling. Сейчас вот посматриваю в сторону этой штуки: nbomber.com

Механизм того, как реализован алгоритм рекомендаций видео раскрывать я, к сожалению, не могу.

Насчет иммутабельности. Да, иммутабельность полезна при работе с несколькими потоками, но в данном случае использование иммутабельности было вызвано несколько другими причинам:
— код, работающий с иммутабельными объектами проще для понимания;
— такой код проще тестировать (как следствие предыдущего пункта)
— при работе в команде, иммутабельный код позволяет уменьшить энтропию в коде, вводя дополнительные ограничения на изменение данных на различных участках бизнес логики.

Correlation ID — это вообще одно из лучших изобретений человечества).

> Поскольку IntelliJ IDEA от Rider на расстоянии больше метра вряд ли кто сможет отличить, а C# один в один похож на Java, какой она будет примерно в 2045 году — новые члены нашей команды почти не почувствовали подмены.

Хорошо подкололи

Это чисто дружеский подкол, ведь если бы в свое время Microsoft не создал C#, то велика вероятность, что сегодня я бы писал именно на Java ))

C#, Java один хрен. писал и на том и на том, правда на C# еще очень давно (2.0) но перейти в джаву не составило проблем

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

ведь если бы в свое время Microsoft не создал C#

это сделали бы солдаты НАТО)))

java 2045 это скорее kotlin или scala, которым по количеству фич C# сильно проигрывает.

С Kotlin немного работал — приятный в целом язык, Scala — это же функциональщина, насколько я могу судить, так что тут сравнивать имеет смысл скорее с F#, чем с C#.

Э-э, ты сначала расскажи чем в 2045-м C# от F# отличаться будет.

Ну начнём с того что F# и так полуживой в плане экосистемы, а последние фичи типа матчинга С# мигрируют после их успешного применения в продакшн-коде на Scala. Я не против того чтобы решётка украла побольше фич из скалы.

Если это как-то отвечает на мой вопрос, то помоги понять как.

Планируете мигрировать на .NET Core 3.1?

Вопрос был больше в использовании новых возможностей языка/рантайма, а не просто в переключении TargetFramework :)

Там не просто поменять Target Frameword, все-таки там много чего поменяли, но тем не менее, не очень сложно. Но обновляться, конечно, стоит, как минимум, они много чего улучшили для работы в контейнерах. Хотя self-contained deployment теперь весит намного больше, потому что они убрали возможность выбирать базовые пакеты.

В этом кстати, еще одно преимущество микросервисов. Новые пишутся сразу на новейшей версии, а старые мигрируются по мере изменений и приоритетов.

якщо у вас хай лорд проект і кубернетіс, то чому ви не авто-скейлитесь при пікових нагрузках, а обрізаєте функціонал

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

Спасибо. Написано просто и понятно.

вертикальное масштабирование становится более выгодным, чем вертикальное

Очепятка

Не в ту сторону поправили :) Щас написано

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

а должно быть наоборот.

упс, заработались :) Спасибо!

Подписаться на комментарии