Thursday, October 23, 2008

Интересный способ мониторинга рабочих сайтов

Иногда возникает ситуация, когда рабочий (production) сайт начинает тормозить, или вообще подвисать. Для нахождения проблемы можно просмотреть код в поисках узких мест или написать нагрузочный тест и посмотреть профилировщиком методы, которые выполняются много времени.

Но часто бывает так, что сайт начинает тормозить только при некотором стечении обстоятельств, например, когда заходит определенный пользователь на определенную страницу. Такого рода узкие места нагрузочным тестом не покроешь, так как не ясно, на каких именно данных проявляется проблема.

В этом случае можно воспользоваться утилитой jstack которая входит в состав JDK начиная с версии 1.5.0. Эта утилита позволяет просмотреть текущее состояние работающих в указанной виртуальной машине потоков (которые thread). Например,
user@laptop$ jstack 3344 "btpool0-439" prio=10 tid=0xa98da000 nid=0x1b3a in Object.wait() [0xa99ff000..0xa99ff130] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:482) - locked <0xb47e0078> (a org.mortbay.thread.BoundedThreadPool$PoolThread) "Java2D Disposer" daemon prio=10 tid=0xaa35c800 nid=0xb2e in Object.wait() [0xa9fd4000..0xa9fd50b0] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116) - locked <0xaf007a30> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132) at sun.java2d.Disposer.run(Disposer.java:125) at java.lang.Thread.run(Thread.java:619) Для каждого потока показаны его атрибуты, в том числе его название, и стектрейс.

Идея в том, чтобы на каждом запросе добавлять в название потока информацию, необходимую для идентификации узких мест. Тогда использовав jstack, из названия потока можно посмотреть, над чем в данный момент работает он работает.

Например, очевидными кандидатами на добавление в название потока являются адрес страницы, которая обрабатывается и логин пользователя. Код по добавлению этой информации логично разместить в HTTP-фильтре. Фрагмент его будет выглядеть приблизительно так:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String threadName = getUser().login + ": " + RequestInfo.get().getRequestUrl(); Thread.currentThread().setName(threadName); chain.doFilter(request, response); } Теперь, если в выводе jstack будет часто видно, например, такое
"medved: /heavy/calculation" prio=10 tid=0x9aa9a800 nid=0x2519 runnable [0x9ad18000..0x9ad 190b0] java.lang.Thread.State: RUNNABLE at com.site.heavy.calculation.Calc.inner(Calc.java:113) at com.site.heavy.calculation.Calc.outer(Calc.java:43) значит, для пользователя medved медленно показывается страница /heavy/calculation. Это уже можно профилировать.

Названия потока удобно использовать также в других случаях отладки, например, записать в него User-Agent, и тогда прямо в стектрейсе ошибки будет видно, под каким браузером она возникла.

Monday, October 13, 2008

Консерваторы и демократы

(Не удержался, чтобы не перепостить - уж очень понравилось.)

Я спросил несовершеннолетнюю дочь своих друзей, кем бы она хотела быть, когда вырастет. Она сказала, что хотела бы когда-нибудь стать Президентом Соединённых Штатов Америки. Оба счастливых родителя, либеральные демократы, присутствовавшие при разговоре, с гордостью переглянулись. Я спросил девочку: "Хорошо, допустим ты стала Президентом, что бы ты сделала в первую очередь?"

Она ответила: "Первым делом я бы предоставила пищу и жильё всем бездомным".

"Чудесно, - согласился я, - весьма достойная цель! Но вовсе не обязательно ждать того времени, когда ты станешь Президентом. Можно уже сейчас начать действовать в соответствии с твоим планом. Приходи ко мне в дом, выполи сорняки в саду, постриги траву на лужайке, подмети двор, и я заплачу тебе пятьдесят долларов. Тогда ты сможешь пойти к лавке, возле которой валяется один из бездомных, и вручить ему свои $50 на покупку еды или для сбережений в счёт покупки будущего дома".

Она надолго задумалась. Её мать смотрела на меня так выразительно, что я не берусь истолковывать её взгляд. В конце концов девочка подняла глаза и спросила: "Почему бы тогда этому бездомному самому не прийти к вам домой и не сделать эту работу - тогда вы бы прямо ему и заплатили эти 50 долларов?"

