Мониторинг серверов через очередь заданий на JAVA

    Недавно был озадачен проблемой мониторинга нескольких десятков серверов (ну наверно редко кто не сталкивался с такой задачей). Проблему можно описать несколькими правилами:

    1. Нужно периодически пинговать сервер
    2. Иногда выполнять какое-либо действие с сервером (например, исполнение команды через ssh), которое засабмитил пользователь
    3. Действия с серверами могут нескольких типов, у каждого действия свой приоритет
    4. Таски (из п.1-3) нельзя выполнять одновременно для каждого сервера
    5. Таски могут завершаться с неудачей, например по причине отсутствия связи с сервером, нужно ждать пока связь восстановится и пытатся выполнить запланированную задачу




    Первое решение, которое приходит большинству в голову — запустить для каждого сервера свой поток и там делать свои дела. Это неплохо, но что делать если в процессе мониторинга набор серверов будет меняться? Запускать и завершать потоки в процессе мониторинга как-то неэлегантно. А что делать если серверов тысяча? Иметь тысячу потоков наверно можно, но зачем это делать когда большинство времени поток простаивает и ждет своего времени для очередного пинга?

    На данную проблему можно взглянуть с другой стороны и представить ее в виде классической задачи «producer-consumer». У нас есть продюсеры, которые производят таски (пинг, команда ssh) и у нас есть консьюмеры, которые эти таски исполняют. Разумеется продюсеров и консьюмеров у нас не по одну экземпляру. Решить нашу задачу «producer-consumer» в JAVA не просто, а очень просто используя классы PriorityQueue и ExecutorService.

    Начнем, как обычно, с юнит-теста:

        @Test
        public void testOffer() {
            PollServerQueue xq = new PollServerQueue();
            
            xq.addTask(new MyTask(1, 11));
            xq.addTask(new MyTask(2, 12));
            xq.addTask(new MyTask(1, 13));
            
            MyTask t1 = (MyTask)xq.poll();
            assertEquals(1, t1.getServerId());
            assertEquals(11, t1.getTaskId());
    
            MyTask t2 = (MyTask)xq.poll();
            assertEquals(2, t2.getServerId());
            assertEquals(12, t2.getTaskId());
            
            MyTask t3 = (MyTask)xq.poll();
            assertEquals(null, t3);
    
            xq.FinishTask(1);
            MyTask t5 = (MyTask)xq.poll();
            assertEquals(1, t5.getServerId());
            assertEquals(13, t5.getTaskId());
        }
    
    


    В этом юнит-тесте мы добавили в нашу очередь три задачи типа MyTask (первый аргумент конструктора означает serverId, второй — taskId). Метод poll извлекает задачу из очереди. Если извлечь задачу не удалось (например, задачи кончились или в очереди остались задачи для серверов, для которых уже выполняются задачи) — метод poll возвращает null. Из кода видно, что завершение задачи для serverId=1 ведет к тому, что из очереди можно извлечь следующую задачу для данного сервера.

    Ура! Юнит-тест написан, можно писать код. Нам потребуется:
    1. Структура данных (HashMap) для хранения текущих исполняемых задач для каждого сервера (currentTasks)
    2. Структура данных (HashMap) для хранения задачи, стоящих в очереди на исполнения. Для каждого сервера — своя очередь (waitingTasks)
    3. Структура данных (PriorityQueue) для последовательного опроса серверов. Необходимо, чтобы в следующий вызов poll() к нам приходила задача для другого сервера. Короче, структура типа револьвера, только пули после каждого выстрела остаются в барабане (peekOrder)
    4. Cтруктура (HashSet) для хранения и быстрого поиска идентификаторов серверов в револьвере, чтобы каждый раз не просматривать револьвер с первого до последнего элемента (servers)
    5. Простой объект для синхронизации (syncObject)


    Теперь процедура извлечения таски из очереди будет простой и короткой. И хотя код получился компактным, публиковать его здесь я не вижу смысла, а отошлю вас на https://github.com/get-a-clue/PollServerQueueExample

    Disclaimer: код на github'e не является законченным, в частности, в нем отсутствует возможность установки приоритетов для задач внутри очереди для каждого сервера и механизма обработки ошибок и возврата failed таски в очередь. Ну и сам код для пингования. Как говорится, меньше кода — лучше спишь. :)

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 10

      +2
      Очень странный подход, понимаю что, because i can, но не лучше ли использовать готовые инструменты?
      Завтра понадобятся отчеты, графики для наглядности или цвообще передать поддержку другому человеку и простое вроде бы решение превратится в дополнительные проблемы и потерю времени.
        0
        Конечно нет необходимости изобретать очередной велосипед в стиле nagios. Задачи, кроме тривиального пинга могут быть самые разнообразные, например выполнить последовательность действий через ssh на удаленном хосте или исполнить sql-скрипт. Я уж не говорю о периодическом опросе каких-нибудь веб-ресурсов с целью получения и обработки статистических данных.
          0
          Так нагиос все это может же, либо я не понял посыл вашего комментария.
            0
            Ну может и может, только программировать и кастомизировать все равно придется.
            А если конечными пользователями продукта будет являться еще кто-либо помимо сисадмина, то пускать их в нагиос совсем не прилично.
              0
              Graphite + Carbon
        +1
        Не понятно ровным счетом ничего. Какая конкретно задача решалась? Для кого предназначен мониторинг? Для админов/девелоперов? Или для того, чтобы в каком-то виде показывать данные мониторинга пользователями?

        ИМХО, касательно систем мониторинга, гораздо больший интерес представляет не конкретный фрагмент кода, а вся картина целиком.
        Каюсь, код на гитхабе не смотрел, но мне кажется, это должно быть указано в статье.

        1. Как происходит обнаружение хостов и сервера мониторинга?
        2. Какие механизмы используются для транспорта сообщений между сервером мониторинга и хостами?
        3. Как участники (сервер и хосты) обрабатывают потерю связи друг с другом?
        4. Откуда вообще (конкретно в вашем случае) берется набор тасков-проверок?
        5. Где, в каком виде хранятся результаты проверок? Насколько детальную информацию можно оттуда получать.
        6. Есть ли (конкретно в вашем случае) возможность разбивки проверок по разным группам/ролям?

        Вот было бы интереснее, если бы в статье описывался ваш опыт использования своих кастомных велосипедов (в хорошем смысле слова).
        Ну и, опять же, было бы интересно прочитать про это все в сравнении с другими (настоящими) системами мониторинга.
        PS: Мне кажется, вы-таки не видели sensuapp.org/
          0
          Данная статья не описывает законченное решение, а предлагает подход к реализации — отказ от схемы «один поток на сервер» и переход на очередь заданий, которая обрабатывается пулом потоков.
          0
          Статья интересная и задача актуальная. Я сам ищу решение: готовое приложение, которое можно было бы легко разместить на нескольких серверах (на случай, если сервер с которого мониторим, сам выходит из строя).
          Запускать тысячу потоков плохо. Но если «свои дела» при работе с каким-то сервером занимают продолжительное время? А что если запускать ограниченное количество потоков (не на каждый севрер, а скажем 10, или процент от общего количества серверов мониторинга), и каждый из потоков, освободившись берет очередное необработанное задание и из очереди?
            0
            Так и делается обычно. Таски, поступающие в очередь, разгребаются не одним потоком, а пулом воркеров. Если отпилить очередь от самого сервера (например, использовать rabbitmq), то вопрос отказоустойчивости решается достаточно тривиально — достаточно запустить насколько «разгребаторов» на разных машинах. Если интересует что-то готовое — могу повториться и посоветовать sensu (http://sensuapp.org). Правда, назвать его готовым решением тяжеловато. Это скорее фреймворк для построения своей собственной системы мониторинга. Работает оно приблизительно так, как я и описал — все сервисы, которые надо мониторить, подписываются через rabbitmq на определенные проверки. Отдельные инстансы самого сервера (их может быть несколько) принимают эти подписки и время от времени запрашивают у сервисов результаты проверок.
              0
              Проблем нет. В опубликованном в исходниках юнит-тесте в качестве пула потоков используется cachedThreadPool, что означает что максимальное количество потоков будет равно (но может быть и меньше) количеству серверов. Если использовать fixedThreadPool, то количество рабочих потоков можно сократить (например 1 поток на 10 серверов), но тогда есть риск что «медленные» сервера будут тормозить весь процесс обработки. Но и эту проблему можно решить, если выстроить приоритеты для «медленных» и «быстрых» серверов в «револьвере».

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое