Понимаю, что то, что я пишу в блоге про SObjectizer, интересует малую долю моих читателей. Однако, в данном случае нужно задействовать блог в качестве канала для привлечения желающих пообсуждать очень интересную и непростую тему. А именно: тему защиты агентной системы от перегрузки (overload control). Поэтому прошу прощения у тех, кому не интересно, всех же остальных приглашаю под кат.
Тема очень непростая. Помнится, лет семь назад мы очень плотно ее обсуждали с Дмитрием Вьюковым, тогда применительно к SObjectizer-4. Но до чего-то путного не договорились. Теперь же у меня есть буквально неделя-две, чтобы включить какой-то базовый механизм overload control в релиз версии 5.4.0. А конь, как говорится, еще не валялся. Так что задача, может, и безнадежная, но зато увлекательная.
Дело в том, что если мы работаем в рамках компонентной системы, в которой преобладают синхронные механизмы взаимодействия между компонентами, то сами эти механизмы выступают естественными регуляторами нагрузки. Например, представим себе классический Unix-овый подход, когда стандартный поток вывода одной программы подается на стандартный поток входа другой программы:
grep *.log "id:([0-9]*)" | tee id_lines.txt | grep -o "([0-9]*)" | sort | tee ids.txt
Здесь регулировка скорости поставщика информации происходит за счет его блокирования на синхронном обращении к printf (или std::cout), если читатель информации не успевает со своим scanf (или std::cin).
А вот когда компонентная система преимущественно использует асинхронное взаимодействие между компонентами (т.е. отсылка сообщения без ожидания результатов его обработки), то ситуация аховая. Т.е. компонент grep может так быстро формировать и отсылать свои сообщения компоненту tee, что tee не будет в состоянии их вовремя обрабатывать.
Последствия в этом случае очевидные: очередь сообщений к tee будет расти, память под эту очередь будет отжираться безостановочно. Из-за чего рано или поздно система начнет деградировать (например, уходя в своп) или же упадет вовсе (из-за исчерпания доступной памяти).
От таких ситуаций должен защищать механизм overload control.
Однако, механизмы overload control я бы разделил на две категории.
К первой категории относятся механизмы, учитывающие особенности прикладной области и позволяющие прикладному приложению адаптироваться к текущему профилю нагрузки с учетом этих особенностей. Например, в показанном выше примере с grep и tee прикладной механизм overload control не должен допускать потери информации между grep и tee. Поэтому он должен обеспечивать замедление grep-а, если tee не успевает разбирать поток входящих сообщений. С другой стороны, если у нас есть система контроля температуры воздуха, в которой замеры с датчика температуры идут сплошным потоком с темпом 3 замера в секунду, то в случае, если мы не успеваем этот поток обрабатывать, из трех новых сообщений мы вполне можем выбросить два первых, т.к. их информация все равно перекрывается данными из третьего сообщения (конечно, ситуация может быть и не такой простой, и нам могут быть нужны все три сообщения, но, если выбор идет между диагностировать перегрузку и как-то на нее прореагировать или же сделать вид, что все нормально и плавно деградировать до полного краха, лучше все-таки как-то среагировать).
Ко второй категории относятся механизмы, которые не завязаны на специфику предметной области. Они стараются обеспечить какие-то базовые гарантии среды исполнения. Например, о том, что 10K агентов (акторов, компонентов) смогут работать, если суммарно в очередях к ним будет стоять не более 1M сообщений. И что при появлении 10K+1 агента (актора, компонента) или при попытке поставить в очередь 1M+1 сообщение будет предпринято какое-то защитное действие, дабы система все-таки продолжала работать с тем, что есть, а не сваливалось в унылое говно плавную деградацию.
Для версии 5.4.0, думаю, следует сосредоточится именно на механизме из второй категории. А уже потом, посмотрев на применение SO-5.4.0 в реальных задачах, решать, как быть с механизмами из первой категории (например, делать ли их в виде внешних библиотек для SObjectizer, или же включать в ядро).
Соответственно, чтобы двигаться в этом направлении, сначала нужно обозначить цели. А потом, если цели понятны, непротиворечивы и утверждены мной теми, кто будет отвечать за реализацию, уже приступать к поиску путей воплощения их в жизнь. Для чего я создал тему на SourceForge, заглавный пост которой продублирую чуть ниже.
Приглашаю заинтересовавшихся высказать свое мнение или поделиться своим опытом. Или подсказать, где можно посмотреть на похожие вещи (про фреймворк SEDA я в курсе). В общем, буду признателен за любое участие, здесь или на SourceForge.
Текст поста с SourceForge
В качества развития темы Возможный вариант реализации overload control для очередей сообщений агентов.
Для того, чтобы реализовывать overload control сначала нужно обозначить цели, которые ставятся перед данным механизмом. Если правильно обозначить и согласовать цели, то затем проще будет двигаться в сторону реализации.
Итак, перед механизмом защиты SObjectizer от перегрузки стоят следующие цели:
1. Обеспечение работоспособности SObjectizer Run-Time в случае ошибки пользователя. Т.е. если пользователь где-то ошибся и для каких-то из его прикладных агентов генерируется нагрузка большая, чем агенты могут обработать, SObjectizer Run-Time должен воспрепятствовать потере работоспособности приложения из-за роста очередей и вызванной этим деградацией приложения.
2. Назначение жестких лимитов для очередей сообщений может дать SObjectizer возможность оптимизировать свою работу. Например, примерять очереди фиксированного размера с предварительно аллоцированным буфером. Что может увеличить пропускную способность очередей сообщений в разы по сравнению с неограниченными очередями, в которых динамическая память выделяется и освобождается по необходимости.
В список целей не входит предоставление пользователю каких-то механизмов адаптации своих агентов к какому-то профилю нагрузки на его приложение. Такие механизмы должны быть завязаны на особенности предметной области и могут требовать какой-то обратной связи от прикладных агентов. Посему для решения этой задачи лучше отталкиваться от набора успешных частных решений в различных проектах, путем их анализа, выделения общих частей, формирования на их основе каких-то универсальных, повторно используемых инструментов. И оформление этих инструментов в виде сопутствующих инструментов (по аналогии с тем, как транспортный слой выделен в отдельную библиотеку so_5_transport). И только в случае, если для эффективной реализации таких инструментов потребуется тесное взаимодействие с ядром SObjectizer, они будут интегрированы в ядро SObjectizer.
Комментариев нет:
Отправить комментарий