Я ответил: "Добро пожаловать в ряды консерваторов, дочка!"

Её родители до сих пор со мной не общаются...

Источник: http://art-of-arts.livejournal.com/

Thursday, October 9, 2008

Устанавливаем на Amazon EC2 и Amazon EBS Java-приложение с MySQL

На примере типичного сайта опишу последовательность действий по установке на хостинг Amazon.

Приложение - сайт на Java, который требует большой вычислительной мощности. Поэтому было принято решение сразу запускаться на Amazon. В работе сайт использует на чтение-запись базу MySQL. Сайт будет развернут на одном экземпляре (instance) Elastic Computing Cloud (EC2). База MySQL - на томе (volume) Elastic Block Storage (EBS). Все пользовательские данные будут храниться в MySQL. На диске экземпляра будут храниться настройки, а также скрипт сборки приложения, который будет собирать приложения из внешних источников (например, из репозитария Subversion).

Далее в тексте, при описании вводимых команд, если приглашение оболочки начинается на $ - команду следует выполнять на локальном компьютере, если приглашение начинается на # - на удаленном экземпляре EC2.

Установка утилит EC2 и настройка окружения

  1. Заходим на http://aws-portal.amazon.com, логинимся, покупаем EC2. На страницах нужно будет найти и запомнить Access Key ID, Secret Access Key и Account ID. Также нужно сгенерировать и сохранить файл с сертификатом X.509 (cert-11111111111111.pem) и файл с приватным ключем (pk-11111111111111.pem).
  2. Устанавливаем Amazon EC2 Command-Line Tools.
    1. Скачиваем с Amazon.
    2. Распаковываем в нужное место (например, /usr/ec2).
    3. Прописываем окружение (вариант для Linux): pupkin@local$ export EC2_HOME=/usr/ec2/ec2-api-tools-1.3-19403 pupkin@local$ export PATH=$PATH:$EC2_HOME/bin pupkin@local$ export EC2_PRIVATE_KEY=/home/pupkin/mega-project/ec2/pk-11111111111111.pem pupkin@local$ export EC2_CERT=/home/pupkin/mega-project/ec2/cert-11111111111111.pem
    4. Проверить, правильно ли установлены утилиты можно, выполнив команду ec2-describe-images. Результатом команды будет список доступных для запуска образов виртуальных машин (AMI): pupkin@local$ ec2-describe-images -a IMAGE ami-5586623c 1fm2knh6gyecefa34102-phe-bucket/image.manifest.xml 437711880492 available public i386 machine IMAGE ami-63ec090a 1VYRBPTAGB3GRJD3N902-AMI-082407/image.manifest.xml 681122512140 available public i386 machine IMAGE ami-18cf2a71 1VYRBPTAGB3GRJD3N902-AMI-110407-dev/image.manifest.xml 681122512140 available public i386 machine ...

Настройка экземпляра

