четверг, 5 марта 2015 г.

[prog] Пример проблемы при разработке SObjectizer (mpmc-mbox, message_limits + redirect)

Т.к. есть несколько читателей блога, которым интересно читать про возникающие при разработке SObjectizer-а проблемы, то для них свежайший пример одной из таковых :)

Описание дано на SF.net: вот здесь. Там же, вторым сообщением, перечислены варианты, к которым лично я склоняюсь сейчас. И, если не будет каких-то других идей, именно они будут взяты за основу. Если у кого-то возникнут собственные мысли на эту тему, то буду признателен, если вы их выскажите (либо на SF, либо прямо здесь, в комментариях).

Пока же скажу пару пояснительных слов о message_limits и redirect-ах.

Такая штука, как message_limits, задумывалась и воплощается в жизнь как средство защиты агентов от перегрузок. Т.е. это не полноценный механизм overload control, в котором агент имеет возможность оперативно реагировать на уровень нагрузки и динамически менять свое поведение (например, менять состояния и обрабатывать сообщения по-другому). Message_limits -- это не более чем способ помешать закинуть в очередь сообщений агента больше сообщений, чем агент в состоянии "проглотить".

Не смотря на то, что message_limits не является полноценной заменой overload control-а, наличие контроля за лимитами сообщений может существенно увеличить "коэффициент спокойного сна" при разработке агентов. Например, если у нас есть агент, работающий на потактовой основе, и мы знаем, что на очередном такте он способен обработать не более 100 запросов, то держать в очереди сообщений 101-й запрос смысла нет. Он все равно не будет обработан.

Ранее задача "отсева" лишних запросов лежала полностью на прикладном программисте. Допустим, есть агент-producer, который вычитывает запросы из TCP/IP канала или берет их из MQ-шной очереди, после чего тупо отсылает запросы агенту-performer-у. Если использовать эту простую схему, то агент-producer запросто может накидать агенту-performer-у 10K запросов за полсекунды, после чего все приложение начнет медленно и печально деградировать, т.к. агент-performer на каждом такте будет обрабатывать лишь 100 запросов, потом засыпать до следующего такта и т.д. Тогда как агент-producer будет поставлять ему все новые и новые запросы (например, из-за истечения тайм-аутов на получение ответов на ранее отосланные запросы).

Хорошим выходом из этой ситуации было бы разделение исполнителя запросов на две части: агент-collector и агент-performer. Первый бы хранил у себя очередь подлежащих обработки запросов, выдавая второму не больше, чем агент-performer реально может обработать. Подробнее эта идиома рассматривалась в блоге чуть ранее (#1, #2, #3). Это, действительно, очень хороший выход, т.к. он позволяет реализовать очень гибкое, заточенное под особенности прикладной области, управление нагрузкой на perfomer-а.

Есть только одна небольшая проблемка. Это все нужно писать самому :)

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

В этих случаях можно использовать лимиты для сообщений. Т.е. указать, что в очереди агента не должно стоять больше N сообщений типа M1, и К сообщений типа M2. Сообщения будет считать сам SObjectizer. Ну и предпринимать затем какие-то действия при превышении лимита так же будет сам SObjectizer.

Сейчас предусматривается четыре типа реакции на превышение лимита:

  • выбрасывание сообщения. Ну не поместилось оно в очередь, ну и ничего страшного. Потом придет новое. Вполне естественный для агентных систем подход, где доставка сообщения от отправителя к получателю не гарантируется. На практике будет востребована в ситуациях, где есть большой поток сообщений, в котором каждое следующее приносит более актуальную информацию, чем предыдущее. Например, поток сообщений и движении мышки по экрану. Или поток сообщений о параметрах работы приложения (количество транзакций в обработке, среднее время на транзакцию). Ну и все такое прочее;
  • прерывание приложения посредством std::abort(). Например, при проектировании прикидывали, что для агента для работы с HSM очередь запросов на проверку криптографической подписи ну никак не может превышать ста штук. Если очередь выросла до таких размеров, значит что-то пошло совершенно не так: HSM глюканул и стал выполнять операции в десять раз дольше положенного. Тут лучше рестартовать, чем тормозить всех клиентов;
  • перенаправление сообщения другому обработчику (операция redirect). Например, агент-HSM не может принять очередной запрос check_signature, но и выбросить этот запрос без всяких следов мы не можем. Поэтому перешлем запрос вспомогательному агенту, работающему на другой рабочей нити, с тем, чтобы факт выбрасывания check_signature был зафиксирован в журнале приложения. Или еще более забавный сценарий: перераспределение нагрузки на нескольких агентов. Есть несколько агентов-performer-ов. Для первого устанавливается лимит запросов и редирект на второго при превышении лимита. На втором так же, но с редиректом на третьего и т.д. У последнего performer-а лишние сообщения просто выбрасываются. Такая схема будет нормально работать в случае с внезапными всплесками нагрузки. Т.е. когда запросы идут с обычной скорость, с ними справляется один performer. Но когда вдруг прилетает пачка запросов, они быстренько распределяются по нескольким performer-ам;
  • преобразование сообщения в другое сообщение (операция transform). Например, агенту-perfomer-у прилетает запрос, который в очередь уже не помещается. Этот запрос тут же преобразуется в ответное сообщение да вы там совсем офигели что-ли i_am_busy или negative_response. Что дает возможность сразу сказать отправителю запроса, что запрос не был обработан и ждать ответа не следует.

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

Отправить комментарий