Re[11]: Почему Эрланг
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.06.15 08:16
Оценка:
Здравствуйте, so5team, Вы писали:

S>О том и речь. Возможность задешево породить в Erlang-е огромное количество легковесных процессов не обязательно будет востребована всегда. На каких-то задачах достаточно будет иметь всего несколько рабочих процессов, которые и будут делать все вычисления/всю обработку. Но тогда подход Erlang-а будет не сильно отличаться от подхода C++/Java.


Будет отличаться. В общем, можно сформулировать следующие принципы. Сначала с "так не делайте":

1. Если дизайн предполагает штатной ситуацией наличие больше 10 (условно) сообщений во входной очереди процесса, архитектура построена неправильно. Все асинхронные (то есть следующее шлётся не дожидаясь ответа на предыдущее) потоки должны быть перенесены внутри ноды в специальные драйвера, которые поставляют процессу данные по методу, аналогичному {active,false|once|N} для gen_tcp. Все асинхронные потоки между нодами в кластере должны идти вне штатных межнодовых коннекторов.

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

Дидактически отличный пример, где это реализуется само без участия программиста — отдача по tcp; пока драйвер не сложит порцию в сокетный буфер, gen_tcp:send не получит подтверждения, то есть OTP здесь принудительно обеспечивает синхронность. Именно поэтому Лапшин почти не боролся с тем, что нам убивало системы

2. Если долгоживущих процессов заметно больше количества ядер, архитектура построена неправильно. Допускается постоянно живущих в объёме M*Ncores, где M — достаточно малая константа. Допускается огромное количество (грубо говоря, миллионы) процессов, но чтобы каждый из них жил одну операцию (самое крупное — ответ на http запрос, или транзакция), не постоянно, и чем короче — тем лучше (нижняя граница срока жизни — определяется моментом, когда переброс данных между такими эфемерами становится слишком дорогим). При требовании долгоживущего состояния — делать пулы рабочих процессов, каждый из которых держит комплект состояний.

Нарушение этого пункта убивает производительность на корню за счёт раздувания памяти и не приводимого в адекват в этом случае GC, в тяжёлых случаях это убивает ноду за счёт переполнения по памяти (с последствиями, аналогичными пункту 1).

3. Если действия, неэффективные для рантайма с динамическими типами и постоянной их проверкой (грубо говоря, математика). Выносить в нативный код или пользоваться новомодными фишками типа транслятора через LLVM (cloudozer сейчас такое творит).

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

Если это выполнено, можно перейти к вкусностям, которые задаются тем, что уже готово в OTP, но требует реализации практически у всех конкурентов. Это:

1. Разделение активности по процессам, которые видны рантайму и поэтому контролируемы. Для каждого процесса можно узнать подробно, чем занимается (получить stacktrace, который не будет съеден оптимизациями; получить словарь процесса, куда часто экспортируется статистика и прочие подробности состояния; узнать объём очереди сообщений; и т.п.)

2. За счёт обмена сообщениями вместо блокируемого состояния — нет проблемы от грубого вмешательства, такого, как посылка фиктивного ответа; убийство процесса-исполнителя целиком (для синхронного запроса на манер gen_call, за счёт монитора таргета со стороны клиента, это немедленно приводит к ответу). Средства контроля и такого защитного управления могут быть как автоматическими, так и ручными, без необходимости глубинного знания потрохов. Там, где в C++/Java/etc. ты не можешь подобраться так, чтобы снять мьютекс, исправив данные, тем самым разрулив заклин на ходу — тут всё это из коробки.

3. Опять же, из коробки кластеризация позволяет, даже не распределяя реальную работу, забраться в живую систему (имея права) через remote shell и починить на ходу. Для автоматов вместо remote shell используется штатный RPC. Заведение этих средств в работу стоит единицы минут времени.

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

N>>Большие объёмы данных, много параллельных запросов и плотные потоки — это три разных случая, обычно не сильно пересекающиеся. Свидетель описывает случай 3, который на штатном рантайме, считаем, нереализуем. Случай 2 — самый подходящий. 1 — зависит от подробностей.

S>Отрадно, когда в треде появляются люди, понимающие что к чему
S>ИМХО, со вторым случаем, т.е. с потоком параллельных запросов, так же не все однозначно. Можно создавать по воркеру на каждый запрос. А можно иметь отдельный воркер на ядро, а запросы представлять внутри воркера в виде конечных автоматов. И у того, и у другого подхода есть свои достоинства и недостатки.

В случае Erlang обычно достаточно по воркеру на запрос. Это проще всем, потому что такой воркер отработал и застрелился, не требуется никакой сложной зачистки за ним. Если же это выводить в пул автоматов, как минимум уже утяжеление, что GC должен реально отработать, а не прихлопнуть целиком всю память застрелившегося процесса. Ну и сами автоматы писать достаточно громоздко. Нам в Clustrx пришлось идти по пути автоматов, потому что надо было хранить состояние объекта со множеством подробностей между оповещениями от него, и я помню неудобства, связанные с этим.
erlang
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.