суббота, 13 июня 2015 г.

[prog.c++11.sobjectizer] Вынесу из комментариев про overload control

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

Итак, был задан вопрос: "Что будет происходить, если агенты выполняют CPU-intensive задачи, а запросы приходят быстрее, чем все ядра процессора успевают их обработать? Тут введением диспетчера проблему не решишь." Вопрос очень хороший. Ответить на него сейчас можно вот так:

А это уже область применения overload control :) Именно поэтому вокруг overload control мы делаем так много кругов. Но пока, к сожалению, ограничиваемся полуфабрикатами.

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

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

Либо мы делаем отсев части заявок. Т.е. отказываемся нормально выполнять какие-то заявки (либо самые старые, либо самые новые, либо по каким-то других критериям, например, запросы от платных абонентов/клиентов стараемся обрабатывать, а от бесплатных анонимусов -- выбрасываем). Отказ может быть оформлен разными способами. Самый простой -- выкидывание заявки как будто ее вообще не было. Тогда задачей отправителя заявки будет контроль тайм-аутов и генерация какого-то осмысленного ответа по истечении времени ожидания. Более сложный -- обработка заявки по другому алгоритму. Скажем, выдача ответа со специальным кодом о том, что система в данный момент перегружена и запрос обслужен быть не может. Или же фиксация заявки для последующей пакетной обработке в офлайне (мол, прямо сейчас заказанный вами прогноз погоды мы вам предоставить не можем, но как только освободимся, вышлем вам его на email).

Либо делаем обратную связь между отправителем и обработчиком. В самом простом варианте -- отправитель не шлет новый запрос, пока не получит ответ на предыдущий. В более сложном варианте -- отправитель шлет N запросов, но затем берет паузу, пока не получит M ответов (где M<=N). В еще более сложном варианте отправитель контролирует скорости поступления ответов и приостанавливает отсылку когда обнаруживает, что скорость ответов снижается ниже какого-то уровня.

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

В SO-5 сейчас есть базовые средства для создания предметно-ориентированных механизмов overload-control. Но готовых реализаций overload-control нет.

Сейчас можно использовать лимиты для сообщений (message limits). Лимиты позволяют либо выбрасывать сообщения, либо передавать их другим исполнителям (которые вместо нормальной обработки, скажем, платежной заявки, будут генерировать ответы с уведомлением о том, что заявка не может быть выполнена прямо сейчас). На данный момент лимиты могут выбрасывать лишь самые новые сообщения, а старые, которые уже стоят в очередях агентов, выбросить нельзя. Хотелось бы добавить и такую возможность, но получится или нет, пока не известно.
В SO-5 есть пример, который показывает общую идею использования message limits: redirect_and_transform.

Так же сейчас можно использовать синхронное взаимодействие между агентами для того, чтобы ограничивать поток заявок между producer-ом и consumer-ом. Очень удобная штука, если есть возможность передавать запросы не по одному, а пачками. Т.е. producer собирает пачку запросов, затем синхронно дергает consumer и передает сразу всю пачку. Пока consumer обрабатывает ее, producer собирает новую пачку запросов. Если эта пачка была собрана быстрее, чем обработана предыдущая, то на синхронной передаче producer будет "проторможен" consumer-ом.
В SO-5 есть пример, который демонстрирует данную технику: collector_performer_pair.

Вот чего пока нет, так это возможности consumer-у определить момент, когда нагрузка на него превышает некоторый предел дабы предпринять какое-то действие. Скажем, consumer обнаруживает, что его очередь заполняется на 80% и переходит в состояние, в котором он не обрабатывает сообщения обычным образом, а предпринимает какие-то альтернативные действия: выдает ответ о невозможности нормальной обработки, фиксирует запросы для последующей обработки в оффлайне, делает более простую и грубую обработку (скажем, при кодировании/декодировании изображений/видео/звука) и т.д.

Думается, что для таких форм overload control нужно иметь приоритеты доставки сообщений и/или возможность дробления общего потока запросов к consumer-у на несколько подпотоков, каждым из которых можно управлять отдельно.

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



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