Хотелось бы добавить по поводу высоконагруженных систем на java относительно этой статьи. В комментариях писать слишком много, поэтому напишу отдельно. Подход не совсем программиста, но может кому пригодится. Это только мое мнение, возможно, оно где-то неверно.

Платформа JEE


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

Java.nio для сети


В java.nio появился не блокирующий ввод вывод, это значит не нужно запускать поток для каждого нового соединения. Один поток может обслуживать несколько сотен и больше соединений, не вызывая частых context switch и не загружая память. Но это не панацея, у такого подхода есть существенный недостаток: любая задержка в потоке сказывается на запросах в очереди. В некоторых случаях подход «одно соединение – один поток» может оказаться предпочтительнее.

Со стороны операционной системы на Linux для поддержки epoll нужно ядро 2.6+. Для Solaris для поддержки eventport нужна java 8 и параметр -Djava.nio.channels.spi.Selector=sun.nio.ch.EventPortSelectorProvider, иначе будет /dev/poll, который тоже не плох.

Пулы потоков


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

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

Для примера: Пул из 100 потоков со средним временем выполнения 5мс может обслуживать 20к запросов в секунду. А если им придется ждать ответа от внешнего сервиса 50мс, то количество обрабатываемых запросов сократится до 1.8к. Если вызов сервиса нужен не для всех запросов, целесообразнее придумать асинхронную обработку.

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

Пулы соединений к БД


Смысл использования пулов заключается в экономии времени на установку соединения и контроль количества соединений. Если вы пишете пул сами, а не используете готовый в случае JEE, нужно внимательно расставить тайминги. Если соединение ожидать слишком долго, это ведет к задержке потока выполнения и росту размера соответствующего пула. При пересоздании пула при сбое нужно сделать тайминг на повтор попытки соединения, может быть стоит его сделать прогрессивным, иначе можно случайно совершить DDoS-атаку на сервер.

Это касается не только БД, но и любого другого внешнего ресурса, даже http.

Очереди


Они нужны для взаимодействия между потоками, в реализации это какой-нибудь LinkedList или LinkedBlockingQueue. Не стоит их делать слишком большими, так как они потребляют много памяти и может так случиться, что потоки не успевают обработать запросы из очереди за время tcp или http таймаута. В тоже время они должны быть достаточно большими, чтобы сглаживать кратковременные всплески нагрузки. Ориентировочная величина в 10 раз больше размера обрабатывающего пула.

JSP


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

Логирование


Понятно, что логировать нужно минимально необходимую информацию. Важный момент: логирование не должно приводить к отказу системы, если нет специальных требований этого. То есть если не удалось записать лог, пишем об этом в System.err и работаем дальше, а не завершаем поток с экзепшеном. Лог можно ротировать по размеру, по времени, но он не должен быть бесконечным. Благо все java библиотеки для логирования замечательно с этим справляются.

Подготовку и вывод дебаг лога рекомендуют оборачивать в нечто подобное, чтобы не тратить лишние такты:

if(log.isDebugEnabled()){
	StringBuilder sb = new StringBuilder();
	sb.append(arg0);
	sb.append(arg1);
	log.debug(sb.toString());
}

Мониторинг java


Для оценки нагрузки важно в реалтайме смотреть размеры хипа, пулов потоков, пулов соединений к БД и другим ресурсам, сессий. Нужно так же иметь исторические данные для оценки. Можно мониторить через jcontrol или подобный функционал, но это не очень удобно. Лучше собирать и писать в базу эти метрики отдельным софтом или самостоятельно. Количество метрик должно быть минимальным для оценки состояния, так как тоже требует системных ресурсов, при большом количестве соединений это может быть ощутимо.

