Передо мной стояла задача – в краткие сроки предложить меры по стабилизации большого трехзвенного Java-приложения, имеющего проблемы с потреблением памяти и производительностью. Времени, как обычно, мало: 1-2 недели на все. На фирме отсутствовала подходящая инфраструктура мониторинга приложений, и в мою задачу не входило ее создавать. Вариант с использованием JConsole не подходил из-за необходимости анализировать потребление за продолжительное время и смотреть его после возможных внезапных перезагрузок приложений.
В одной из фирм, где я работал, было реализовано впечатляющее по удобству и простоте решение для мониторинга Java-приложений на основе RRD Tool. Состояло оно из несложной надстройки на perl-скриптах, обеспечивающих сбор и отображение данных через HTTP и ряда доработок-агентов сбора данных в самом приложении. Для меня это стало идеей решения, однако, времени на написание обвязки над RRD у меня не было.
После аккуратного поиска нашелся бесплатный инструмент, реализующий необходимую мне надстройку – Cacti. Cacti это приложение, написанное в инфраструктуре Apache-PHP-MySql, позволяющее настраивать сбор и отображение данных мониторинга на основе веб-интерфейса. Разобраться с ним оказалось несложно, пару дней для подъема инфраструктуры, затем настройка и дописывание агентов сбора данных и все.
Далее в статье подробно описывается решение, позволившие решить мою задачу и, в конце концов, провести успешную стабилизацию приложения на фирме.
Что нужно для работы:
- Cacti 0.8.7i (самая свежая версия на момент реализации решения)
- Apache 2.2.21 (движок для Cacti)
- PHP 5.3.8 (платформа, на которой написана Cacti)
- MySQL 5.5 (хранение настроек Cacti)
- RRDTool 1.2.30 (отрисовка диаграмм и хранение данных мониторинга)
(указаны версии, на которых запустился мониторинг, подойдет любая работающая связку Apache-PHP-MySql)
Опишу принцип работы мониторинга на Cacti: при помощи «назначенных заданий» Windows (или cron в unix) производится периодический запуск опроса (polling) агентов сбора данных, которыми в моем случае были: сама JVM (потребление памяти) и специализированные доработки приложения. Собранные сведения размещаются в базе данных RRD – т.е. в циклических буферах в виде файлов. Далее, накопленные данные из RRD можно просматривать через веб-интерфейс Cacti в различных масштабах в разрезе минут, часов, дней, месяцев, др.
План работы – поднять всю необходимую инфраструктуру, адаптировать анализируемые приложения для сбора данные и настроить сбор и вывод данных в Cacti.
Настройка инфраструктуры
PHP
Добавить путь к php.exe в переменную PATH, тот же путь прописать в переменной PHPRC
Скопировать файл php.ini-production в php.ini и внести в php.ini следующие изменения:
Раскомментировать строчки:
extension_dir = c:\php\ext extension=php_mysql.dll extension=php_snmp.dll extension=php_sockets.dll cgi.force_redirect = 0 date.timezone = "Europe/Moscow"
Apache
Добавить в conf\httpd.conf следующие строки:
LoadModule php5_module c:\php\php5apache2_2.dll AddType application/x-httpd-php .php DirectoryIndex index.html index.htm index.php
MySql
Добавить путь к mysql.exe в переменную PATH
Создание схемы cacti:
mysql --user=root --password create cacti
Импортируем структуру данных схемы cacti
mysql --user=root --password cacti < c:\apache2\htdocs\cacti\cacti.sql
Создаем пользователя cactiuser:
mysql --user=root --password
Далее в командной строке MySql:
mysql> create user cactiuser@localhost IDENTIFIED BY ’cactiuser’ mysql> GRANT ALL ON cacti.* TO cactiuser@localhost; mysql> flush privileges;
Для быстрой проверки работоспособности связки php-mysql-apache я использовал следующих скрипт php:
<?php mysql_connect( "localhost", "cactiuser", "cactiuser" ) or die("Can't connect"); mysql_query( "USE cacti" ) or die("Can't select mysql database"); echo "Success\n"; ?>
Его надо положить в файлик с именем, например, testphp.php, скопировать в директорию Apache htdocs/ и загрузить страничку localhost:8080/testphp.php. Должна появиться надпись «Success».
Настройка Cacti
Разархировать дистрибутив cacti в директорию Apache /htdocs.
Проверить что файл cacti /include/config.php содержит следующие строки:
$database_default = "cacti"; $database_hostname = "localhost"; $database_username = "cactiuser"; $database_password = "cactiuser"; $database_port = "3306";
Зайти на адрес localhost:8080/cacti/ под логином admin/admin.
В настройках Settings->Paths задать пути к внешним утилитам (рекомендуется использовать пути в стиле Unix, например c:/php/php.exe).
Настроить запуск команды «php cacti/poller.php» каждые 5 минут (через Windows Scheduled Tasks). У меня для этого используется батничек:
start /MIN php.exe cacti\poller.php
Настройка Cacti Spine (опционально: это poller, написанный на C++, который используется для ускорения опроса, рекомендуется Cacti)
Разархивировать spine архив в директорию cacti убедиться что spine.conf содержит следующие строки:
DB_Host 127.0.0.1 # строго не localhost!!! DB_Database cacti DB_User cactiuser DB_Password cactiuser DB_Port 3306
Выбор и настройка сбора данных
Я пробовал два способа сбора данных – SNMP и опрос JMX сервера в составе JVM и приложений. SNMP поддерживается Cacti и его разумно использовать, если нужно смотреть только использование памяти JVM и, нужно это сделать очень быстро. Я начал с SNMP, но после первых успехов перешел на JMX. Cacti не поддерживает JMX, поэтому за дополнительную гибкость надо платить — требуются усилия по написанию приемной и ответной части на Java.
Ниже привожу соответствующий код.
Код для опроса JVM по состоянию памяти (аналогично пишется код для произвольного JMX-сервера):
import java.io.Closeable; import java.io.IOException; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryUsage; import javax.management.JMX; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; public class JvmHeapJmxClient { public static void main(String[] args) { JMXConnector jmxc = null; try { String jmxHost = args[0]; String jmxPort = args[1]; String jmxConnectionString = "service:jmx:rmi:///jndi/rmi://" + jmxHost + ":" + jmxPort + "/jmxrmi"; JMXServiceURL url = new JMXServiceURL(jmxConnectionString); jmxc = JMXConnectorFactory.connect(url, null); MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); ObjectName memoryMBeanName = new ObjectName("java.lang:type=Memory"); MemoryMXBean memoryMBeanProxy = JMX.newMXBeanProxy(mbsc, memoryMBeanName, MemoryMXBean.class, true); MemoryUsage memoryUsage = memoryMBeanProxy.getHeapMemoryUsage(); echo( "used:" + memoryUsage.getUsed() + " " + "committed:" + memoryUsage.getCommitted() + " " + "init:" + memoryUsage.getInit() + " " + "max:" + memoryUsage.getMax()); } catch(Exception e) { e.printStackTrace(); } finally { closeStream(jmxc); } } private static void echo(String msg) { System.out.println(msg); } private static void closeStream(Closeable stream) { try { if (stream != null) { stream.close(); } } catch (IOException e) { e.printStackTrace(); } } }
Код Java для запуска JMX. Все эти сложности нужны для фиксирования порта и имени хоста, что требуется при наличии межсетевого экрана. Для каждого сервиса требуется по 2 порта, как видно из кода – один для http доступа, другой для RMI. Распределение портов очевидно можно сделать по-другому (в данном случае RMI-порт = http-порт + 1), в том числе явно указать оба порта. В строку запуска приложений нужно будет добавить следующие слова (jmxagent – jar-файл с кодом агента):
-Djmxagent.port=<SERVER_JMX_PORT> -Djmxagent.host=<SERVER_HOST> -javaagent:jmxagent.jar
import java.io.IOException; import java.lang.management.ManagementFactory; import java.rmi.registry.LocateRegistry; import java.util.HashMap; import javax.management.MBeanServer; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; public class JmxFirewallAgent { private JmxFirewallAgent() { } public static void premain(String agentArgs) throws IOException { try { final int rmiRegistryPort = Integer.parseInt(System.getProperty("jmxagent.port")); LocateRegistry.createRegistry(rmiRegistryPort); final int rmiServerPort = rmiRegistryPort + 1; MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); HashMap<String,Object> env = new HashMap<String,Object>(); final String hostname = System.getProperty("jmxagent.host"); JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://" + hostname + ":" + rmiServerPort + "/jndi/rmi://" + hostname + ":" + rmiRegistryPort + "/jmxrmi"); JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); cs.start(); } catch (Exception e) { e.printStackTrace(); } } }
Далее идут скриншоты наиболее важных настроек Cacti.
Настройка опроса – важный момент в общих настройках Cacti. Я использую Spine, опрос запускается каждые 5 минут, данные снимаются каждые 10 секунд. Смотрите далее настройки RRD в составе Cacti – их требуется продумать заранее, т.к. далее менять без перезаписи базы RRD проблематично. Здесь неплохая подборка советов по этим настройкам:
Мои настройки RRD.
Пример содержимого одной настройки:
Настраивам Device – это сервер с IP-адресом, с которого будут забираться данные. У нас несколько серверов, на каждом из них запущено несколько сервисов Java.
Настраиваем Data Input Method – задание способа сбора данных. В моем случае это батничек, который запускает Java программу опроса JMX (ее исходные коды были приведены выше.) Входные параметры у него это адрес и порт сервера, т.е. Java-приложения.
Пример описания входного параметра – в данном случае адрес сервера. Используется предопределенное ключевое слово – hostname, которое автоматически заполняется Cacti. Второй параметр будет заполняться для каждой настройки сбора данных Data Source, как будет видно дальше.
Выходные параметры, которые возвращает батничек. Cacti понимает их в формате:
ключ1:значение1 ключ2:значение2
Моя программа возвращает 4 выходных значения, вот пример задания одного из них (commited):
Далее создаем Data Template – шаблон для задания источников данных. Самая важная настройка в Cacti, на мой взгляд. Шаблон связывается с настройкой базы RRD, в нем указываются параметры для хранения в базе (Data Source Item) и дополнительные настройки, в данном случае это jmx host и jmx port, первый, как уже говорилось, заполняется автоматически, второй будет заполняться на каждом источнике данных, т.е. источник данных будет соответствовать одному Java-приложению.
На основе шаблона создается Data Source – источник данных, которые затем можно будет отображать на диаграммах Graph. В источнике указывает Device, к которому надо подключаться для сбора данных, шаблон источника, имя и месторасположение базы данных RRD. В моем случае также требуется указать дополнительный параметр – порт JMX. Важный момент – при существенных обновлениях настроек сбора данных в Data Source или Data Template (например, удаление-добавление параметров Data Source Item) требуется пересоздание базы данных RRD. Это можно сделать руками вне Cacti (я до этого не добрался) или пересоздать Data Source с потерей всех предыдущих данных. Это, видимо, самое неприятная особенность связки Cacti-RRD, с которой мне пришлось столкнуться.
Приступаем к отображению данных путем задания шаблонов диаграмм Graph Template. Настройки в основном задают способ визуализации данных. Требуется указать параметры из Data Template, которые требуется выводить на диаграмме и в каком виде. Насколько я понимаю все эти настройки – непосредственная оболочка над командным интерфейсом RRD.
Пример задания параметра Data Source. Я использую для отображения потребления памяти 5 элементов на диаграмме: Макс, мин, и текущее потребление в виде сплошной закраски (порядок вывода важен!), затем два параметра – максимальное выделенное и зарезервированное потребление в виде линий. Пример, см. ниже.
Диаграмма Graph – задается шаблон, сервер и задание соответствия между конкретным Data Source и паметрами шаблона диаграммы:
Пример окончательной диаграммы использующей все заданные настройки: Видно что потреблениев среднем на уровне 3Гб, однако довольно часто достигает максимальной выделенной границы в 6Гб (-Xmx). Данные можно смотреть с произвольной детальностью (от 1 тика сбора до 2лет, как на примере и больше). Все это зависит от заданных настроек базы RRD. С моими настройками мне вполне хватило информации для решения проблем с потреблением.
На этом описание моего решения заканчивается. Подчеркиваю, что основная идея решения – это скорость реализации при высоком качестве полученного результата. Вопросы и идеи по улучшению приветствуются.
Ту часть настроек, которая касается моих специфических параметров производительности я приводить не стал, поскольку они полностью аналогичны уже описанным.
Спасибо за внимание!
Выводы:
Cacti позволил мне успешно решить задачу быстрой настройки мониторинга Java-приложений. Если вам нужно быстро с нуля сделать мониторинг – рекомендую это как один из рабочих вариантов. Если в ваши планы входит построение долгосрочного мониторинга, то, на мой взгляд, имеет смысл посмотреть на следующие варианты: внедрять мощные системы типа Nagios, либо писать свою специализированную надстройку над RRD. Плюсы и минусы Cacti:
Минусы решения:
- Довольно быстрое нарастание количества однотипных настроек в случае большого числа сред и серверов приложений Java.
- Ограниченная производительность «неродных» JMX решений для Cacti.
Плюсы решения:
- Высокая скорость развертывания при минимальном дополнительном кодировании
- Простота и удобство интерфейса просмотра диаграмм и их настройки (не нужно сразу учить что-то сложное)
ссылка на оригинал статьи http://habrahabr.ru/post/179391/
Добавить комментарий