В качестве ОС выбираем Ubuntu Linux. За основу берем образ Ubuntu 8.04 LTS Hardy от alestic.com. Дальше доставим туда необходимые Java, MySQL и всё остальное.
  1. Запускаем экземпляр с нужным образом.
    1. Ищем последний по дате нужный образ. Выбираем base (лишнего нам не надо) и 64-битную платформу (нашему сайту будет нужно много памяти). pupkin@local$ ec2-describe-images -a |grep ubuntu |grep alestic |grep 64 IMAGE ami-17d7337e alestic-64/ubuntu-8.04-hardy-base-64-20080924.manifest.xml
    2. Генерируем ключик для доступа к экземпляру по SSH pupkin@local$ ec2-add-keypair pstam-keypair и сохраняем его в файле, например, /home/pupkin/mega-project/ec2/id_rsa-pstam-keypair.
      Если используется OpenSSH, для этого файлы нужно выставить правильные права доступа: pupkin@local$ chmod 600 /home/pupkin/mega-project/ec2/id_rsa-pstam-keypair
    3. Запускаем выбранный экземпляр pupkin@local$ ec2-run-instances ami-17d7337e -k pstam-keypair -t m1.large RESERVATION r-4073a129 701161471898 default INSTANCE i-1b903272 ami-17d7337e pending pstam-keypair 0 m1.large 2008-10-02T12:54:37+0000 us-east-1b aki-b51cf9dc ari-b31cf9da i-1b903272 - это идентификатор экземпляра. Он будет нужен позднее.
      pending - означает, что экземпляр в процессе запуска.

      Ждем 15 секунд, и выполняем команду pupkin@local$ ec2-describe-instances RESERVATION r-4073a129 701161471898 default INSTANCE i-1b903272 ami-17d7337e ec2-75-101-241-247.compute-1.amazonaws.com domU-12-31-39-01-59-41.compute-1.internal running pstam-keypair Статус должен поменяться на running. Если не поменялся - нужно подождать еще.
      Когда статус поменяется на running, в таблице будет видно внешний (ec2-75-101-241-247.compute-1.amazonaws.com) и внутренний (domU-12-31-39-01-59-41.compute-1.internal) адреса экземпляра. Внешний адрес используется для доступа к экземляру из интернета, внутренний - с других инстансов (если такие будут).
  2. Открываем доступ к экземпляру по нужным портам: pupkin@local$ ec2-authorize default -p 22 pupkin@local$ ec2-authorize default -p 80 проверяем pupkin@local$ telnet ec2-75-101-241-247.compute-1.amazonaws.com 22
  3. Логинимся на экземпляр и устанавливаем нужные пакеты pupkin@local$ ssh -i /home/pupkin/mega-project/ec2/id_rsa-pstam-keypair root@ec2-75-101-241-247.compute-1.amazonaws.com root@domU-12-31-39-01-59-41# apt-get update root@domU-12-31-39-01-59-41# apt-get install mysql-server root@domU-12-31-39-01-59-41# apt-get install apache2 root@domU-12-31-39-01-59-41# apt-get install sun-java6-jdk root@domU-12-31-39-01-59-41# apt-get install zabbix-agent root@domU-12-31-39-01-59-41# apt-get install subversion root@domU-12-31-39-01-59-41# apt-get install postfix root@domU-12-31-39-01-59-41# apt-get install mailx Дополнительные пакеты можно будет поставить и позднее, при необходимости.

Настройка Elastic IP

При каждом старте экземпляре ему выдается новый IP-адрес. Чтобы при этом избежать проблем с DNS-ом, закажем также Elastic IP. pupkin@local$ ec2-allocate-address ADDRESS 75.101.137.69 pupkin@local$ ec2-associate-address -i i-1b903272 75.101.137.69 ADDRESS 75.101.137.69 i-1b903272 - это идентификатор экземляра. Если вы его забыли, можно посмотреть командой ec2-describe-instances). Теперь этот адрес можно (и нужно) прописать в DNS, например, связав с именем http://mega-project.pupkin.com.

Настройка EBS

  1. Создаем том. pupkin@local$ ec2-create-volume -s 200 -z us-east-1b us-east-1b - зона, или датацентр Amazon. Её нужно выбрать такой же, в какой запущен экземпляр, чтобы не гонять лишний трафик. Посмотреть зону экземпляра можно командой ec2-describe-instances. Ждем пока поднимется, смотрим информацию о томах: pupkin@local$ ec2-describe-volumes VOLUME vol-2a08ed43 200 us-east-1b available 2008-10-02T15:50:14+0000
  2. Подключаем том к инстансу pupkin@local$ ec2-attach-volume -i i-1b903272 vol-2a08ed43 -d /dev/sdd1
  3. Для новосозданного тома создаём и монтируем файловую систему на экземпляре pupkin@local$ ssh -i /home/pupkin/mega-project/ec2/id_rsa-pstam-keypair root@ec2-75-101-241-247.compute-1.amazonaws.com root@domU-12-31-39-01-59-41# mkfs -t ext3 /dev/sdd1 root@domU-12-31-39-01-59-41# mkdir /mnt/db root@domU-12-31-39-01-59-41# mount /dev/sdd1 /mnt/db Монтирование /dev/sdd1 в /mnt/db нужно прописать в /etc/fstab. Тогда при перезапуске экземпляра том примонтируется автоматически.

Настройка ПО

  1. MySQL MySQL нужно перенести из папки по умолчание (/var) на EBS в раздел с данными (мы его смонтировали в /mnt/db).
    1. Останавливаем MySQL. root@domU-12-31-39-01-59-41# /etc/init.d/mysql stop * Stopping MySQL database server mysqld
    2. Копируем уже созданную БД. root@domU-12-31-39-01-59-41# mkdir /mnt/db/lib /mnt/db/log root@domU-12-31-39-01-59-41# mv /var/lib/mysql /mnt/db/lib/ root@domU-12-31-39-01-59-41# mv /var/log/mysql /mnt/db/log/ root@domU-12-31-39-01-59-41# test -f /vol/log/mysql/mysql-bin.index && perl -pi -e 's%/var/log/%/vol/log/%' /vol/log/mysql/mysql-bin.index
    3. Правим конфиг. root@domU-12-31-39-01-59-41# vim /etc/mysql/my.cnf Раздел mysqld должен быть таким [mysqld] innodb_file_per_table datadir = /mnt/db/lib/mysql log_bin = /mnt/db/log/mysql/mysql-bin.log max_binlog_size = 1000M log_slow_queries = /mnt/db/log/mysql/mysql-slow.log long_query_time = 10
    4. Запускаем. root@domU-12-31-39-01-59-41# /etc/init.d/mysql start
  2. Собственно сайт
    1. Создаём пользователя, под которым будет работать сайт (если сайт сделан в виде отдельного процесса). root@domU-12-31-39-01-59-41# /usr/sbin/adduser mega-project
    2. Пишем скрипт сборки, который
      • чекаутит последнюю версию сайта из репозитария Subversion;
      • делает сборку;
      • перезапускает сайт из новособранной версии.
    3. Запускаем скрипт, настраиваем конфиги приложения, запускаем приложение. Теперь можно зайти на http://mega-project.pupkin.com/ и посмотреть на работающий сайт :)

Сохраняем образ тома

Чтобы не потерялась проделанная работа, нужно сохранить образ настроеного экземпляра. Сохраненный образ также пригодится, если экземпляр нужно будет перезапустить, для поднятия кластера и др.
  1. Копируем ключи и сертификат - они будут нужны на экземпляре. pupkin@local$ cd /home/pupkin/mega-project/ec2/id_rsa-pstam-keypair pupkin@local$ scp -i id_rsa-pstam-keypair id_rsa-pstam-keypair root@ec2-75-101-241-247.compute-1.amazonaws.com: pupkin@local$ scp -i id_rsa-pstam-keypair pk-11111111111111.pem root@ec2-75-101-241-247.compute-1.amazonaws.com: pupkin@local$ scp -i id_rsa-pstam-keypair cert-11111111111111.pem root@ec2-75-101-241-247.compute-1.amazonaws.com:
  2. Собираем пакет с файлами root@domU-12-31-39-01-59-41# ec2-bundle-vol -e /tmp -d /mnt -k pk-11111111111111.pem -c cert-11111111111111.pem -u <Account ID без символов -> -p mega-project-image -s 1500 1500 - размер образа. На стоимость хранения не оказывает влияния, но при небольших размерах быстрее архивируется (перед хранением) и быстрее стартует экземпляр.
    Размер образа должен быть не меньше, чем размер примонтированной на / файловой системы.
    Если значение размера будет слишком маленьким, команда ec2-bundle-vol может выдать сообщение об ошибке execution failed: "rsync ... "
  3. Заливаем собранный пакет в Amazon Structured Storage Service (S3). root@domU-12-31-39-01-59-41# ec2-upload-bundle -b pupkin-images -m /mnt/mega-project-image.manifest.xml -a <Access Key Id> -s <Secret Access Key>
  4. С локальной машины регистрируем пакет как образ.
  5. pupkin@local$ ec2-register pupkin-images/mega-project-images.manifest.xml Всё, дело сделано :).

Возникшие проблемы и вопросы

  1. Почему-то MySQL сразу при старте начинает сильно загружать процессор. Полечилось перезапуском MySQL.
UPD. При переносе данных MySQL в другую папку нужно также поправить настройки apparmor (в /etc/apparmor.d/usr.sbin.mysqld) и logrotate (в /etc/logrotate.d/mysql-server)