Команда LMAX представила презентацию о том, как им удалось выполнить 100 тыс. Запросов в секунду с задержкой менее 1 мс . Они подкрепили эту презентацию блогом , техническим документом (PDF) и самим исходным кодом .
Недавно Мартин Фаулер опубликовал отличную статью об архитектуре LMAX и упоминает, что теперь они способны обрабатывать шесть миллионов заказов в секунду, и выделяет несколько шагов, которые команда предприняла, чтобы подняться на другой порядок производительности.
До сих пор я объяснил, что ключ к скорости процессора бизнес-логики - все делать последовательно в памяти. Просто делать это (и ничего глупого) позволяет разработчикам писать код, который может обрабатывать 10K TPS.
Затем они обнаружили, что, концентрируясь на простых элементах хорошего кода, можно довести это до 100K TPS. Для этого просто нужен хорошо продуманный код и небольшие методы - по сути, это позволяет Hotspot лучше выполнять оптимизацию, а процессорам - эффективнее кэшировать код во время работы.
Потребовалось немного больше сообразительности, чтобы подняться еще на порядок. Команда LMAX нашла несколько полезных вещей для этого. Один из них заключался в написании пользовательских реализаций Java-коллекций, которые были разработаны с учетом удобства кэширования и осторожности с мусором.
Еще один метод достижения такого высокого уровня производительности - это уделение внимания тестированию производительности. Я давно заметил, что люди много говорят о методах повышения производительности, но одна вещь, которая действительно имеет значение, это проверить это
Фаулер упомянул, что было найдено несколько вещей, но он упомянул только пару.
Существуют ли другие архитектуры, библиотеки, методы или «вещи», которые помогают достичь такого уровня производительности?
источник
Ответы:
Существуют всевозможные методы для высокопроизводительной обработки транзакций, и тот, что в статье Фаулера, является одним из многих на переднем крае. Вместо того, чтобы перечислять набор методов, которые могут или не могут быть применимы к чьей-либо ситуации, я думаю, что лучше обсудить основные принципы и то, как LMAX обращается с большим количеством из них.
Для крупномасштабной системы обработки транзакций вы хотите сделать как можно больше из следующих действий:
Минимизируйте время, затрачиваемое на самые медленные уровни хранения. От самого быстрого до самого медленного на современном сервере у вас есть: CPU / L1 -> L2 -> L3 -> RAM -> Disk / LAN -> WAN. Переход от даже самого быстрого современного магнитного диска к самой медленной оперативной памяти составляет более 1000x для последовательного доступа; произвольный доступ еще хуже.
Минимизируйте или исключите время, потраченное на ожидание . Это означает совместное использование как можно меньшего количества состояний, и, если состояние должно быть общим, по возможности избегая явных блокировок
Распределите рабочую нагрузку. Процессоры не получили гораздо быстрее , в последние несколько лет, но они уже получили меньше, и 8 ядер довольно часто встречаются на сервере. Помимо этого, вы можете даже распределить работу по нескольким машинам, что является подходом Google; Самое замечательное в этом то, что он масштабирует все, включая ввод / вывод.
По словам Фаулера, LMAX использует следующий подход к каждому из них:
Сохраняйте все состояния в памяти все время. Большинство движков баз данных на самом деле будут делать это в любом случае, если вся база данных может поместиться в памяти, но они не хотят ничего оставлять на волю случая, что понятно на торговой платформе в реальном времени. Чтобы справиться с этим, не прибегая к большому риску, им пришлось создать несколько облегченных инфраструктур резервного копирования и восстановления после сбоев.
Используйте очередь без блокировки («прерыватель») для потока входных событий. В отличие от традиционных долговременных очередей сообщений, которые окончательно не блокируются и фактически включают болезненно медленные распределенные транзакции .
Немного. LMAX выбрасывает его под шину на основании того, что рабочие нагрузки являются взаимозависимыми; результат одного меняет параметры для других. Это критическое предостережение, которое Фаулер явно призывает. Они делают некоторые используют параллелизм для того , чтобы обеспечить отказоустойчивости, но все бизнес - логики обрабатывается на одном потоке .
LMAX - не единственный подход к масштабному OLTP. И хотя он сам по себе довольно блестящий, вам не нужно использовать передовые методы, чтобы достичь такого уровня производительности.
Из всех вышеперечисленных принципов, # 3, вероятно, самый важный и самый эффективный, потому что, честно говоря, аппаратное обеспечение дешево. Если вы сможете правильно распределить рабочую нагрузку между полдюжиной ядер и несколькими десятками машин, то предел для традиционных методов параллельных вычислений - это предел . Вы будете удивлены тем, какую пропускную способность вы можете достичь, используя только несколько очередей сообщений и распределенный круговой распределитель. Очевидно, что он не так эффективен, как LMAX - на самом деле даже не близко - но пропускная способность, задержка и экономическая эффективность - это отдельные проблемы, и здесь мы говорим конкретно о пропускной способности.
Если у вас есть те же особые потребности, что и у LMAX - в частности, общее состояние, которое соответствует бизнес-реальности, а не поспешному выбору дизайна - тогда я бы предложил опробовать их компонент, потому что я не видел много иначе это соответствует этим требованиям. Но если мы просто говорим о высокой масштабируемости, то я бы посоветовал вам больше исследовать распределенные системы, потому что они являются каноническим подходом, используемым большинством организаций сегодня (Hadoop и смежные проекты, ESB и смежные архитектуры, CQRS, который Фаулер также упоминания и тд).
Твердотельные накопители также собираются изменить игру; возможно, они уже есть. Теперь у вас может быть постоянное хранилище с аналогичным временем доступа к ОЗУ, и, хотя твердотельные накопители серверного уровня по-прежнему ужасно дороги, они со временем снизятся в цене, как только число пользователей увеличится. Он был тщательно исследован, и его результаты ошеломляют и со временем будут только улучшаться, поэтому концепция «держать все в памяти» намного менее важна, чем раньше. Итак, еще раз, я постараюсь сосредоточиться на параллелизме, когда это возможно.
источник
Я думаю, что самый большой урок, который можно извлечь из этого, состоит в том, что вам нужно начать с основ:
Во время тестирования производительности вы профилируете свой код, находите узкие места и исправляете их одно за другим.
Слишком много людей прыгают прямо к части «почини их один за другим». Они тратят кучу времени на написание «пользовательских реализаций java-коллекций», потому что они просто знают, что причина в том, что их система работает медленно, из-за нехватки кэша. Это может быть способствующим фактором, но если вы перейдете прямо к настройке низкоуровневого кода, вы, скорее всего, упустите большую проблему использования ArrayList, когда вы должны использовать LinkedList, или реальной причины, по которой ваша система работает. Это происходит медленно, потому что ваш ORM загружает потомков объекта и, таким образом, делает 400 отдельных поездок в базу данных для каждого запроса.
источник
Я не буду особенно комментировать код LMAX, потому что я думаю, что он достаточно подробно описан, но вот несколько примеров того, что я сделал, что привело к значительным улучшениям производительности.
Как всегда, это методы, которые следует применять, если вы знаете, что у вас есть проблема и вам необходимо повысить производительность, иначе вы, скорее всего, просто будете проводить преждевременную оптимизацию.
Помогите JIT-компилятору в окончательном создании полей, методов и классов. Final позволяет выполнять конкретные оптимизации, которые действительно помогают JIT-компилятору. Конкретные примеры:
Замените классы коллекций массивами - это приводит к снижению читабельности кода и усложняет его обслуживание, но почти всегда быстрее, поскольку устраняет слой косвенности и извлекает выгоду из множества приятных оптимизаций доступа к массиву. Обычно это хорошая идея для внутренних циклов / кода, чувствительного к производительности, после того, как вы определили его как узкое место, но избегайте иного для удобства чтения!
По возможности используйте примитивы - примитивы существенно быстрее, чем их объектные эквиваленты. В частности, бокс добавляет огромное количество накладных расходов и может вызвать неприятные паузы в GC. Не допускайте упаковки каких-либо примитивов, если вы заботитесь о производительности / задержке.
Минимизируйте блокировку низкого уровня - замки очень дороги на низком уровне. Найдите способы либо полностью избежать блокировки, либо блокируйте на грубом уровне, так что вам нужно будет редко блокировать только большие блоки данных, и низкоуровневый код может продолжаться, не беспокоясь о проблемах блокировки или параллелизма.
источник
final
некоторых JIT это может быть понятно, другие - нет. Это зависит от реализации (как и многие советы по настройке производительности). Согласитесь с распределением средств - вы должны это сравнить. Обычно я обнаружил, что лучше исключить ассигнования, но YMMV.Помимо того, что уже было сказано в отличном ответе от Aaronaught, я хотел бы отметить, что подобный код может быть довольно сложным для разработки, понимания и отладки. «Хотя это очень эффективно ... это очень легко испортить ...», как один из их парней упомянул в блоге LMAX .
Учитывая вышесказанное, я думаю, что те, кто выбирает Disruptor и аналогичные подходы, лучше убедиться, что у них есть ресурсы для разработки, достаточные для поддержки их решения.
В целом, подход Disruptor выглядит довольно многообещающе для меня. Даже если ваша компания не может позволить себе использовать его, например, по причинам, указанным выше, подумайте над тем, чтобы убедить ваше руководство «инвестировать» определенные усилия в его изучение (и SEDA в целом) - потому что, если они этого не сделают, есть шанс, что однажды их клиенты оставят их в пользу более конкурентоспособного решения, требующего в 4, 8 и т. д. меньше серверов.
источник