Highload — это тема одновременно модная и достаточно заезженная, тем более, что нет четкого определения, что же такое «Highload». Для ясности, давайте назовем «Highload» сетевое приложение, которое должно обрабатывать 1000 запросов в секунду. А приложение, обрабатывающее 1 запрос в секунду, соответственно, «не Highload». Мой опыт показывает, что между первым и вторым есть существенная разница в архитектуре, подходах к разработке и проблемах. В этой статье я попытаюсь изложить эти отличия, как я их понимаю.

image

Итак…

Многие фреймворки имеют ограничения на производительность


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

Кеширование, кеширование и еще раз кеширование


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

Простота


В целом — чем проще система, тем быстрее она работает. Нужно стремиться к максимальной простоте, часто в ущерб понятности, концептуальности или красоте архитектуры.

Пропускная способность сети имеет пределы


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

Не забывайте про off-heap


Некоторые объекты в Java требуют дополнительную память для работы, поэтому не нужно рассчитывать, что если -Xmx выставлен, то приложение точно влезет на сервер. Например, каждый поток в Java требует от 256K до 2 мегабайт off-heap памяти для своей работы. При умножении на 1000 получается уже достаточно много, так что следите за кол-вом потоков, используемых вашим приложением.

GC не резиновый


Даже если строгих требований к latency нет, о сборке мусора нужно помнить. Старайтесь ограничивать кол-во аллокаций и, особенно, общий объем выделяемой памяти на каждый запрос. Все необходимые метрики есть в профайлере Java Mission Control.

Аккуратнее с логгированием


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

Поведение при нехватке ресурсов


Что делать, если приложению приходит больше запросов, чем оно способно прожевать? Если база внезапно стала отвечать в два раза медленней? Если в сети начались потери пакетов? Хорошее приложение должно не виснуть наглухо, а отвечать «завтра приходи». Мораль — для всех взаимодействий с внешними системами должны быть таймауты, приложение должно ограничивать кол-во параллельно обрабатываемых запросов и клиент должен знать, что делать, если приложение занято или не отвечает.

Сделайте нормальный мониторинг


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

И всегда тестируйте приложение под нагрузкой


Приложение легко может умереть под высокой нагрузкой, вполне прилично себя ведя под низкой. Если оно работало неделями на 1 запросе в секунду, на 1000 оно может умереть из-за ничтожной утечки памяти или ресурсов, перегрузки сети, перегрузки или переполнения диска и еще по 1001 причине. Поэтому — всегда гоняйте билд на полной нагрузке перед релизом на прод.

На этом всё. Комментируйте, поправляйте, делитесь своим опытом.

Комментарии (6)


  1. kidar2
    03.11.2015 12:59
    +19

    Про Java ничего не нашёл в статье.


    1. Scf
      03.11.2015 13:40
      -1

      У разных языков программирования разный рантайм, разные наборы библиотек, разные подходы к разработке и разная культура. Изложенное в статье — это опыт программирования на Java и для JVM. Конечно, что-то верно для всех языков и платформ, но везде есть своя специфика и какие-то свои острые углы, неприменимые для Java/JVM разработки.

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


      1. Suvitruf
        03.11.2015 14:36

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


        1. Scf
          03.11.2015 15:58

          Это я и пытаюсь объяснить — я не хочу давать советы для технологий, с которыми я не знаком. Моё мнение — эти советы не универсальны, так что удаление слова Java из названия статьи будет немного обманом.


    1. madkite
      05.11.2015 16:16

      ну как — наличие GC и то, что стэк потока не в heap-е, который под GC. Но негусто, конечно. Я ожидал услышать что-то типа — не юзайте поток для каждого долгоживущего соединения, юзайте неблокирующий ввод/вывод (Java NIO). Хотя если 1k уже считать highload…


  1. gto
    03.11.2015 18:07
    +6

    Простите, конечно, но смысл статьи: «Чтобы построить HL приложение, нужно задуматься». А так оно не понятно? Я бы даже больше сказал, при проектировке любого приложения надо заботится и о памяти и о gc и о кешах. Ведь даже одному запросу в секунду будет приятно получить ответ сразу же, без задержек. И соглашусь с первым комментатором, жизненых примеров-то, которые украшают любую статью, нет, а без них польза написанного сомнительна.