Deadlock потоков


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

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

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


  1. aparamonov
    06.11.2015 06:13
    +6

    Кто-нибудь использует J2EE для хай-лоад проектов? Какая нагрузка, стабильность?
    Я честно о таком впервые слышу. Обычные отзывы о J2EE контейнерах перемешаны с кровью, потом и слезами даже на простых проектах. Не слышал об исключениях.


    1. asm0dey
      06.11.2015 07:57
      +2

      А большая нагрузка — это сколько? У нас 300 запросов в секунду формально на двух серверах, реально на одном и мы не упираемся в производительность.


      1. samodum
        06.11.2015 11:39
        -6

        Обычно принято считать большие нагрузки примерно от 1000 запросов в секунду


      1. abatyuk
        07.11.2015 09:01
        +1

        «Большая нагрузка» — как «большие данные». С одной стороны баззворд, с другой — для каждого свое понимание. Наверное, как и в случае с большими данными, это когда не помещаешься в один сервер уже по физическим ограничениям (например, совокупный объем передаваемых данных больше гигабитного сетевого канала)

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


        1. asm0dey
          07.11.2015 09:08

          Ну вот да, я написал своими руками сервис на EE, который обрабатывает 10000 запросов в секунду на EE платформе на одном сервере. Но сервис-то простой, чего ему не работать?


    1. mirwide
      06.11.2015 19:20

      «Сбербанк Онлайн» — думаю можно считать высоконагруженным.


      1. gurinderu
        06.11.2015 19:34

        Уточните заодно сколько серверов обслуживает СБОЛ


        1. mirwide
          06.11.2015 19:44

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


    1. dea
      07.11.2015 01:36

      Кто-нибудь использует J2EE?!


      1. sandello
        07.11.2015 09:42

        Йа! :) Уже лет 10, а то и больше. Начинал с JBoss 3.


        1. dea
          07.11.2015 14:09
          -2

          А в каком JBoss до сих пор J2EE?


          1. sandello
            07.11.2015 14:10

            Гм… вы меня смутили. Возможно я не правильно истолковал «J2EE»?


            1. dea
              07.11.2015 14:15

              Возможно. Лучше различать JEE и J2EE, слишком разные платформы.


              1. sandello
                07.11.2015 14:19
                +1

  1. aparamonov
    06.11.2015 06:37
    +5

    Что касается пунктов…

    Сейчас очень распространены легковесные фреймворки типа Play (со своим шаблонизатором, который компилится в исходный код) и SPA решения…
    JSP, конечно, — боль. Но почему сразу сервлеты? У вас же код нечитаемым месивом будет.
    И, да, а почему JSP тормозной? Он же в сервлет компилируется.

    Совет не писать собственный пул потоков в бд доставило…

    Почему вы рассматриваете очереди, deadlocks в этом ключе?
    Давно придумали functional, reactive programming, чтобы увести в 0 взаимодействие между потоками. А если взаимодействие есть, то его осуществлять на неблокируемых операциях like CAS.


    1. mirwide
      06.11.2015 18:42

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


  1. asm0dey
    06.11.2015 08:10
    +1

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


    1. Borz
      06.11.2015 08:27
      +3

      Оборачивать в .isDebugEnabled() всё равно приходится, если аргументами передаются не просто объекты, у которых вызов toString() выдаст что-то адекватное. Как пример — объект типа массив.

      неправильно:

      log.debug("myArray: {}", myArray);
      

      правильно:
      if (log.isDebugEnabled()(){
        log.debug("myArray: {}", Arrays.toString(myArray));
      }
      


      1. omickron
        06.11.2015 09:02
        +1

        Теоретически да, я согласен, такой способ верный.

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


        1. Borz
          06.11.2015 11:18
          +1

          Как минимум, это будет «экономия на спичках» — внутри метода debug так же происходит вызов isDebugEnabled. Но я лучше вызову дважды isDebugEnabled, чем сперва преобразую объекты в нужную для debug-лога форму, а потом только проверю — а надо ли мне выводить в debug-лог что-то?

          Для себя использую простые правила для оборачивания кода в if (isDebugEnabled):
          0) если не получается передать просто объект(-ы) как параметры в log
          1) если подряд идут несколько вызовов log.
          В остальных случаях просто вызываю log.debug


  1. fls_welvet
    06.11.2015 11:49

    Немного своего опыта.

    Платформа JEE. Само понятие высоконагруженной системы говорит о том, что стандартные решения не подходят или нуждаются в доработке. Тк готовые решения обычно подходят для всего и ничего не знают про ваш домен, то они вряд ли смогут применить оптимизации, доступные только вам. При разработке подобных систем стоит выбирать несвязанные (опенсорсные) компоненты и включать их в проект. Например, отлично подходит embedded jetty в качестве сервера (его отдельные куски потом можно будет переписать). В качестве IOC контейнера лучше выбрать guice, который решает только одну задачу, чем спринг, который решает сразу все.

    Очереди. Время обработки сообщения, попавшего в конец очереди размера x10, будет во много раз меньше чем время ожидания его обработки. Нет смысла заставлять пользователя ждать когда мы при этом ничего не делаем. Очереди должны быть размером x1-x2 и необходимо постоянно мониторить их размер. По хорошему очереди всегда должны быть пустыми. Исключение составляют очереди, которые обрабатываются батчем (например, из-за io).

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

    Мониторинг. + мониторинг gc, safepoint'ов и прочего в самой java.

    И еще немного от себя:

    Профилирование. Нужно освоить JMC и периодически просматривать дампы на предмет больших и не нужных аллокаций/промоушенов. Так же в поиске горлышке очень поможет расстановка меток в коде и их последующей визуализации (как это сделано в js профилировщике, например, в ff).

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


    1. dougrinch
      06.11.2015 12:53

      Про очередь. Согласен, но только при равномерной нагрузке. У меня же, например, получается постоянно 300 запросов в секунду, но каждые 10 секунд приходит сразу 2000. Тут хочешь не хочешь, но запас в очереди должен быть большой.


    1. gurinderu
      06.11.2015 14:16

      А еще можно попробовать в качестве IOC юзать Dagger2. Это может помочь для хаилоада, т.к. в нем нет рефлекшина в рантаиме.


    1. shapovalex
      07.11.2015 21:11
      +2

      Тезис «не нужно стесняться логировать» очень спорный и хорош только для небольших проектов. Когда количество логов начинается измеряться в терабайтах в сутки, то команда отвечающая за спланк (или другой лог агрегатор) заставляет тебя пересмотреть полезность некоторых логов)


    1. trix
      08.11.2015 17:41

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


      1. gurinderu
        08.11.2015 20:02
        -1

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


        1. trix
          09.11.2015 01:31

          спринг создает прокси только когда его просят создать прокси :) если в проекте не нужны декларативные транзакции и прочая радость, тогда и прокси никто не создает (если не копипастит конфиги с aop:autoproxy)
          если в проекте нужно проксить только отдельные классы, то для того и созданы две разные аннотации Service и Component


          1. gurinderu
            09.11.2015 08:43

            Согласен, погорячился. Забыл что у меня Spring Boot много лишнего успевает сделать.


  1. Throwable
    06.11.2015 14:25
    +7

    Понеслааась…

    и вы можете сделать архитектуру именно такой, как вам нужно. Но это при том условии, что у вас неограниченное количество человеко-часов
    Подключите фреймворк или библиотеку, которая наиболее подходит задаче. Альтернатив всегда десятки. На крайний случай, практически все, что входит в стек JEE доступно standalone.

    и в итоге все равно получиться чуточку хуже, чем то, как это реализовано в современных JEE серверах.
    Всегда задаю вопрос: а что такого особенного реализовано в JEE серверах? Какие супер-пупер технологии? Вебстек, альтернатив которому уже десятки? Наихудшая, непонятная и неудобная модель компоновки и запуска приложения, связанная с запаковкой всего в .Xar? Рудиментарный EJB? CDI? Webservices? JDBC pool? JPA? Все это доступно standalone без особого напряга. Правильный ответ дают лишь немногие: это JTA (distributed transactions). Если Вы не используете JMS и в проекте только одна база данных, можете смело забыть о JEE.

    Все промахи всплывут только под реальной нагрузкой, и архитектуру приложения придется переделывать не один раз.
    До сих пор всплывали промахи, связанные именно с сервером приложений и стеком JEE. Причем в этом случае архитектуру Вы уже сильно не поменяете.

    Java.nio для сети
    Не успев полностью сесть на JEE вы уже прикручиваете сбоку какой-то левый велосипед, который и близко не лежал к стеку. Если уж Вы рекомендуете JEE, так используйте его средства: http servlets, JMS, Remote EJB, WS… Либо как белый человек пишите свой resource adapter, согласно спецификации. И уж там можете пользовать хоть java.nio, хоть netty…

    Если вы пишете пул сами, а не используете готовый в случае JEE, нужно внимательно расставить тайминги.
    Если Вы используете готовый, то нужно еще более внимательно расставить тайминги. Weblogic имеет более 100 настроек для пула. Еще надо найти и включить мониторинг для пула, чтобы время от времени выявлять незакрытые ресурсы и впоследствие ломать голову над тем, где в коде происходит leaking.

    Они нужны для взаимодействия между потоками,
    В JEE нет такого паттерна как «взаимодействие между потоками». Есть асинхронное выполнение, есть асинхронное взаимодействие посредством JMS. Все остальное синхронно.

    Не стоит использовать JSP для высоко нагруженной системы, даже с кэшем он будет медленнее сервлетов.
    JSP при использовании скриплетов (теги с процентиками) компилируется в .class и работает с той же скоростью, что и сервлет. Если вы используете различные tag libraries (например JSTL и EL), то перформанс будет немного проседать ввиду того, что приходится интерпретировать теги. Однако, вроде бы есть компилятор для JSTL. Если же вы используете JSF, то перформанс падает значительно. JSF — технология не для hiload.

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

    Дело в том, что сервер следит за своими потоками и отлавливает deadlock
    Полная фигня. Сервер не отлавливает ничего. Сама архитектура JEE предполагает отсутствие дедлоков и правильную блокировку. Просто серверный пул конфигурируется и мониторится сервером. Плюс некоторые сервера могут корректно закрывать некоторые оставленные ресурсы типа connections из пулов. И еще корректно закрываться в случае остановки сервера. Недостаток всех пулов: нельзя использовать ThreadLocal (вернее очень осторожно).


    1. asm0dey
      06.11.2015 14:47

      TransactionManager там реализован. И это очень сложно написать самому. JTA вообще сложная штука, если честно. EJB 3 ничем особо не отличается от спринговых бинов. Почему он рудиментарен?


      1. Throwable
        06.11.2015 15:35
        +3

        Это CDI ничем не отличается от спринговых бинов — то же, только хуже. А EJB — это более тяжелая технология, которая служит для интеграции с платформой. Почему рудиментарные? Stateless EJB организует пул объектов, которые гарантируют изолированность вызовов, что вобщем-то не требуется, если ваш объект stateless. Statefull вообще непонятно где может использоваться, плюс имеет такую фичу как пассивация объекта через какое-то время и сохранение состояния где-то в дебрях диска. Внезапно происходят exceptions если ваш Stateful не Serializable. Message driven bean до сих пор нет стандартного способа хорошо указать ActivationConfigProperties. Они все vendor-specific. Singleton bean мешает кластеризированию — он, оказывается, всего лишь server instance singleton, а не application singleton. Годная вещь — remote EJB interface. Но она также vendor-specific, и практически всегда непроксируема (только в локальной сетке), поэтому всегда предпочтительней JAX-WS или JAX-RS, чем remote EJB.
        И, наконец, очень медленные. Чтобы посмотреть через что проходит ваш простой вызов к EJB, просто сделайте stacktrace…


        1. asm0dey
          06.11.2015 19:58

          Насчёт пула стейтлессов — это просто разные подходы. С пулом вы можете огрничить пропускную способность вашего бина сверху. Можете, прошу прощения, сделать в бине синхронайзд метод и всё будет. Стэйтфул — иногда полезно, хотя кейсы редки. Но оно же для JSF (который весьма ублюдочен, да, но иногда используется…) хорошо. Насчёт проксей — у меня для вас грустна новость, даже две. 1 — спринг их тоже создаёт, 2 — это не узкое место. А ещё там на каждом шагу секьюрити манагер, что может и совсем не бесплатно, зато даёт кое какие гарантии.


    1. asm0dey
      06.11.2015 14:48

      Прошу прощения, не дочитал ваш тезис. Вы, оказывается сами про JTA написали.


    1. asm0dey
      06.11.2015 14:50

      Я ещё добавлю что Wildfly, например, имеет вендор-спцифик валидаторы для коннекшнов к разным БД. Оракла, постгреса и так далее.


    1. mirwide
      06.11.2015 19:04
      -2

      Не имел ввиду JEE как набор стандартов/спецификаций, а только как сервер приложений, наверно забыл добавить. То что он умеет EJB, JMS и тд. совпадение — они скорее для удобства чем для лучшей производительности.
      А в серверах приложений уже реализовано много для высокой нагрузки, где-то лучше где-то хуже. Но во всех есть правильная работа с сетью через java.nio(причем здесь велосипед я не понял из комментария) и многопоточность и возможность это контролировать и управлять. Писать это самому глупо, когда есть готовое и в том числе опенсорсное с возможностью подкрутить для себя.
      Это тоже что использование стороннего фреймворка, вы экономите время и получаете обкатанное решение. И кучу дополнительных проблем с ним же.
      WebSphere отлавливает взаимоблокировки, блокировки и зацикливания в потоках. Вы не правы. Configuring the hang detection policy
      Остальное совсем не в тему))