Продолжение серии статей, рассказывающих о построении своими руками системы мониторинга рассчитанной на большой поток событий.
Схема хранения данных, выбор протокола, реализация агентов мониторинга.
В прошлой статье мы определили что такое система мониторинга и как она оценивается. В этот раз мы с вами попытаемся разобраться с архитектурой и реализовать агенты мониторинга для типовой (сферической в вакууме) инфраструктуры уровня крупного отдела некоей корпорации.
При мониторинге чаще всего отслеживается некоторое ограниченное во времени событие. Это может быть например проседание той или иной статистики, срабатывание датчика, или например недоступность какого-либо сервиса. Соответственно каждае событие может быть активным или завершившимся. Основное действие, которое может выполнить оператор над событием — подтвердить (далее — ACK от acknowledged) или, если событие повторяется периодически, выставить признак периодического события (далее PACK) для того чтобы не замусоривать аларм-лист большим числом записей. В случае периодических событий, при каждой регистрации не будет содаваться новая запись в аларм-листе, а будет увеличиваться счетчик регистрации. В добавок к этому у оператора должна быть возможность отбросить ошибочно пришедшее сообщение, например переместив его в архив.
Итак, каждое событие может быть активным или завершившимся и иметь при этом некоторый статус:
- (NACK) — не периодическое, не подтвержденное. Присваивается по умолчанию.
- (ACK) — не периодическое, подтвержденное.
- (PACK) — периодическое, подтверждается автоматически.
Помимо статуса исходя из важности каждого объекта мониторинга и влияния того или иного события ему назначают некоторый приоритет исходя из которого оператор определяет очередность обработки событий. Обычно выделяют четыре приоритета событий
- Critical — критические сбои, способные повлечь за собой неустранимые последствия или длительные отказы. (Например срабатывание пожарной сигнализации, датчика затопления или одновременный отказ основного и резервного источников питания)
- Major — события оказывающие существенное влияние на основную функцию объекта мониторинга. (Например падение сетевых интерфейсов, резко подскочившая загрузка оперативной памяти или процессора, перегрев)
- Minor — события оказывающие слабое влияние на основную функцию объекта мониторинга.
- Information — события не влияющие на основную функцию объекта мониторинга. (Например сообщение о плановом снятии backup’a)
Так же имеет смысл подразделять события на категории. Например:
- EXT — события, вызванные внешними факторами (пропадание внешнего питания, срабатывание внешних датчиков)
- INT — события непосредственно относящиеся к объекту мониторинга
- OTH — другие, некатегоризированные, события.
Естественно приведенная классификация условна. Но разделение аварий на категории позволяет сильно упростить работу операторов.
Исходя из всего вышеизложенного попробуем описать аварию как объект класса Alarm
public class Alarm
{
public enum Status
{
NACK, // Не подтвержденное
PACK, // Периодическое
ACK // Подтвержденное
}
public enum Priority
{
Critical = 1, // Критическое, первый приоритет
Major = 2, // Мажорное, второй приоритет
Minor = 3, // Минорное, третий приоритет
Info = 4 // Информация, четвертый приоритет или не требует действий
}
public enum Category
{
Ext, // Внешние
Intern, // Внутренние
Other // Не категоризированные
}
private Status _AlarmStatus = NACK;
public DateTime DateACK; // Дата установки статуса подтвержденного собыитя
public DateTime DateUnACK; // Дата снятия статуса подтвержденного собыития
public DateTime DatePACK; // Дата установки статуса периодического события
public DateTime DateUnPACK; // Дата снятия статуса периодического события
public DateTime DatePrior; // Дата изменения приоритета
public DateTime DateStart; // Дата начала события
public DateTime DateCeasing; // Дата завершения события
public Status AlarmStatus // Статус события
{
get
{
return _AlarmStatus;
}
set
{
if(_AlarmStatus != value)
{
switch(_AlarmStatus)
{
case Status.ACK:
DateUnACK = DateTime.Now;
break;
case Status.PACK:
DateUnPACK = DateTime.Now;
break;
}
switch (value)
{
case Status.ACK:
this.DateACK = DateTime.Now;
break;
case Status.PACK:
this.Date.PACK = DateTime.Now;
break;
}
_AlarmStatus = value;
}
}
}
private Category _AlarmCategory;
public Category AlarmCategory // Категория события
{
get
{
return _AlarmCategory;
}
}
private bool _Ceasing = false;
private int _PACKCount;
public int PACKCount // Счетчик регистрации периодического события
{
get
{
return _PACKCount;
}
}
public bool Ceasing // Флаг завершения события
{
get
{
return _Ceasing;
}
set
{
if (_Ceasing != value)
{
_Ceasing = value;
if (_Ceasing)
{
DateCeasing = DateTime.Now;
}
else
{
DateCeasing = null;
if (AlarmStatus == Status.PACK)
{
PACKCount++;
}
}
}
}
}
private Priority _AlarmPriority;
public Priority AlarmPriority // Приоритет события
{
get
{
return _AlarmPriority;
}
set
{
if (_AlarmPriority != value)
{
DatePrior = DateTime.Now;
_AlarmPriority = value;
}
}
}
public string AlarmObject; // Имя объекта мониторинга
public string AlarmSource; // Имя источника, от которого было получено сообщение
public string AlarmInfo; // Краткое описание события
public string AlarmDescr; // Подробное описание события
public Alarm (Category Cat, Priority Prior, string Ob, string Source, string Info, string Descr)
{
_AlarmCategory = Cat;
_AlarmPriority = Prior;
AlarmObject = Ob;
AlarmSource = Source;
AlarmInfo = Info;
AlarmDescr = Descr;
}
}
Это базовый класс события на основе которого мы дальше будем реализовывать логику работы с событиями.
Событие однозначно идентифицируется категорией, приоритетом, объектом мониторинга и кратким описанием. Для полноты картины добавляется еще детальное описание.
Да, если вы еще не поняли все куски кода здесь будут на C#, нравится мне этот музыкальный язык не смотря на его M$ родословную. 🙂
Теперь попробуем определиться с протоколом, по которому будут общаться агенты мониторинга с сервером, а тот в свою очередь с аларм-терминалами. Традиционным решением будет вариант, при котором агенты мониторинга при каждой регистрации события заносят его в базу данных. Из которой, в свою очередь их забирают аларм-терминалы. В этом случае сервер системы мониторинга представляет из себя сервер базы данных с минимальной логикой реализованной в той же базе данных.
Для того чтобы добавить в класс Alarm поддержку взаимодействия с базой данных, в него нужно добавить всего пару методов. И хотя мы этим займемся чуть позже, взаимодействие между агентами мониторинга, сервером системы мониторинга и аларм-терминалами мы реализуем без прямого (или слой доступа) обращения агентов мониторинга к базе данных.
Основная задача системы мониторинга, как уже говорилось выше, это максимально оперативно и надежно оповестить оператора о событии (сбое). И в этом случае задача запихнуть запись в базу оказывается второстепенной по отношению к гарантированной доставке сообщения о событии оператору.
По этому в качестве протокола для системы мониторинга мы возьмем XMPP (Jabber). Почему именно его, а не скажем POP/SMTP, Telnet или (не к ночи будь помянут) SNMP ? Вопервых XMPP стал во многих, даже довольно крупных (и неповоротливых) компаниях стандартом для внутрикорпоративного сервиса мгновенных сообщений, и в них имеется в наличие уже поднятый и настроенный сервер. Во вторых, в отличие от Telnet, XMPP обеспечивает большую безопасность (шифрование и все такое прочее 🙂 ). Ну и в конце концов он быстрее чем POP/SMTP, мгновенные сообщения всетаки 😉 . Это основные причины. В дополнение к ним, при правильной реализации агентов мониторинга, можно обойтись без разработки приложений — аларм-терминалов (ага — как же 🙂 ). Ну и наконец для C# есть хорошая библиотека, реализующая XMPP — agsXMPP .
Итак, мы определиилсь с тем как мы будем передавать сообщения о событиях, теперь нужно определиться с тем как эти события будут генерироваться.
Каждый агент мониторинга представляет из себя приложение (демон, службу) (лишнее вычеркнуть 🙂 ) которое на основании некоторых правил задаваемых конфигурационным файлом или динамически (например пользовательскими командами через тот же джабер) генерирует сообщения о начале, окончании или изменении приоритета.
Чаще всего для генерации того или иного события должно выполняться не одно а несколько условий. Например для идентификации пожара не достаточно срабатывания пожарных датчиков, должен так же присутствовать пеерегрев оборудования. В противном случае каждый неисправный пожарный датчик будет вызывать серьезный переполох.
Условий же, из которых составляются правила — не так много. Это наличие/отсутствие чего-либо где-либо, и достижение какого-либо числового показателя некоторого порога. Или если пользоваться красивыми формулами :
Для некоторого объекта $$ \omega $$ правило $$ R_1 (\omega , \Omega) $$ будет иметь вид
$$ R_1 (\omega, \Omega ) = \left\{ \begin{array}{lc} 1, & \omega \in \Omega, \\ 0, & \omega \notin \Omega . \end{array} \right. $$
где $$ \Omega $$ — некоторое множество объектов.
А для некоторого числового показателя $$ \phi $$ правило $$ R_2 (\psi , g_0 , g_1 ) $$ будет иметь вид
$$ R_2 (\psi, g_0 , g_1 ) = \left\{ \begin{array}{lc} 1, & g_0 \leq \psi \leq g_1, \\ 0, & \psi < g_0, \\ 0, & g_1 < \psi. \end{array} \right. $$
где $$ g_0 $$ и $$ g_1 $$ — границы интервала.
Соответственно решение о появлении или завершении события $$ A $$ описываемого набором объектов $$ [\omega_1 , … , \omega_n] $$ и набором интервалов числовых значений $$ [ I_1=(g_0,g_1)_1 ,…, I_m = (g_0,g_1)_m ] $$ при состоянии объекта мониторинга $$ S $$ описываемого набором множеств объектов $$ [ \Omega_1 ,…, \Omega_n ] $$ и набором числовых значений $$ [\psi_1 ,…, \psi_m] $$ принимается на основании логической функции
$$ \begin{array}{lcc} P_A^S = & [ R_1 ( \omega_1 , \Omega_1 ) \vee … \vee R_2 ( \psi_j , I_j ) ] & \wedge … \wedge \\ & [ R_1 ( \omega_l , \Omega_l ) \vee … \vee R_2 ( \psi_m , I_m ) ] & \end{array} $$
Конфигурация которой, для каждого отслеживаемого события $$ A $$ может быть своя.
Каждый агент так же хранит список активных событий (Ceaing == false) — Current Alarm List или CAL. Если при очередной проверке логическая функция $$ P_A^S $$ возвращает отрицательное значение, то генерируется сообщение о завершении аварии (полю Ceasing объекта класса Alarm присваивается значение TRUE ) и запись удаляется из CAL агента.
О том как события отрабатываются на сервере и передаются на аларм-терминалы — в следующий раз.
По-моему ошибка в алгоритме есть.
Наверняка 😉 К тому же это не алгоритм, а простое описание класса 🙂