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.
А в остальном - довольно неплохо. Счастливой кластеризации.
Tweet
>> К сожалению, в комплекте примеров jsp нет страниц, которые демонстрируют использование сессий.
А чем сервлеты не угодили? Среди них есть примеры с сессиями:
http://xxxxxxxxxxxx:8080/examples/servlets/servlet/SessionExample ?
Ну, поленился смотреть примеры сервлетов. Признаюсь. :-) Можно было и так. Если буду пересматривать статью – учту.
Thnx за feedback.
Являясь не профессионалом в области построения кластеров, прочитал с удовольствием, спасибо, но… второй нюанс удручает.
Я думаю что добавление узла кластера – не такое частое событие, чтоб невозможно было уложиться в SLA.
отлично, великолепно, очеень пригодилась статья
Молодцы, наткнулся случайно, а сколько пользЫ!!!!!
Спасибо !!
Ценная статья.
Классная статья. Но почему то не получается репликация сессий.(Возможно не все смог угадать из-за [xml] )
Можно ли поменять redirectPort=”8443″ на какой-то другой порт?
Будет ли оно работать?
Vladimir,
А почему бы и не работать ему ?
rostovsky,
Есть такая проблемка, постепенно испраляем
Читаю документацию по коннекторам – 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”?
Это просто ошибка или я чего-то не знаю/не понимаю?
Vladimir,
Catalina – это рабочее имя проекта, заодно и название HTTP-движка, используемого внутри Tomcat.
Tomcat же – это уже end-user бренд WebApp-севрера
Привет. Может кто знает как разделить фермы кластеров между собой (так, чтоб не видели друг друга). Например 1 группа: q,w,e в один кластер, а 2 группа r,t во второй. И чтоб это группы не пересекались.
@rostovsky
Думаю должно получиться выделением
разных значений для mcastAddr, mcastPort
По поводу разделения ферм кластеров:
В разных группах кластеров нужно использовать разные хосты и разные jvmRoute (в server.xml). Еще в membership необходимо указать разные порты (Спасибо Vadim!!)
После этого при запуске томкатов ноды разных групп кластеров не видят друг друга
Sorry chto translitom :-(((
kak proizvodit update “tomcat cluster’a” na novuyu versiyu web.war ?
uchitivaya chto nekotorie klassi , ispolzyemie v session , pomenyalis !
Ребят подскажите а как настроить репликации сессий томкатов , если они в разных подсетках?и хотелось бы задавать адресс томката из другой сити точечно.Заранее пасиб
@Konstantin,
Насколько я знаю есть инструменты для каскадного развертывания приложения в кластере.
@pako,
Первое что приходит в голову – построить VPN между сетями.
А вообще не совсем понятно зачем при горизонтальном масштабировании размещать Tomcat-сервера в разных подсетях?
А вот если раскинуть Tomcat по разным серверам, то как будет выглядить workers.properties?
Есть 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
Параметры “workers.tomcat_home” и “workers.java_home” из файла workers.properties обязательны только для воркеров типа jni. Так что в примере их можно не указывать.
thanks=)