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

Есть несколько способов определить, что ваша программа страдает от переполнения очередей сообщений. Например, можно воспользоваться etop :

#!/bin/sh

erl -name etop- ` date + % s ` -hidden -s etop -s erlang halt
-output text -node project @ example.com -setcookie qwerty
-tracing off -sort msg_q -interval 5

Сей скрипт показывает список процессов на заданной ноде с наиболее длинными очередями сообщений:

К сожалению, вывод etop не особо информативен. Наиболее подробный отчет можно получить, зайдя на ноду по remsh:

erl -name remsh- ` date + % s ` -setcookie qwerty -remsh project @ example.com

… и воспользовавшись функциями processes/0 и process_info/2 . Например, как-то так:

rp([ {P, RN, L, IC, ST} || P <- processes(), { _, L } <- [ process_info(P, message_queue_len) ], L >= 1000, [{_, RN}, {_, IC}, {_, ST}] <- [ process_info(P, [registered_name, initial_call, current_stacktrace]) ] ]).

Также хорошо зарекомендовал себя Observer. Для запуска Observer я использую следующий скрипт:

#!/bin/sh

erl -smp -name observer- ` date + % s ` -setcookie qwerty
-eval «net_kernel:connect(‘project@example.com’), observer:start().»

Вот как в Observer выглядит список процессов, отсортированный по длине очередей сообщений:

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

Неплохая идея — писать длины очередей сообщений и прочие метрики в какой-нибудь Graphite . Разумеется, если за метриками никто не следит 24/7, вряд ли они помогут заблаговременно предвидеть падение приложения и предотвратить его. Для решения этой проблемы вам нужен не Graphite, а некая система посылки SMS-уведомлений или вроде того. Метрики же интересны тем, что они позволяют (1) понять, что происходит с системой, если вы только что получили уведомление о ее неисправности, а также (2) разобраться в проблеме уже после ее возникновения.

Наконец, не представляет большого труда написать gen_server , который раз в несколько секунд проверяет длины очередей всех работающих процессов. Если процесс имеет слишком длинную очередь сообщений, делается запись в логе с указанием текущего стэктрейса процесса, самих сообщений и прочей информации. Мне кажется, такой gen_server должен быть в составе любого приложения на Erlang. Он избавляет от необходимости постоянно смотреть в etop или Observer, чтобы поймать и диагностировать проблему.

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

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

Можно попробовать ускорить обработку сообщений. Например, если вы видите, что процесс повисает на функции lists:member/2 , возможно, вместо списков имеет смысл использовать множества. Если некий gen_server долгое время выполняет какие-то расчеты, ходите в этот gen_server только за данными, а сами расчеты выполняйте на стороне клиента.

В некоторых случаях множество сообщений можно объединить в одно большое сообщение. Например, вместо того, чтобы вызывать gen_event:notify/2 для списка событий, пошлите одно сообщение со списком всех этих событий. Вообще, имеет смысл всегда посылать списки событий, даже если этот список в большинстве случаев состоит из одного элемента. Это избавит вас от переписывания большого количества кода в будущем. Также в некоторых случаях вместо кучи событий «у пользователя X поменялось свойство Y» можно послать одно событие «у пользователя X поменялись какие-то свойства».

Часто помогает замена асинхронного вызова на синхронный. Например, cast можно заменить на call, а notify — на sync_notify. Перед посылкой сообщения можно проверять текущую длину очереди сообщений процесса, и если она больше заданного порога, делать синхронный вызов вместо асинхронного. Или делать timer:sleep(100) , после чего повторять попытку асинхронного вызова. Писать код с проверками лучше сразу , а не после того, как возникнет проблема. В конце концов, программа, которая иногда немного тупит, лучше программы, аварийно завершающей свою работу в выходные, правда?

Нередко оказывается, что от посылки сообщений можно вообще отказаться. Например, вместо использования gen_server, хранящего в своем состоянии dict, лучше использовать ETS . В некоторых случаях состояние gen_server’а можно держать в protected ETS, делая чтение напрямую из ETS, а запись — через gen_server.

Иногда можно написать процесс-прокси, который быстро разгребает свою очередь сообщений и либо пересылает их проксируемому процессу, если его очередь сообщений невелика, либо сохраняет сообщения в куче до лучших времен. В некоторых случаях сообщения в куче можно заменять на только что прибывшие сообщения. Случается, что часть сообщений можно вообще игнорировать. Также иногда можно прибивать проксируемый процесс. Например, если он обслуживает сокет и очередь сообщений растет, значит клиент либо не успевает обрабатывать сообщения, либо имеет слишком медленный канал. В любом случае, это не наша проблема, пусть реконнектится. Наконец, Erlang позволяет с легкостью распараллеливать код. Вместо одного gen_server’а поднимите десяток и сделайте шардинг сообщений между ними.

Однако следует понимать, что переполнение очередей сообщений означает, что у вас есть producer, который генерирует сообщения быстрее, чем их может обработать consumer. Обычно это означает ошибку by design . Либо consumer должен ходить за сообщениями к producer’у по мере своей готовности, либо producer должен проверять готовность consumer’а прежде, чем что-то ему посылать. Подумайте, не проще ли будет изменить дизайн вашего приложения, чем ставить подпорки.

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

Дополнение: Еще по теме борьбы с переполнением очередей — Пишем относительно простой менеджер локов на Erlang

admin

Share
Published by
admin

Recent Posts

Консоль удаленного рабочего стола(rdp console)

Клиент удаленного рабочего стола (rdp) предоставляет нам возможность войти на сервер терминалов через консоль. Что…

2 месяца ago

Настройка сети в VMware Workstation

В VMware Workstation есть несколько способов настройки сети гостевой машины: 1) Bridged networking 2) Network…

2 месяца ago

Логи брандмауэра Windows

Встроенный брандмауэр Windows может не только остановить нежелательный трафик на вашем пороге, но и может…

2 месяца ago

Правильный способ отключения IPv6

Вопреки распространенному мнению, отключить IPv6 в Windows Vista и Server 2008 это не просто снять…

2 месяца ago

Ключи реестра Windows, отвечающие за параметры экранной заставки

Параметры экранной заставки для текущего пользователя можно править из системного реестра, для чего: Запустите редактор…

2 месяца ago

Как управлять журналами событий из командной строки

В этой статье расскажу про возможность просмотра журналов событий из командной строки. Эти возможности можно…

2 месяца ago