=============== Агенты-близнецы =============== :Author: Евгений Охотников :Contact: eao197 at intervale dot ru; eao197 at yahoo dot com :Version: 0.1 :Date: 2007.11.25 .. _SObjectizer: http://sobjectizer.sourceforge.net .. contents:: **Оглавление** Мотивация ========== Данный текст появился в результате пересечения двух идей: - о том, что диспетчеров, которые обеспечивают работу всех пассивных агентов на контексте всего одной рабочей нити уже недостатчно. Например, такими диспетчерами являются шатные диспетчеры с одной рабочей нитью и диспетчер с активными агентами. На многоядерных/многопроцессорных машинах можно было бы выделить пассивным агентами N нитей (например, по числу ядер/процессоров) и распределять пассивных агентов по этим нитям (но гарантировать, что один агент не сможет одновременно работать сразу на более чем одной нити); - `соображений Дмитрия Вьюкова `__, высказанных в списке рассылки SObjectizer о том, что в приложении может существовать несколько *ключевых* агентов, которые обрабатывают значительную часть трафика сообщений в приложении. Например, агент, который владеет списком активных транзакции и к которому нужно обратиться при начале каждой новой транзакции. При плотном потоке транзакций этот агент может стать узким местом, поскольку транзакции не смогут обрабатываться до получения ответа от этого агента. В условиях многоядерности/многопроцессорности можно было бы попытаться избавиться от данного узкого места, если бы удалось разрешить подобным агентам работать сразу на нескольких нитях. В текущей версии SObjectizer 4.4.0 многопоточную работу агента можно обеспечить только за счет преобразования его событий в insend-события. Такие события гарантированно вызываются внутри ``so_4::api::send_msg()`` на контексте той нити, которая обращается к ``send_msg()``. Это приведет к тому, что если несколько агентов на своих нитях отсылают сообщения ключевому агенту, то события ключевого агента стартуют **параллельно** на контексте каждого инициатора сообщения. У решения с insend-событиями есть свои минусы: - оно будет замечательно работать, если ключевому агенту в своих событиях не придется модифицировать свои внутренние данные. Т.е. если все запросы к ключевому агенту будут только на чтение. В этом случае действительно возможно параллельное исполнение события агента на нескольких нитях. Однако, ситуация серьезно ухудшается, если данные ключевого агента могут модифицироваться. В этом случае ключевому агенту придется защищать их при помощь mutex-ов. Но тогда, во-первых, в ключевом агенте появляется ручное управление синхронизацией, а SObjectizer ценен тем, что он в значительной степени освобождает программиста от этой работы. В частности, синхронизация внутри insend-событий не видна прикладному программисту, поэтому она, потенциально, может стать причиной тупиков и взаимных блокировок, обнаружить и устранить которые будет очень сложно. И, во-вторых, синхронизация внутри insend-события сводит на нет идею запуска события на разных нитях -- ведь большинство из них будут заблокированны операцией синхронизации; - если обработчики событий ключевых агентов окажутся достаточно ресурсоемкими, то время их работы может негативно сказаться на работе агентов-инициаторов. Действительно, агенты-инициаторы расчитаны на то, что они отсылают сообщение, которое где-то и когда-то обработается, а инициаторы могут сразу же продолжать свою работу. Но тут вмешивается ключевой агент со своим insend-событием, который прерывает работу агент-инициаторов на значительное время, тем самым приостанавливая всех агентов, разделяющих рабочие нити с агентоми-инциаторами. **Примечание.** Данное предложение является всего лишь первым шагом в решении описанной выше задачи. Здесь обозначенны лишь общие наметки предполагаемого подхода. Поэтому с течением времени возможен серьезный пересмотр и переработка изложенных ниже идей. Суть агентов-близнецов ====================== Суть предлагаемого подхода в том, чтобы позволить регистрировать в SObjectizer нескольких одинаковых агентов под **одним** именем. При этом агенты должны быть полностью одинаковыми -- принадлежать одному типу и выполнять одинаковую подписку. Каждому из агентов-близнецов SObjectizer выделяет собственный контекст. При этом предполагается, что контекст для близнецов может предоставлять специальный диспетчер, обеспечивающий пассивным агентам не одну, а N рабочих нитей. Когда агентам-близнецам по их общему имени отсылается сообщение, то возможны следующие результаты: - сообщение доставляется на обработку только одному из близнецов. Остальные агенты-близнецы даже не знают про существование этого сообщения; - сообщение доставляется сразу всем близнецам. Управлять способами доставки сообщений агентам-близнецам должен специальный объект-балансировщик. Этот объект реализуется разработчиком агентов-близнецов в соответствии с логикой работы близнецов. Примеры использования ===================== Маршрутизация mbapi-сообщений ----------------------------- `Библиотека MBAPI `__ содержит специального агента-маршрутизатора mbapi-сообщений. Этот агент отвечает за: - контроль коммуникационных каналов. Как только создается новый коммуникационный канал агент-маршрутизатор обязан отослать в него информацию о локальных mbox-ах приложения. В ответ из канала приходит информация об mbox-ах удаленной стороны. На основании ответа из канала агент-маршрутизатор модифицирует свою таблицу маршрутизации, добавляя в нее имена удаленных mbox-ов и идентификатор канала. При закрытии какого-либо канала агент-маршрутизатор вычеркивает из таблицы имена ставших недоступными mbox-ов; - сериализацию и отправку в коммуникационные каналы mbapi-сообщений. Агент-маршрутизатор получает mbapi-сообщение, определяет, на какой mbox оно адресовано, выбирает канал, через который будет идти доставка, сериализует mbapi-сообщение в обычное сообщение специального глобального агента и отсылает полученное результирующее сообщение в нужный канал. Как раз работу по сериализации и отсылке результирующих сообщений в коммуникационные каналы могут выполнять агенты-близнецы. Для этого нужно, чтобы объект-балансировщик доставлял им исходные mbapi-сообщение поочереди. Аналогичная ситуация и с сообщением ``msg_client_connected`` с информацией о появлении очередного коммуникационного канала. Его достаточно доставить любому из близнецов, чтобы он отослал в канал список локальных mbox-ов. Однако, такие сообщения, как ``msg_client_disconnected`` (о закрытии коммуникационного канала), список удаленных mbox-ов или сообщение о появлении/исчезновении локального mbox-а, должны доставляться сразу всем агентам-близнецам. Тогда все они смогут поддерживать одинаковые копии таблиц маршрутизации, хотя это и будут собственные копии каждого агента-близнеца. Распределение криптографических операций по разным контекстам ------------------------------------------------------------- Если приложение серьезно использует криптографические механизмы, особенно на основе ассиметричной криптографии, то для выполнения ряда криптографических операций может быть выделен специальный агент, в задачи которого может входить: - проверка криптографической подписи; - формирование криптографической подписи; - шифрование и дешифрация данных. При большом потоке запросов к этому агенту он легко может стать узким местом. Поэтому такой криптографический агент может быть заменен несколькими агентами-близнецами, нагрузка на которых будет балансироваться. В случае использования специализированных криптоустройств, подключаемых тем или иным образом к компьютеру (SmartCard, HSM, крипто-платы), количество криптографических агентов-близнецов может выбираться исходя из количества доступных криптоустройств, а не из количества ядер/процессоров. Некоторые технические подробности ================================= Ниже приводятся некоторые предварительные соображения о том, как работа с агентами-близнецами может выглядеть на уровне программного кода. При регистрации агентов-близнецов SObjectizer нужно будет передать объект, реализующий следующий интерфейс: :: class twin_agents_controller_t { public : //! Создание нужного количества агентов-близнецов. /*! * Может быть создано любое их количество. * Аргумент desired_count задает только оптимальное с * точки зрения диспетчера число. */ virtual std::vector< so_4::rt::agent_t * > create( //! Общее имя для создаваемых агентов. const std::string & common_name, //! Оптимальное с точки зрения диспетчера число агентов-близнецов. unsigned int desired_count ) = 0; //! Выбор получателя очередного сообщения. /*! * Этот метод вызывается при получении сообщения, которое * адресуется кому-то из агентов-близнецов. * * Данный метод отвечает за выбор конкретного агента, который будет * обрабатывать данное сообщение. Метод может выбрать любого агента * или всех агентов сразу. */ virtual std::vector< so_4::rt::agent_t * > select_message_target( //! Описание сообщения. const message_routing_info_t & message, //! Список агентов-близнецов, из которых нужно делать выбор. const std::vector< so_4::rt::agent_t * > & twins_list ) = 0; }; При этом SObjectizer будет поступать с агентами-близнецами следующим образом. 1. При регистрации агентов-близнецов SObjectizer использует экземпляр ``twin_agents_controller_t`` в качестве фабрики. Метод ``create()`` создает экземпляры агентов, которые SObjectizer регистрирует под одним общим именем. Для каждого агента запускается метод ``so_on_subscription()``, отрабатывает ``evt_start()`` и т.д. При этом каждый агент-близнец думает, что он единственный агент в системе, имеющий данное общее имя. 2. При отсылке сообщения на общее имя агентов-близнецов SObjectizer использует экземпляр ``twin_agents_controller_t`` для выбора агента-получателя. Для этого вызывается метод ``select_message_target`` и сообщение доставляется тем агентам, указатели на которых оказались в возвращенном ``select_message_target`` векторе. Подобный подход обеспечивает прозрачность агентов-близнецов для: - самих агентов-близнецов. Они должны быть написаны так, чтобы каждый из них думал, что он единственный агент в системе. Что позволит легко изменять их количество в зависимости от конкретных условий (например, от числа доступных ядер/процессоров); - прикладного кода, который отсылает агентам-близнецам сообщения. Снаружи видно только одно общее имя агента и используются штатные функции ``so_4::api::send_msg()``. Поэтому изменение числа агентов-близнецов или же полный отказ от агентов-близнецов не сказывается на остальных частях прикладной системы. Единственное место, которое знает детали работы агентов-близнецов -- это место создания ``twin_agents_controller_t`` и регистрация агентов-близнецов в SObjectizer. .. vim:ts=2:sw=2:sts=2:expandtab:tw=78:ft=rst: