gen_server работает медленнее, когда почтовый ящик становится больше

У меня проблема, я использую gen server для выполнения такой простой работы, как эта:

  one handle_cast to do a long time work(takes 60 seconds)
  one handle_cast to do a very fast work

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

Проблема аналогична этой проблеме с stackoverflow В Erlang, когда почтовый ящик процесса увеличивается, он работает медленнее, почему?.

Но я все еще не понимаю. Если это из-за сбора мусора, то как сборка мусора может занимать так много времени или так часто?


person 蛋汤饭    schedule 09.12.2016    source источник
comment
Не могли бы вы показать код предложения handle_cast, который занимает много времени? Одна из причин, по которой это может произойти, — это наличие выражения receive, совпадающего с определенными сообщениями.   -  person legoscia    schedule 09.12.2016
comment
@legoscia О, нет. Этот процесс просто вставляет около 30 000 документов в mongodb в цикле. Моя машина может записывать более 5000 документов в секунду, и это также не связано с дисковым вводом-выводом mongodb.   -  person 蛋汤饭    schedule 09.12.2016
comment
@legoscia И эта проблема похожа на то, что встречается " title="в erlang, когда почтовый ящик увеличивается, он работает медленнее, почему"> stackoverflow.com/questions/36216246/   -  person 蛋汤饭    schedule 09.12.2016
comment
Такие вопросы лучше сопровождать SSCCE. В противном случае окружающие могут в основном только догадываться о том, что происходит.   -  person Ryan Stewart    schedule 10.12.2016


Ответы (3)


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

Потому что весь трафик проходит через один единственный процесс, и это создает узкое место.

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

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

person user8755563    schedule 09.12.2016
comment
Да, дизайн системы может быть ошибкой. Но все же проблема в том, почему почтовый ящик замедляет процесс. - person 蛋汤饭; 09.12.2016
comment
Это может быть вызвано избирательным приемом. - person user8755563; 09.12.2016
comment
Нет, у ген-сервера нет выборочного приема, и в моем коде также нет оператора приема. - person 蛋汤饭; 09.12.2016
comment
выборочное получение — см., например, ndpar.blogspot.cz/2010/ 11/ - person user8755563; 09.12.2016
comment
reciece находится внутри gen_rerver - если у вас длинная почтовая очередь, то каждое получение проверяет каждый элемент в очереди. Так что большая проблема => снижение производительности. Измените свой код, чтобы иметь более одного рабочего, который обрабатывает входные данные. - person user8755563; 09.12.2016

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

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

Возникает новая потенциальная проблема: возможно ли обрабатывать новые запросы параллельно с вставкой документов в MongoDB?

Если да, то все в порядке

Если нет, вам придется изменить свой дизайн, например, вернуть отрицательное подтверждение любому клиенту, чей запрос не совместим с вставкой в ​​​​базу данных (или проигнорировать запрос, если это возможно). Вы должны очистить почтовый ящик как можно быстрее, накапливать сообщения в течение 60 секунд — не лучший вариант, вы толкаете erlang далеко за пределы его обычного использования, просто представьте свой вариант использования:

  • На сервер поступает длинный запрос, он входит в цикл обновления базы данных;
  • за это время входящие сообщения накапливаются в почтовом ящике, т. е. копируются в область памяти сервера;
  • очень скоро процессу будет не хватать памяти, и виртуальной машине придется приостановить процесс (поэтому ваш цикл базы данных), чтобы увеличить память, выделенную серверу, и в конечном итоге сделать некоторую копию данных.
  • такое управление памятью замедляет работу сервера, обновление базы данных занимает больше времени, накапливается больше сообщений и т.д.
person Pascal    schedule 10.12.2016
comment
Спасибо за ваш ответ. Я постараюсь переделать свой сервер. Но я все же понял, что это управление памятью слишком долго, чтобы приостановить мой процесс с 60 до 600. - person 蛋汤饭; 10.12.2016

Наконец, я нашел свой ответ в этой статье.

В разделе 3. Архитектура памяти erlang ориентирована на процессы. Каждый процесс выделяет и управляет своей собственной областью памяти, которая обычно включает плату, частный стек и частную кучу.

Это вызывает недостаток: Высокая фрагментация памяти

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

Столь большой размер почтового ящика может замедлить работу всей системы.

Но, это еще не главный момент. Следуйте замечательному комментарию @pascal, долгие затраты на работу с базой данных, я проигнорировал, что gen_sever предназначен для отправки и получения. Я столкнулся с gen_sever: вызов в gen явно идет выборочный прием! Простите меня за то, что я немного попробовал erlang~

person 蛋汤饭    schedule 12.12.2016
comment
Кроме того, я подозреваю, что 60-секундный запрос на обновление MongoDB содержит синхронные вызовы, которые заставляют серверный процесс много раз анализировать свой почтовый ящик: синхронный доступ к процессу осуществляется путем отправки сообщения и ожидания ответа. Оператор получения выполняется в вызывающем процессе (на вашем сервере), и каждый раз, когда приходит сообщение, он должен анализировать почтовый ящик. Есть механизм оптимизации этого синтаксического анализа, но я думаю, что если ответы из базы данных и другие входящие запросы чередуются, оптимизация менее эффективна. - person Pascal; 12.12.2016
comment
Это действительно действительно почтовая точка grep! Все мои операции с монго — это вызовы синхронизации (db_pool, реализованный poolboy), и я столкнулся с erlang-opt gen_server github.com/erlang/otp/blob/OTP-18.3/lib/stdlib/src/gen.erl#L146 . Просто из-за выборочного приема у меня процесс тормозится! Спасибо еще раз. - person 蛋汤饭; 12.12.2016