вторник, 9 июня 2015 г.

[prog.c++11] О планах по поводу delivery priorities и overload control для версии 5.6.0

Начал писать нижеследующий текст как ответ для LOR-а. Но, т.к. объем текста стал быстро расти, решил утащить его в блог, дабы иметь под рукой.

Тема с overload control имеет давнюю историю. Лет шесть назад активно обсуждали ее с Дмитрием Вьюковым в google-groups. После чего эта тема всегда держалась в поле зрения, но реализовать что-либо не получалось, т.к. хороший overload control должен быть заточен под задачу. А как объединить общий механизм (коим является SO-5) с domain-specific overload control... Вот это был (да и остается) непростой вопрос. Эта тема несколько раз обсуждалась (#1, #2, #3, #4), но практическим итогом на данный момент является такой механизм, как message limits.

Message limits позволяют задать ограничения на количество сообщений конкретного типа, которые могут ждать в очереди агента. Например, не более 100 сообщений mouse_move, не более 1000 сообщений log_message, не более 1 сообщения handle_request и т.д. Вместе с ограничением на количество сообщений задается и тип реакции на превышение лимита. Например, просто выбросить лишнее сообщение, переслать лишнее сообщение другому агенту, трансформировать сообщение во что-то другое и переслать другому обработчику. Ну и, конечно, крайний вариант -- тупо вызвать std::abort().

Это то, что уже есть в SO-5 (начиная с версии 5.5.4) и может использоваться "из коробки". Но это механизм overload defense, а не overload control. Для полноценного overload control нужно иметь возможность быстро среагировать на рост размера очереди и предпринять какие-то действия.

Здесь перспективно смотряться приоритеты доставки.

Идея пока такая: агент вешает какие-то нотификаторы на свою очередь сообщений (вероятно, это будет расширение механизма message limits, т.е. будут отдельные пороги по каждому типу сообщений), как только диагностируется превышение порога, нотификатор отсылает агенту специальное сообщение. Агент ловит это сообщение с более высоким приоритетом и получает возможность как-то среагировать на рост очереди. Например, перейти в другое состояние, создать дополнительных агентов, которые будут участвовать в обработке и т.д.

Собственно, в этом и состоит основная цель работ по добавлению приоритетов доставки. Хотя эти приоритеты могут оказаться полезными и в других ситуациях.

В качестве способа добавления приоритетов в SO-5.6.0 сейчас рассматривается такой.

Агент может определять т.н. event-stream-ы. По умолчанию у агента уже есть дефолтный event-stream с нулевым приоритетом. Обычные методы подписки связывают обработчики событий с дефолтным event-stream-ом.

Добавляется возможность создать произвольное количество дополнительных event-stream-ов с другими приоритетами. Подписываться агент должен будет уже на конкретный event-stream. Т.е. если раньше агент делал так:

class my_agent : public so_5::rt::agent_t
{
public :
   virtual void so_define_agent() override
   {
      so_subscribe_self().event( my_agent::handle_request )
            .event( my_agent::reconfigure )
            .event( my_agent::overload_detected );
   }
...

То в версии 5.6.0 появится возможность делать вот так:

class my_agent : public so_5::rt::agent_t
{
   // Дополнительный event-stream с более высоким приоритетом.
   so_5::rt::event_stream_t m_overload_stream = so_make_event_stream(1);

public :
   virtual void so_define_agent() override
   {
      // Подписка на сообщения из дефолтного стрима.
      so_subscribe_self().event( my_agent::handle_request )
            .event( my_agent::reconfigure );

      // Подписка на более высокоприоритетное сообщение из
      // дополнительного event-stream-а.
      so_subscribe_self( m_overload_stream )
            .event( my_agent::overload_detected );
   }
...

Вероятно, будут созданы новые диспетчеры, которые реализуют поддержку приоритетной доставки. Тут дело в том, что диспетчер с поддержкой приоритетов должен иметь более сложную логику и более высокие накладные расходы. Поэтому выглядит разумным иметь два типа диспетчеров -- обычные, без поддержки приоритетов, но с высокой скоростью обработки заявок. Плюс приоритетные диспетчеры, не такие быстрые, но зато способные обрабатывать event-stream-ы с разными приоритетами.

При привязке агента к диспетчеру диспетчер будет забирать у агента информацию о всех event-stream-ах агента. Соответственно, если обычный диспетчер обнаружит, что к нему привязывают агента с несколькими event-stream-ами, то он откажется это делать и агент не будет зарегистрирован.

Приоритетный диспетчер будет забирать все event-stream-ы агента и рассовывать их по своим очередям заявок. И обслуживать, соответственно, в порядке убывания приоритетов. Т.е. пока есть непустые event-stream-ы с самым высоким приоритетом, event-stream-ы с более низким приоритетом не обслуживаются.

Однако, event-stream-ы могут использоваться не только для обеспечения приоритетов доставки. Так же они могут позволить еще такой сценарий overload control, как выбрасывание самого старого сообщения какого-то типа. Допустим, у агента в очереди стоит 100 сообщений mouse_move. Приходит 101-е сообщение. Можно было бы просто выбросить самое старое mouse_move, т.к. смысла в нем нет. Но сейчас в SO-5 такой возможности нет, т.к. это самое старое сообщение находится где-то в очереди заявок диспетчера и добраться до него не представляется возможным.

А вот такая сущность, как event-stream может позволить задешево выбрасывать самое старое из находящихся в нем сообщений.

Так же, теоритически, можно рассматривать операции start/stop/pause для event-stream. Т.е. агент, переходя в какое-то состояние (например, reconfiguration) пристанавливает какой-то event-stream и сообщения не добавляются в этот поток и не извлекаются из него.

Пока черновые планы выглядят как-то так. Насколько полно все это это будет воплощено в жизнь... Трудно сказать. Опыт показывает, что пока не сделаешь и не отладишь, лучше не зарекаться.

Но даже если все это будет сделано, то нельзя будет сказать, что в SO-5.6.0 появятся полноценные механизмы overload control на все случаи жизни. Скорее просто набор базовых примитивов для создания заточенных под конкретную прикладную задачу механизмов окажется более обширным и удобным. С помощью которых разработка хорошего способа контроля за нагрузкой будет проще, но все равно она будет на плечах прикладного программиста. И, вероятно, идиома collector-performer еще долго будет сохранять свою актуальность.

Комментариев нет: