Tomcat clustering

Рубрика: Development, Java | 25 June 2007, 08:21 | juriy

Эта заметка посвящена тому, как развернуть кластер из Tomcat серверов и настроить простую балансировку нагрузки для кластера.

Начнем сначала. Зачем нужен кластер? Обычным приложениям, которые обслуживают относительно небольшой поток пользователей и к которым не предъявляются серьезные требования со стороны бизнеса, кластер, возможно и не пригодится.

Совсем по-другому обстоят дела с теми приложениями, которые являются ключевыми для функционирования бизнеса, к примеру, приложения которые обрабатывают биллинговые операции. Если “ляжет” сервис такого рода, то некоторый бизнес-процесс, тоже перестанет функционировать.

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

Что касается масштабируемости: увеличить производительность системы можно за счет расширения кластера. То есть, если два сервера не справляются с задачей, то довольно просто поставить третий, подключить его к кластеру и увеличить производительность системы на 50%. Надо заметить, что расширять кластер таким образом бесконечно нельзя, есть некоторые сдерживающие факторы: к примеру, объем трафика, который необходим для передачи данных сессий между нодами кластера. Об этом чуть позже.

В этой заметке приведен рецепт развертывания кластера из нескольких Tomcat серверов на одной рабочей станции. Необходимые компоненты: дистрибутив Tomcat 5.0.28 (можно 5.5.xx – процесс ничем не отличается), Apache HTTP server 2.0.59 + mod_jk.

Шаг 1. Разворачиваем первый Tomcat.
Выберите директорию для установки Tomcat серверов кластера. Разархивируйте первый дистрибутив. Запустите сервер и убедитесь, что все работает. Теперь у нас есть установленный Tomcat, на котором уже запущено несколько приложений. Работу кластера мы будем тестировать на примере приложения jsp-examples из дистрибутива.

Шаг второй. Настроим Apache HTTP + mod_jk.
Первый вопрос, который возникает на этом шаге: зачем _одному_ Tomcat’у (ведь кластера еще нет) нужен еще и Apache, если Tomcat и сам неплохо справляется с обработкой HTTP запросов? Ответ, который предоставляют разработчики, довольно неожиданный: Tomcat действительно неплохо работает с HTTP запросами, но в реальном мире, где на сервер сыплются тонны некорректных запросов и оборванных соединений – Apache справляется куда лучше. Второй нюанс: Tomcat все же написан на Java. Цена кроссплатформенности Java – это невозможность оптимизировать код под конкретную платформу. И снова-таки Apache лишен этого недостатка.
Таким образом в нашей архитектуре Apache HTTP server будет выступать в качестве “фильта” между пользователями и Tomcat серверами. На следующих шагах Apache будет выполнять еще и функцию распределителя нагрузки.

1. Установите Apache.
2. Запустите Apache и убедитесь что он работает: зайдите на http://localhost – там должна быть стартовая страничка Apache.
3. Скачайте mod_jk необходимой версии, переименуйте файл в mod_jk.so и поместите его в папку modules.
4. В файл httpd.conf добавляем такие строки:

LoadModule jk_module modules/mod_jk.so

# Path to workers.properties
JkWorkersFile conf/workers.properties

# Path to jk logs
JkLogFile logs/mod_jk.log

# Jk log level [debug/error/info]
JkLogLevel info

# Jk log format
JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "

# JkOptions for forwarding
JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories

# JkRequestLogFormat set the request format
JkRequestLogFormat "%w %V %T"

JkMount /jsp-examples alpha
JkMount /jsp-examples/* alpha

5. В папке conf создаем файл workers.properties. Содержимое этого файла будет таким:


workers.tomcat_home=C:/apps/cluster/alpha
workers.java_home=C:/apps/jdk1.5.0_11

worker.list=alpha

worker.alpha.port=8009
worker.alpha.host=localhost
worker.alpha.type=ajp13

Естественно, вместо указанных путей укажите собственные.
Теперь поправим конфигурацию Tomcat:

6. Убедимся, что AJP коннектор настроен именно так:

<connector port="8009"
	enableLookups="false" redirectPort="8443" debug="0"
	protocol="AJP/1.3" />

7. Перезагружаем Tomcat и Apache. Убедимся, что все работает так как надо:
Если в браузере набрать http://localhost/jsp-examples/ - должна отобразиться страница с примерами jsp из дистрибутива Tomcat. Если страничка есть - значит все работает.
Поздравляю, теперь обработкой HTTP запросов занимается Apache.

Шаг третий. Добавим в кластер еще один сервер и настроим балансировку нагрузки.

1. Разархивируем еще один дистрибутив Tomcat.
2. В server.xml поправим конфигурацию так, чтобы не было конфликтов портов:

<server port="8006" shutdown="SHUTDOWN" debug="0">
...

    <connector port="8081"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" redirectPort="8443" acceptCount="100"
               debug="0" connectionTimeout="20000"
               disableUploadTimeout="true" />
...

    <connector port="8010"
               enableLookups="false" redirectPort="8443" debug="0"
               protocol="AJP/1.3" />
...

3. Поправим workers.properties, чтобы нагрузка баллансировалась между двумя нодами:


workers.tomcat_home=C:/apps/cluster/alpha

workers.java_home=C:/apps/jdk1.5.0_11

worker.list=balancer

worker.alpha.port=8009
worker.alpha.host=localhost
worker.alpha.type=ajp13
worker.alpha.lbfactor=1

worker.bravo.port=8010
worker.bravo.host=localhost
worker.bravo.type=ajp13
worker.bravo.lbfactor=1

worker.balancer.type=lb
worker.balancer.balance_workers=alpha,bravo

4. Поправим httpd.conf:

JkMount /jsp-examples balancer
JkMount /jsp-examples/* balancer

5. Чтобы понимать, на какую ноду мы попали, поправим файлы webapps\jsp-examples\index.html в обеих нодах. В первой ноде добавим строку "Alpha", а во второй "Bravo". Теперь будет видно, какой из серверов в действительно обрабатывает запрос.

5. Запустим только что установленный Tomcat (теперь работает две ноды).
6. Перезапустим Apache.
7. Набираем в браузере http://localhost/jsp-examples/
8. Смотрим, какая нода обрабатывает наш запрос и останавливаем соответствующий Tomcat.
9. Обновляем страницу в браузере - теперь запрос обрабатывает другая нода (и никаких 404 :-))

Только что мы проимитировали ситуацию, когда одна из нод кластера перестала функционировать. Как видно - переключение на работающую ноду прошло абсолютно прозрачно для пользователя (если бы не надписи "Alpha" и "Bravo" пользователь даже не узнал-бы о переключении).

Шаг четвертый. Настаиваем репликацию сессий.

При существующей архитектуре, данные сессии пользователя при крахе ноды будут утеряны. Это далеко не всегда допустимо. На этом шаге мы настроим репликацию сессий: Tomcat ноды будут обмениваться данными о сессиях. Таким образом, если одна из нод упадет - копии сессии останутся на остальных нодах и пользователь сможет продолжить нормально работать.
На данном этапе элементы кластера не знают о существовании друг-друга. Фактически, все что надо сделать для репликации сессий это "познакомить" их.

К сожалению, в комплекте примеров jsp нет страниц, которые демонстрируют использование сессий. Поэтому прийдется немного повозиться, чтобы получить возможность проверить репликацию.

Создадим страничку session.jsp с таким содержанием:

<%@ page contentType="text/html;charset=windows-1251" %>
<html>
<head>
	<meta http-equiv="Content-Type"	content="text/html; charset=windows-1251">
	<title>Test sessions</title>
</head>
<body>
	<%="Node: " + getServletContext().getRealPath(".") + "<br />" %>
	<%
		if (session.getAttribute("sessionObj") == null) {
			out.println("No session. Setting sessionObj to \"alpha\"");
			session.setAttribute("sessionObj", "alpha");
		} else {
			out.print("sessionObj " + session.getAttribute("sessionObj"));
		}
	%>

</body>
</html>

Для ноды bravo создадим точно такую же страницу, только вместо alpha будем устанавливать строку "bravo".

1. Убедимся, что сейчас репликация сессий не работает:
1.1. Зайдем на http://localhost/jsp-examples/session.jsp, заметим, какая нода обработала запрос.
1.2. Остановим соответствующую ноду.
1.3. Обновим в браузере страницу: как видно, вторая нода понятия не имеет о том, какие объекты добавляла в сессию первая нода.
1.4. Остановим обе ноды.

Теперь, собственно, репликация.

1. В server.xml для ноды alpha прописываем

<engine name="Catalina" defaultHost="localhost" debug="0" jvmRoute="alpha">

2. В server.xml для ноды bravo прописываем

<engine name="Catalina" defaultHost="localhost" debug="0" jvmRoute="bravo">

2. Раскомментируем блок Cluster нод alpha и bravo

3. В блоке Cluster ноды alpha устанавливаем


            <membership
                className="org.apache.catalina.cluster.mcast.McastService"
                mcastAddr="228.0.0.4"
				mcastBindAddress="127.0.0.1"
                mcastPort="45564"
                mcastFrequency="500"
                mcastDropTime="3000"/>
			<receiver
                className="org.apache.catalina.cluster.tcp.ReplicationListener"
                tcpListenAddress="auto"
                tcpListenPort="4001"
                tcpSelectorTimeout="100"
                tcpThreadCount="6"/>

4. Аналогично для bravo

<membership
                className="org.apache.catalina.cluster.mcast.McastService"
                mcastAddr="228.0.0.4"
				mcastBindAddress="127.0.0.1"
                mcastPort="45564"
                mcastFrequency="500"
                mcastDropTime="3000"/>

			<receiver
                className="org.apache.catalina.cluster.tcp.ReplicationListener"
                tcpListenAddress="auto"
                tcpListenPort="4002"
                tcpSelectorTimeout="100"
                tcpThreadCount="6"/>

5. В web.xml приложения jsp-examples обеих нод добавляем параметр <distributable/>:


 <web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

	<distributable/>

    <description>
      JSP 2.0 Examples.
    </description>
	...
	...

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

<strong>Заключение.</strong>

В этой заметке описан способ настройки кластера Tomcat серверов. Мы использовали HTTP сервер Apache для обработки HTTP запросов и для баллансировки нагрузки между серверами.

Для того чтобы добавить в кластер новую ноду необходимо сделать следующее:

1. Разархивировать дистрибутив tomcat.
2. В server.xml указать


<server port="xxxx" shutdown="SHUTDOWN" debug="0">

тут xxxx - значение порта, которое не будет конфликтовать с другими портами кластера

3. Поправляем http коннектор


    <connector port="xxxx"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" redirectPort="8443" acceptCount="100"
               debug="0" connectionTimeout="20000"
               disableUploadTimeout="true" />

тут xxxx - значение порта, которое не будет конфликтовать с другими портами кластера

4. Указать параметры AJP коннектора:


    <connector port="yyyy"
               enableLookups="false" redirectPort="8443" debug="0"
               protocol="AJP/1.3" />

yyyy - аналогично

5. Указать значение jvmRoute для Catalina


<engine name="Catalina" defaultHost="localhost" debug="0" jvmRoute="myNextNode">

значение jvmRoute должно быть уникальным для каждой ноды кластера.

6. Раскомментировать блок Cluster и в нем настроить


            <membership
                className="org.apache.catalina.cluster.mcast.McastService"
                mcastAddr="228.0.0.4"
				mcastBindAddress="127.0.0.1"
                mcastPort="45564"
                mcastFrequency="500"
                mcastDropTime="3000"/>

			<receiver
                className="org.apache.catalina.cluster.tcp.ReplicationListener"
                tcpListenAddress="auto"
                tcpListenPort="zzzz"
                tcpSelectorTimeout="100"
                tcpThreadCount="6"/>

zzzz - аналогично предыдущим.

В workers.properties добавить описание worker'a для новой ноды

worker.myNextNode.port=yyyy
worker.myNextNode.host=localhost
worker.myNextNode.type=ajp13
worker.myNextNode.lbfactor=1
...
worker.balancer.balance_workers=alpha,bravo,myNextNode

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

Лично мне не удалость настроить для кластера Tomcat серверов Deployment Farm - замечательная возможность кластера, которая позволяет поставлять обновленное приложение одновременно на весь кластер.

Второй нюанс - кластером нельзя управлять динамически: поменяв workers.properties необходимо перезагрузить Apache.

А в остальном - довольно неплохо. Счастливой кластеризации.

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

24 Responses to “Tomcat clustering”

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

  1. vadim

    >> К сожалению, в комплекте примеров jsp нет страниц, которые демонстрируют использование сессий.
    А чем сервлеты не угодили? Среди них есть примеры с сессиями:
    http://xxxxxxxxxxxx:8080/examples/servlets/servlet/SessionExample ?

  2. juriy

    Ну, поленился смотреть примеры сервлетов. Признаюсь. :-) Можно было и так. Если буду пересматривать статью – учту.

    Thnx за feedback.

  3. dotpack

    Являясь не профессионалом в области построения кластеров, прочитал с удовольствием, спасибо, но… второй нюанс удручает.

  4. vadim

    Я думаю что добавление узла кластера – не такое частое событие, чтоб невозможно было уложиться в SLA.

  5. nestor

    отлично, великолепно, очеень пригодилась статья

  6. skhil

    Молодцы, наткнулся случайно, а сколько пользЫ!!!!!

  7. Михаил

    Спасибо !!
    Ценная статья.

  8. rostovsky

    Классная статья. Но почему то не получается репликация сессий.(Возможно не все смог угадать из-за [xml] )

  9. Vladimir

    Можно ли поменять redirectPort=”8443″ на какой-то другой порт?
    Будет ли оно работать?

  10. Vadim Voituk

    Vladimir,
    А почему бы и не работать ему ?

    rostovsky,
    Есть такая проблемка, постепенно испраляем

  11. Vladimir

    Читаю документацию по коннекторам – http://tomcat.apache.org/tomcat-4.1-doc/config/coyote.html
    ***
    The Coyote HTTP/1.1 Connector element represents a Connector component that supports the HTTP/1.1 protocol. It enables Catalina to function as a stand-alone web server, in addition to its ability to execute servlets and JSP pages.
    ***

    Не подскажете почему там есть строчка “It enables Catalina” а не “It enables Tomcat”?
    Это просто ошибка или я чего-то не знаю/не понимаю?

  12. Vadim Voituk

    Vladimir,
    Catalina – это рабочее имя проекта, заодно и название HTTP-движка, используемого внутри Tomcat.
    Tomcat же – это уже end-user бренд WebApp-севрера

  13. rostovsky

    Привет. Может кто знает как разделить фермы кластеров между собой (так, чтоб не видели друг друга). Например 1 группа: q,w,e в один кластер, а 2 группа r,t во второй. И чтоб это группы не пересекались.

  14. Vadim Voituk

    @rostovsky
    Думаю должно получиться выделением
    разных значений для mcastAddr, mcastPort

  15. rostovsky

    По поводу разделения ферм кластеров:
    В разных группах кластеров нужно использовать разные хосты и разные jvmRoute (в server.xml). Еще в membership необходимо указать разные порты (Спасибо Vadim!!)
    После этого при запуске томкатов ноды разных групп кластеров не видят друг друга

  16. Konstantin

    Sorry chto translitom :-(((

    kak proizvodit update “tomcat cluster’a” na novuyu versiyu web.war ?

    uchitivaya chto nekotorie klassi , ispolzyemie v session , pomenyalis !

  17. pako

    Ребят подскажите а как настроить репликации сессий томкатов , если они в разных подсетках?и хотелось бы задавать адресс томката из другой сити точечно.Заранее пасиб

  18. Vadim Voituk

    @Konstantin,
    Насколько я знаю есть инструменты для каскадного развертывания приложения в кластере.

    @pako,
    Первое что приходит в голову – построить VPN между сетями.
    А вообще не совсем понятно зачем при горизонтальном масштабировании размещать Tomcat-сервера в разных подсетях?

  19. Ale

    А вот если раскинуть Tomcat по разным серверам, то как будет выглядить workers.properties?

  20. Xploit

    Есть 2 инстанса томката (ядро, клиент). Ядро стартует верно. А вот когда клиент стартует – томкат грузит webapp но не догружает. Лог клиента:
    Aug 24, 2009 4:38:02 PM org.apache.coyote.http11.Http11Protocol init
    INFO: Initializing Coyote HTTP/1.1 on http-8080
    Aug 24, 2009 4:38:03 PM org.apache.catalina.startup.Catalina load
    INFO: Initialization processed in 599 ms
    Aug 24, 2009 4:38:03 PM org.apache.catalina.core.StandardService start
    INFO: Starting service Catalina
    Aug 24, 2009 4:38:03 PM org.apache.catalina.core.StandardEngine start
    INFO: Starting Servlet Engine: Apache Tomcat/6.0.18
    Aug 24, 2009 4:38:03 PM org.apache.catalina.ha.tcp.SimpleTcpCluster start
    INFO: Cluster is about to start
    Aug 24, 2009 4:38:03 PM org.apache.catalina.tribes.transport.ReceiverBase bind
    INFO: Receiver Server Socket bound to:/192.168.69.152:4000
    Aug 24, 2009 4:38:03 PM org.apache.catalina.tribes.membership.McastServiceImpl setupSocket
    INFO: Setting cluster mcast soTimeout to 500
    Aug 24, 2009 4:38:03 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
    INFO: Sleeping for 1000 milliseconds to establish cluster membership, start level:4
    Aug 24, 2009 4:38:04 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
    INFO: Done sleeping, membership established, start level:4
    Aug 24, 2009 4:38:04 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
    INFO: Sleeping for 1000 milliseconds to establish cluster membership, start level:8
    Aug 24, 2009 4:38:05 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
    INFO: Done sleeping, membership established, start level:8

  21. nazica

    Параметры “workers.tomcat_home” и “workers.java_home” из файла workers.properties обязательны только для воркеров типа jni. Так что в примере их можно не указывать.

  22. msangel

    thanks=)

Leave a Reply