Вся мощь серверов приложений проявляется в том, что они обеспечивают управляемую среду исполнения программ. С одной стороны это обозначает, что сервер приложений предоставляет некий набор API, например JSR-316 - Java EE 6, а с другой - сервер приложений предоставляет средства, обеспечивающие управление жизненным циклом программы, транзакциями, доступом к ресурсам и другим системам, а так же потоками.
Для управления потоками со стороны сервера приложений используется такое понятие, как пул потоков. При работе приложений, управляемых сервером, потоки не создаются каждый раз по требованию программы, а берутся из пула. Тем самым исключается ситуация, когда на сервере исполняется потоков больше, чем можно физически обработать, и он вынужден не столько заниматься полезной работой, сколько переключаться между потоками. Реализуется данное поведение за счет того, что если все потоки из пула исчерпаны, то задача, требующая исполнения, блокируется и ожидает освобождения потока.
Администратор сервера приложений может настроить следующие параметры пула:
При этом сервер приложений использует параметры, заданные администратором, совместно со своими собственными оптимизациями, анализируя текущую нагрузку, количество свободных ядер процессора и объем доступной оперативной памяти.
В сервере приложений Oracle WebLogic для управления доступом к пулу потоков используется механизм WorkManager'ов. При этом можно определять как глобальные для всего домена WorkManager'ы, так и локальные - для приложения, сервлета, EJB-компонента.
Глобальный WorkManager создается с помощью консоли администратора или WLST. В консоли администратора используется пункт меню Environment -> Work Managers.
При нажатии на данную ссылку открывается страница Summary of Work Managers, на которой расположена кнопка создания следующих объектов:
Важно заметить, что ограничения имеют приоритет перед классами запросов.
Приложение использует глобально-определенный WorkManager как шаблон. Каждое приложение создает свой собственный экземпляр, который обрабатывает задачи, связанные с данным приложением и отделяет эти работы от других приложений.
Обработка потоков каждого приложения осуществляется отдельно, что позволяет каждому приложению быть остановленным без нарушения работы другого приложения. Хотя каждое приложение использует свой экземпляр WorkManager'а, основные компоненты (ограничения и классы запросов) являются разделяемыми.
Локальный WorkManager создается путем описания в соответствующем файле weblogic-...xml:
Например, weblogic.xml, в котором создается WorkManager с ограничениями на минимальное и максимальное число потоков, может выглядеть так:
Для использования WorkManager'а в коде приложения используется API, предоставляемое библиотекой CommonJ, являющейся реализацией JSR-237 Timer & WorkManager. В данной статье рассмотрим только пакет commonj.work.
Work Manager API поддерживает следующие интерфейсы:
Пример использования данного API в коде:
Чтобы осуществить инъекцию WorkManager'а в JNDI-дерево и получить к нему доступ из сервлета, необходимо описать данный WorkManager в resource-ref'еренсах дескриптора развертывания web.xml:
Помимо получения доступка к WorkManager'у через lookup, его можно так же инъектировать в сервлет как ресурс:
На рисунке виден пример вывода в консоль, осуществленного сервлетом. Обе задачи, описанные в коде сервлета, выполняются с помощью WorkManager'а wm/somews и в одном потоке, так как для данного WorkManager'а ограничения MinThread и MaxThread установлены равными единице. Сам же сервлет работает под управлением WorkManager'а по-умолчанию - default.
Важно! Work Manager API не предоставляет никаких механизмов восстановления после сбоев и долгосрочного хранения задач. Если среда управляемого сервера выходит из строя или выключается, любые текущие задачи теряются.
Под декларативным назначением WorkManager'а будем понимать указание использовать заданный WorkManager для диспетчеризации запросов. Т.е. для обработки запросов к ресурсу будут использоваться потоки, созданные с помощью указанного WorkManager'а. Под ресурсом понимается сервлет, EJB-компонент, сервис OSB и т.д.
Для декларативного назначения WorkManager'а сервлету необходимо установить значение параметра wl-dispatch-policy сервлета равным JNDI-наименованию WorkManager'а:
В результате для обработки каждого запроса сервлетом будет использоваться один поток:
Для декларативного назначения WorkManager'а EJB-компоненту необходимо указать JNDI-имя данного WorkManager'а в качестве значения элемента wls:dispatch-policy являющегося подэлементом элемента wls:weblogic-enterprise-bean файла weblogic-ejb-jar.xml:
Сам WorkManager можно определить в этом же файле.
Код сессионного бина следующий:
В результате исполнения EJB в консоль выведется название WorkManager'а:
Управляемый сообщениями компонент считывает сообщения из JMS- или JCA-источников данных. При этом по-умолчанию используется WorkManager default и считывание осуществляется в 16 потоков. Такое число потоков может быть как недостаточным, так и избыточным.
Декларативное назначение WorkManager'а управляемому сообщениями компоненту производится аналогично его назначению сессионному компоненту: необходимо указать JNDI-имя данного WorkManager'а в качестве значения элемента wls:dispatch-policy являющегося подэлементом элемента wls:weblogic-enterprise-bean файла weblogic-ejb-jar.xml:
Код управляемого сообщениями компонента может быть следующим:
Если ограничить количество потоков для WorkManager'a wm/DemoWorkManager двумя, то в консоль при обработке сообщений будут выводиться следующие строки:
Видно, что сообщения обрабатываются попарно.
В консоли администратора сервера приложений WebLogic при этом будет отображаться количество подписчиков на очередь сообщений. Видно, что данное число равно числу потоков в WorkManager'е - двум:
Важно! Чтобы считывание сообщений действительно происходило в несколько потоков, нужно сделать следующее:
Управляемым сообщениями POJO-компонентом называют Java-класс, реализующий интерфейс javax.jms.MessageListener, но не являющийся EJB-компонентом. Подключение такого компонента к очереди сообщений может осуществляться, например, посредством Spring Framework.
При этом Spring Framework содержит средства интеграции с СommonJ, в частности - класс org.springframework.scheduling.commonj.WorkManagerTaskExecutor. Объект данного класса можно инъектировать в другие компоненты Spring Framework, содержащие соответствующее свойство, например в org.springframework.jms.listener.SimpleMessageListenerContainer:
Сам управляемый сообщениями POJO:
Для того, чтобы Spring Framework получил доступ к WorkManager'у, на последний должна быть объявлена ссылка в web.xml:
После развертывания проекта на сервере приложений управляемый сообщениями POJO начинает слушать очередь. При этом, независимо от настроек WorkManager'а в консоли администратора отображается, что на очередь есть 16 подписчиков - значение по-умолчанию:
В то же время, если задать WorkManager'у ограничение в один поток, то сообщения будут считываться строго последовательно:
Если же увеличить количество потоков до трех, то и сообщения будут считываться из очереди тройками:
Дополнительных настроек очереди сообщений при использовании управляемого сообщениями POJO делать не нужно.
WorkManager для прокси-сервиса OSB указывается в качестве значения параметра Dispatch Policy на вкладке ТИП ТРАНСПОРТА Transport. При этом в IDE Eclipse подгружаются глобальные WorkManager'ы домена, на котором будет разворачиваться проект для OSB
Назначение WorkManager'а для бизнес-сервиса OSB происходит аналогично.
Мы рассмотрели один из важнейших компонентов среды сервера приложений - WorkManager. Данный компонент предназначен для тонкой настройки производительности системы в зависимости от возможностей имеющегося оборудования и приоритета исполняемых на сервере задач.
Сервер приложений WebLogic максимально полно использует возможности WorkManager'ов для управления потоками. Практически каждый компонент стека Java EE (сервлет, сессионный и управляемый событиями компоненты) и даже POJO и сервисы OSB могут быть настроены таким образом, чтобы использовать для своей работы потоки с помощью указанного WorkManager'а, сконфигурированного определенным образом. Допускается даже изменение параметров доступа к потокам в зависимости от того, какой именно пользователь выполняет запрос.
Очень грубая ошибка пытаться создавать потоки в управляемой среде с помощью явного обращения к классу Thread. Такие потоки не будут управляться сервером приложений и могут привести к значительной просадке производительности. Если в приложении нужно создать отдельный поток, то необходимо использовать CommonJ API.
Понравилось сообщение - подпишитесь на блог и Twitter
Пул потоков
Для управления потоками со стороны сервера приложений используется такое понятие, как пул потоков. При работе приложений, управляемых сервером, потоки не создаются каждый раз по требованию программы, а берутся из пула. Тем самым исключается ситуация, когда на сервере исполняется потоков больше, чем можно физически обработать, и он вынужден не столько заниматься полезной работой, сколько переключаться между потоками. Реализуется данное поведение за счет того, что если все потоки из пула исчерпаны, то задача, требующая исполнения, блокируется и ожидает освобождения потока.
Администратор сервера приложений может настроить следующие параметры пула:
- Приоритет потоков - параметр позволяет ранжировать потоки, создаваемые разными пулами, по приоритету. Таким образом можно настроить поведение при котором запрос пользователя к критичному для бизнеса приложению приостанавливает другие потоки в системе.
- Количество потоков в пуле - параметр позволяет задать минимальное и максимальное количество потоков, находящихся в пуле. При этом современные сервера приложений, например Oracle WebLogic, позволяют не ограничивать максимальное значение потоков в пуле константой, а указать источник данных (пул соединений с базой данных), на основе которого данное значение будет вычисляться. Таким образом обеспечивается полное однозначное соответствие - одно соединение с базой данных на один поток.
При этом сервер приложений использует параметры, заданные администратором, совместно со своими собственными оптимизациями, анализируя текущую нагрузку, количество свободных ядер процессора и объем доступной оперативной памяти.
Настройка пула потоков в WebLogic
В сервере приложений Oracle WebLogic для управления доступом к пулу потоков используется механизм WorkManager'ов. При этом можно определять как глобальные для всего домена WorkManager'ы, так и локальные - для приложения, сервлета, EJB-компонента.
Глобальный WorkManager создается с помощью консоли администратора или WLST. В консоли администратора используется пункт меню Environment -> Work Managers.
При нажатии на данную ссылку открывается страница Summary of Work Managers, на которой расположена кнопка создания следующих объектов:
- Work Manager. Все ниже перечисленные объекты используются в качестве значений соответствующих свойств WorkManager'а.
- Response Time Request Class - класс запроса на основе ожидаемого времени ответа. Класс запросов определяет принцип планирования, который используется WebLogic'ом для распределения потоков. Класс запроса гарантирует, что задача с высшим приоритетом будет исполнена раньше задачи с низшим, даже если она появилась позже. Класс запроса на основе ожидаемого времени ответа позволяет задать целевое время ответа в миллисекундах. При этом, естественно, не гарантируется, что каждый запрос будет обрабатываться указанное количество времени, но обеспечивается что среднее время обработки запросов будет пропорционально их допустимому времени ожидания.
- Fair Share Request Class - класс запроса на основе отношения приоритетов потоков. Значение приоритета по-умолчанию равно 50. Если есть модуль А, использующий пул потоков с настроенным классом запроса на основе отношения приоритетов потоков с приоритетом 80, и модуль В, использующий пул с приоритетом 20, то WebLogic будет отдавать 80% времени использования потока модулю А и 20% - модулю В. При этом важно понимать, что значение приоритета задается не в процентах, а в относительных величинах. Вместо 80 и 20 можно использовать 400 и 100 - отношение будет тем же самым.
- Context Request Class - назначает классы запроса на основе клиентской информации, такой как текущий пользователь или группа.
- Maximum Threads Constraint - ограничение задает максимальное количество потоков из пула, доступное WorkManager'у. Можно определить max-threads-constraint в терминах доступности ресурса от которого зависит обработка запросов, такого как пул соединений.
- Minimum Threads Constraint - гарантирует количество потоков, которые сервер размещает на запросы для исключение ситуации взаимоблокировки. По-умолчанию - ноль.
- Capacity Constraint - отклоняет запросы, если превышен лимит потоков на их обработку. По-умолчанию значение лимита равно -1 - не ограничено.
Важно заметить, что ограничения имеют приоритет перед классами запросов.
Приложение использует глобально-определенный WorkManager как шаблон. Каждое приложение создает свой собственный экземпляр, который обрабатывает задачи, связанные с данным приложением и отделяет эти работы от других приложений.
Обработка потоков каждого приложения осуществляется отдельно, что позволяет каждому приложению быть остановленным без нарушения работы другого приложения. Хотя каждое приложение использует свой экземпляр WorkManager'а, основные компоненты (ограничения и классы запросов) являются разделяемыми.
Локальный WorkManager создается путем описания в соответствующем файле weblogic-...xml:
- weblogic-application.xml - для приложения;
- weblogic-ejb-jar.xml - для EJB-компонента;
- weblogic.xml - для сервлета.
Например, weblogic.xml, в котором создается WorkManager с ограничениями на минимальное и максимальное число потоков, может выглядеть так:
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd
http://xmlns.oracle.com/weblogic/weblogic-web-app
http://xmlns.oracle.com/weblogic/weblogic-web-app/1.3/weblogic-web-app.xsd">
<wls:weblogic-version>10.3.6</wls:weblogic-version>
<wls:context-root>WorkManagerDemo</wls:context-root>
<wls:work-manager>
<wls:name>wm/somewm</wls:name>
<wls:min-threads-constraint>
<wls:name>MinThreadsCountFive</wls:name>
<wls:count>1</wls:count>
</wls:min-threads-constraint>
<wls:max-threads-constraint>
<wls:name>MaxThreadsCountFive</wls:name>
<wls:count>1</wls:count>
</wls:max-threads-constraint>
</wls:work-manager>
</wls:weblogic-web-app>
<wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd
http://xmlns.oracle.com/weblogic/weblogic-web-app
http://xmlns.oracle.com/weblogic/weblogic-web-app/1.3/weblogic-web-app.xsd">
<wls:weblogic-version>10.3.6</wls:weblogic-version>
<wls:context-root>WorkManagerDemo</wls:context-root>
<wls:work-manager>
<wls:name>wm/somewm</wls:name>
<wls:min-threads-constraint>
<wls:name>MinThreadsCountFive</wls:name>
<wls:count>1</wls:count>
</wls:min-threads-constraint>
<wls:max-threads-constraint>
<wls:name>MaxThreadsCountFive</wls:name>
<wls:count>1</wls:count>
</wls:max-threads-constraint>
</wls:work-manager>
</wls:weblogic-web-app>
Использование WorkManager'а в коде приложения
Для использования WorkManager'а в коде приложения используется API, предоставляемое библиотекой CommonJ, являющейся реализацией JSR-237 Timer & WorkManager. В данной статье рассмотрим только пакет commonj.work.
Work Manager API поддерживает следующие интерфейсы:
- WorkManager - данный интерфейс предоставляет множество методов, которые используются запуска работ на исполнение. На уровне приложения, каждый экземпляр WorkManager'а возвращает WorkItem.
- Work - интерфейс позволяет исполнять код асинхронно. Такой код помещается внутрь класса, реализующего данный интерфейс. Другими словами, это - работа, которая исполняется с помощью WorkManager API.
- WorkItem - после того, как экземпляр Work был отправлен в WorkManager, тот возвращает WorkItem, который используется для получения статуса завершения экземпляра Work.
- WorkListener - является интерфейсом обратного вызова, который обеспечивает коммуникацию между WorkManager'ом и запланированной задачей, определенной посредством интерфейса Work. Можно использовать WorkManagerListener для определения текущего статуса элемента Work. Важно! WorkListener всегда исполняется в той же JVM, в которой исполнялся оригинальный поток, использовавшийся для запуска Work через WorkManager.
- WorkEvent - отправляется в WorkListener как образ Work, обработанной WorkManager'ом.
- RemoteWorkItem - интерфейс является расширением интерфейса WorkItem, что позволяет работе исполняться удаленно. Данный интерфейс позволяет сериализованным работам исполняться на любом узле кластера.
Пример использования данного API в коде:
package ru.demo.workmanager;
import java.io.IOException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import commonj.work.Work;
import commonj.work.WorkException;
import commonj.work.WorkManager;
public class WorkManagerDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
System.out.println("[servlet] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] executing in thread: " + Thread.currentThread().getName());
InitialContext context = new InitialContext();
System.out.println("[servlet] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] executing in thread: " + Thread.currentThread().getName());
WorkManager wm = (WorkManager) context.lookup("java:comp/env/wm/somewms");
System.out.println("Got JavaEE work manager!");
wm.schedule(new Work() {
@Override
public void run() {
System.out.println("[servlet] self-tuning workmanager: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] self-tuning thread: " + Thread.currentThread().getName());
}
@Override
public void release() {
}
@Override
public boolean isDaemon() {
return false;
}
});
wm.schedule(new Work() {
@Override
public void run() {
System.out.println("[servlet] self-tuning workmanager: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] self-tuning thread: " + Thread.currentThread().getName());
}
@Override
public void release() {
}
@Override
public boolean isDaemon() {
return false;
}
});
}
catch (NamingException e) {
e.printStackTrace();
}
catch (WorkException e) {
e.printStackTrace();
}
}
}
import java.io.IOException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import commonj.work.Work;
import commonj.work.WorkException;
import commonj.work.WorkManager;
public class WorkManagerDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
System.out.println("[servlet] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] executing in thread: " + Thread.currentThread().getName());
InitialContext context = new InitialContext();
System.out.println("[servlet] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] executing in thread: " + Thread.currentThread().getName());
WorkManager wm = (WorkManager) context.lookup("java:comp/env/wm/somewms");
System.out.println("Got JavaEE work manager!");
wm.schedule(new Work() {
@Override
public void run() {
System.out.println("[servlet] self-tuning workmanager: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] self-tuning thread: " + Thread.currentThread().getName());
}
@Override
public void release() {
}
@Override
public boolean isDaemon() {
return false;
}
});
wm.schedule(new Work() {
@Override
public void run() {
System.out.println("[servlet] self-tuning workmanager: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] self-tuning thread: " + Thread.currentThread().getName());
}
@Override
public void release() {
}
@Override
public boolean isDaemon() {
return false;
}
});
}
catch (NamingException e) {
e.printStackTrace();
}
catch (WorkException e) {
e.printStackTrace();
}
}
}
Чтобы осуществить инъекцию WorkManager'а в JNDI-дерево и получить к нему доступ из сервлета, необходимо описать данный WorkManager в resource-ref'еренсах дескриптора развертывания web.xml:
<resource-ref>
<res-ref-name>wm/somewms</res-ref-name>
<res-type>commonj.work.WorkManager</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
<res-ref-name>wm/somewms</res-ref-name>
<res-type>commonj.work.WorkManager</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
Помимо получения доступка к WorkManager'у через lookup, его можно так же инъектировать в сервлет как ресурс:
package ru.demo.workmanager;
import java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import commonj.work.Work;
import commonj.work.WorkException;
import commonj.work.WorkManager;
public class WorkManagerDemoServlet extends HttpServlet {
@Resource(name = "wm/somewms")
private WorkManager wm;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
System.out.println("[servlet] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] executing in thread: " + Thread.currentThread().getName());
wm.schedule(new Work() {
@Override
public void run() {
System.out.println("[servlet] self-tuning workmanager: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] self-tuning thread: " + Thread.currentThread().getName());
}
@Override
public void release() {
}
@Override
public boolean isDaemon() {
return false;
}
});
wm.schedule(new Work() {
@Override
public void run() {
System.out.println("[servlet] self-tuning workmanager: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] self-tuning thread: " + Thread.currentThread().getName());
}
@Override
public void release() {
}
@Override
public boolean isDaemon() {
return false;
}
});
}
catch (WorkException e) {
e.printStackTrace();
}
}
}
import java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import commonj.work.Work;
import commonj.work.WorkException;
import commonj.work.WorkManager;
public class WorkManagerDemoServlet extends HttpServlet {
@Resource(name = "wm/somewms")
private WorkManager wm;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
System.out.println("[servlet] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] executing in thread: " + Thread.currentThread().getName());
wm.schedule(new Work() {
@Override
public void run() {
System.out.println("[servlet] self-tuning workmanager: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] self-tuning thread: " + Thread.currentThread().getName());
}
@Override
public void release() {
}
@Override
public boolean isDaemon() {
return false;
}
});
wm.schedule(new Work() {
@Override
public void run() {
System.out.println("[servlet] self-tuning workmanager: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[servlet] self-tuning thread: " + Thread.currentThread().getName());
}
@Override
public void release() {
}
@Override
public boolean isDaemon() {
return false;
}
});
}
catch (WorkException e) {
e.printStackTrace();
}
}
}
На рисунке виден пример вывода в консоль, осуществленного сервлетом. Обе задачи, описанные в коде сервлета, выполняются с помощью WorkManager'а wm/somews и в одном потоке, так как для данного WorkManager'а ограничения MinThread и MaxThread установлены равными единице. Сам же сервлет работает под управлением WorkManager'а по-умолчанию - default.
Важно! Work Manager API не предоставляет никаких механизмов восстановления после сбоев и долгосрочного хранения задач. Если среда управляемого сервера выходит из строя или выключается, любые текущие задачи теряются.
Декларативное назначение WorkManager'а сервлету
Под декларативным назначением WorkManager'а будем понимать указание использовать заданный WorkManager для диспетчеризации запросов. Т.е. для обработки запросов к ресурсу будут использоваться потоки, созданные с помощью указанного WorkManager'а. Под ресурсом понимается сервлет, EJB-компонент, сервис OSB и т.д.
Для декларативного назначения WorkManager'а сервлету необходимо установить значение параметра wl-dispatch-policy сервлета равным JNDI-наименованию WorkManager'а:
<servlet>
<servlet-name>WorkManagerDemoServlet</servlet-name>
<servlet-class>ru.demo.workmanager.WorkManagerDemoServlet</servlet-class>
<init-param>
<param-name>wl-dispatch-policy</param-name>
<param-value>wm/somewms</param-value>
</init-param>
</servlet>
<servlet-name>WorkManagerDemoServlet</servlet-name>
<servlet-class>ru.demo.workmanager.WorkManagerDemoServlet</servlet-class>
<init-param>
<param-name>wl-dispatch-policy</param-name>
<param-value>wm/somewms</param-value>
</init-param>
</servlet>
В результате для обработки каждого запроса сервлетом будет использоваться один поток:
Декларативное назначение WorkManager'а сессионному EJB-компоненту
Для декларативного назначения WorkManager'а EJB-компоненту необходимо указать JNDI-имя данного WorkManager'а в качестве значения элемента wls:dispatch-policy являющегося подэлементом элемента wls:weblogic-enterprise-bean файла weblogic-ejb-jar.xml:
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-ejb-jar xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-ejb-jar" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-ejb-jar http://xmlns.oracle.com/weblogic/weblogic-ejb-jar/1.2/weblogic-ejb-jar.xsd">
<!--weblogic-version:10.3.6-->
<wls:weblogic-enterprise-bean>
<wls:ejb-name>Calculator</wls:ejb-name>
<wls:stateless-session-descriptor>
<wls:business-interface-jndi-name-map>
<wls:business-remote>ru.at.demo.Calculator</wls:business-remote>
<wls:jndi-name>Calculator</wls:jndi-name>
</wls:business-interface-jndi-name-map>
</wls:stateless-session-descriptor>
<wls:jndi-name>CalculatorBean</wls:jndi-name>
<wls:dispatch-policy>CalculatorWorkManager</wls:dispatch-policy>
</wls:weblogic-enterprise-bean>
<wls:work-manager>
<wls:name>CalculatorWorkManager</wls:name>
</wls:work-manager>
</wls:weblogic-ejb-jar>
<wls:weblogic-ejb-jar xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-ejb-jar" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-ejb-jar http://xmlns.oracle.com/weblogic/weblogic-ejb-jar/1.2/weblogic-ejb-jar.xsd">
<!--weblogic-version:10.3.6-->
<wls:weblogic-enterprise-bean>
<wls:ejb-name>Calculator</wls:ejb-name>
<wls:stateless-session-descriptor>
<wls:business-interface-jndi-name-map>
<wls:business-remote>ru.at.demo.Calculator</wls:business-remote>
<wls:jndi-name>Calculator</wls:jndi-name>
</wls:business-interface-jndi-name-map>
</wls:stateless-session-descriptor>
<wls:jndi-name>CalculatorBean</wls:jndi-name>
<wls:dispatch-policy>CalculatorWorkManager</wls:dispatch-policy>
</wls:weblogic-enterprise-bean>
<wls:work-manager>
<wls:name>CalculatorWorkManager</wls:name>
</wls:work-manager>
</wls:weblogic-ejb-jar>
Сам WorkManager можно определить в этом же файле.
Код сессионного бина следующий:
package ru.at.demo;
import javax.ejb.Stateless;
@Stateless(name = "Calculator", mappedName = "Calculator")
public class Calculator implements CalculatorRemote {
@Override
public int add(int x, int y) {
System.out.println("\n\n\t add result = " + (x+y));
System.out.println("[EJB] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
return x + y;
}
@Override
public int sub(int x, int y) {
System.out.println("\n\n\t sub result = " + (x-y));
System.out.println("[EJB] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
return x - y;
}
@Override
public int mul(int x, int y) {
System.out.println("\n\n\t mul result = " + (x*y));
System.out.println("[EJB] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
return x * y;
}
@Override
public int div(int x, int y) {
System.out.println("\n\n\t div result = " + (x/y));
System.out.println("[EJB] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
return x / y;
}
}
import javax.ejb.Stateless;
@Stateless(name = "Calculator", mappedName = "Calculator")
public class Calculator implements CalculatorRemote {
@Override
public int add(int x, int y) {
System.out.println("\n\n\t add result = " + (x+y));
System.out.println("[EJB] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
return x + y;
}
@Override
public int sub(int x, int y) {
System.out.println("\n\n\t sub result = " + (x-y));
System.out.println("[EJB] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
return x - y;
}
@Override
public int mul(int x, int y) {
System.out.println("\n\n\t mul result = " + (x*y));
System.out.println("[EJB] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
return x * y;
}
@Override
public int div(int x, int y) {
System.out.println("\n\n\t div result = " + (x/y));
System.out.println("[EJB] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
return x / y;
}
}
В результате исполнения EJB в консоль выведется название WorkManager'а:
Декларативное назначение WorkManager'а управляемому сообщениями EJB-компоненту
Управляемый сообщениями компонент считывает сообщения из JMS- или JCA-источников данных. При этом по-умолчанию используется WorkManager default и считывание осуществляется в 16 потоков. Такое число потоков может быть как недостаточным, так и избыточным.
Декларативное назначение WorkManager'а управляемому сообщениями компоненту производится аналогично его назначению сессионному компоненту: необходимо указать JNDI-имя данного WorkManager'а в качестве значения элемента wls:dispatch-policy являющегося подэлементом элемента wls:weblogic-enterprise-bean файла weblogic-ejb-jar.xml:
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-ejb-jar xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-ejb-jar" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-ejb-jar http://xmlns.oracle.com/weblogic/weblogic-ejb-jar/1.2/weblogic-ejb-jar.xsd">
<!--weblogic-version:10.3.6-->
<wls:weblogic-enterprise-bean>
<!--options:DESTINATION_JNDI-->
<wls:ejb-name>DemoMDB</wls:ejb-name>
<wls:message-driven-descriptor>
<wls:connection-factory-jndi-name>jms/xa/ConnectionFactory</wls:connection-factory-jndi-name>
</wls:message-driven-descriptor>
<wls:dispatch-policy>wm/DemoWorkManager</wls:dispatch-policy>
</wls:weblogic-enterprise-bean>
</wls:weblogic-ejb-jar>
<wls:weblogic-ejb-jar xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-ejb-jar" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-ejb-jar http://xmlns.oracle.com/weblogic/weblogic-ejb-jar/1.2/weblogic-ejb-jar.xsd">
<!--weblogic-version:10.3.6-->
<wls:weblogic-enterprise-bean>
<!--options:DESTINATION_JNDI-->
<wls:ejb-name>DemoMDB</wls:ejb-name>
<wls:message-driven-descriptor>
<wls:connection-factory-jndi-name>jms/xa/ConnectionFactory</wls:connection-factory-jndi-name>
</wls:message-driven-descriptor>
<wls:dispatch-policy>wm/DemoWorkManager</wls:dispatch-policy>
</wls:weblogic-enterprise-bean>
</wls:weblogic-ejb-jar>
Код управляемого сообщениями компонента может быть следующим:
package name.samolisov.mdb.wm.demo;
import java.util.Date;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
@MessageDriven(mappedName = "jms/Queue2")
public class DemoMDB implements MessageListener {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("[DemoMDB] Receiving the message: " + textMessage.getText());
System.out.println("[DemoMDB] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[DemoMDB] executing in thread: " + Thread.currentThread().getName());
System.out.println("[DemoMDB] start at " + (new Date()));
Thread.sleep(2000);
System.out.println("[DemoMDB] end at " + (new Date()));
} catch (JMSException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import java.util.Date;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
@MessageDriven(mappedName = "jms/Queue2")
public class DemoMDB implements MessageListener {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("[DemoMDB] Receiving the message: " + textMessage.getText());
System.out.println("[DemoMDB] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[DemoMDB] executing in thread: " + Thread.currentThread().getName());
System.out.println("[DemoMDB] start at " + (new Date()));
Thread.sleep(2000);
System.out.println("[DemoMDB] end at " + (new Date()));
} catch (JMSException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Если ограничить количество потоков для WorkManager'a wm/DemoWorkManager двумя, то в консоль при обработке сообщений будут выводиться следующие строки:
Видно, что сообщения обрабатываются попарно.
В консоли администратора сервера приложений WebLogic при этом будет отображаться количество подписчиков на очередь сообщений. Видно, что данное число равно числу потоков в WorkManager'е - двум:
Важно! Чтобы считывание сообщений действительно происходило в несколько потоков, нужно сделать следующее:
- Явно определить фабрику соединений, используемую управляемым сообщениями компонентом:
<wls:message-driven-descriptor>
<wls:connection-factory-jndi-name>jms/xa/ConnectionFactory</wls:connection-factory-jndi-name>
</wls:message-driven-descriptor>
- Установить значение параметра Maximum Messages per Session у данной фабрики равным единице:
Декларативное назначение WorkManager'а управляемому сообщениями POJO-компоненту
Управляемым сообщениями POJO-компонентом называют Java-класс, реализующий интерфейс javax.jms.MessageListener, но не являющийся EJB-компонентом. Подключение такого компонента к очереди сообщений может осуществляться, например, посредством Spring Framework.
При этом Spring Framework содержит средства интеграции с СommonJ, в частности - класс org.springframework.scheduling.commonj.WorkManagerTaskExecutor. Объект данного класса можно инъектировать в другие компоненты Spring Framework, содержащие соответствующее свойство, например в org.springframework.jms.listener.SimpleMessageListenerContainer:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd">
<bean id="taskExecutor" class="org.springframework.scheduling.commonj.WorkManagerTaskExecutor">
<property name="workManagerName" value="java:comp/env/wm/DemoWorkManager"/>
<property name="resourceRef" value="true"/>
</bean>
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
<property name="environment">
<props>
<prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
<prop key="java.naming.provider.url">t3://localhost:7011</prop>
</props>
</property>
</bean>
<bean id="connectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate" ref="jndiTemplate"/>
<property name="jndiName" value="jms/ConnectionFactory"/>
</bean>
<bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate" ref="jndiTemplate"/>
<property name="jndiName" value="jms/Queue"/>
</bean>
<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="receiver"/>
<property name="taskExecutor" ref="taskExecutor"/>
</bean>
<bean id="receiver" class="name.samolisov.spring.wm.demo.JMSReceiver"/>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd">
<bean id="taskExecutor" class="org.springframework.scheduling.commonj.WorkManagerTaskExecutor">
<property name="workManagerName" value="java:comp/env/wm/DemoWorkManager"/>
<property name="resourceRef" value="true"/>
</bean>
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
<property name="environment">
<props>
<prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
<prop key="java.naming.provider.url">t3://localhost:7011</prop>
</props>
</property>
</bean>
<bean id="connectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate" ref="jndiTemplate"/>
<property name="jndiName" value="jms/ConnectionFactory"/>
</bean>
<bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate" ref="jndiTemplate"/>
<property name="jndiName" value="jms/Queue"/>
</bean>
<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="receiver"/>
<property name="taskExecutor" ref="taskExecutor"/>
</bean>
<bean id="receiver" class="name.samolisov.spring.wm.demo.JMSReceiver"/>
</beans>
Сам управляемый сообщениями POJO:
package name.samolisov.spring.wm.demo;
import java.util.Date;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
public class JMSReceiver implements MessageListener {
public JMSReceiver() {
System.out.println("[JMSReceiver] construct");
}
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("[JMSReceiver] Receiving the message: " + textMessage.getText());
System.out.println("[JMSReceiver] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[JMSReceiver] executing in thread: " + Thread.currentThread().getName());
System.out.println("[JMSReceiver] start at " + (new Date()));
Thread.sleep(2000);
System.out.println("[JMSReceiver] end at " + (new Date()));
} catch (JMSException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import java.util.Date;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
public class JMSReceiver implements MessageListener {
public JMSReceiver() {
System.out.println("[JMSReceiver] construct");
}
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("[JMSReceiver] Receiving the message: " + textMessage.getText());
System.out.println("[JMSReceiver] executing in: " +
(((weblogic.work.ExecuteThread) Thread.currentThread()).getWorkManager().getName()));
System.out.println("[JMSReceiver] executing in thread: " + Thread.currentThread().getName());
System.out.println("[JMSReceiver] start at " + (new Date()));
Thread.sleep(2000);
System.out.println("[JMSReceiver] end at " + (new Date()));
} catch (JMSException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Для того, чтобы Spring Framework получил доступ к WorkManager'у, на последний должна быть объявлена ссылка в web.xml:
<resource-ref>
<res-ref-name>wm/DemoWorkManager</res-ref-name>
<res-type>commonj.work.WorkManager</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
<res-ref-name>wm/DemoWorkManager</res-ref-name>
<res-type>commonj.work.WorkManager</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
После развертывания проекта на сервере приложений управляемый сообщениями POJO начинает слушать очередь. При этом, независимо от настроек WorkManager'а в консоли администратора отображается, что на очередь есть 16 подписчиков - значение по-умолчанию:
В то же время, если задать WorkManager'у ограничение в один поток, то сообщения будут считываться строго последовательно:
Если же увеличить количество потоков до трех, то и сообщения будут считываться из очереди тройками:
Дополнительных настроек очереди сообщений при использовании управляемого сообщениями POJO делать не нужно.
Декларативное назначение WorkManager'а сервисам Oracle Service Bus
WorkManager для прокси-сервиса OSB указывается в качестве значения параметра Dispatch Policy на вкладке ТИП ТРАНСПОРТА Transport. При этом в IDE Eclipse подгружаются глобальные WorkManager'ы домена, на котором будет разворачиваться проект для OSB
Назначение WorkManager'а для бизнес-сервиса OSB происходит аналогично.
Заключение
Мы рассмотрели один из важнейших компонентов среды сервера приложений - WorkManager. Данный компонент предназначен для тонкой настройки производительности системы в зависимости от возможностей имеющегося оборудования и приоритета исполняемых на сервере задач.
Сервер приложений WebLogic максимально полно использует возможности WorkManager'ов для управления потоками. Практически каждый компонент стека Java EE (сервлет, сессионный и управляемый событиями компоненты) и даже POJO и сервисы OSB могут быть настроены таким образом, чтобы использовать для своей работы потоки с помощью указанного WorkManager'а, сконфигурированного определенным образом. Допускается даже изменение параметров доступа к потокам в зависимости от того, какой именно пользователь выполняет запрос.
Очень грубая ошибка пытаться создавать потоки в управляемой среде с помощью явного обращения к классу Thread. Такие потоки не будут управляться сервером приложений и могут привести к значительной просадке производительности. Если в приложении нужно создать отдельный поток, то необходимо использовать CommonJ API.
Ресурсы
- Oracle Fusion Middleware Timer and Work Manager API (CommonJ) Programmer's Guide for Oracle WebLogic Server 11g Release 1 (10.3.6);
- Using Work Managers to Optimize Scheduled Work;
- Tuning Message-Driven Beans.
Понравилось сообщение - подпишитесь на блог и Twitter
6 комментариев:
Добрый день.
Что делать в ситуации, когда в системе существует несколько WorkManager используемых для доступа к различным ресурсам и все потоки одного из них уйдут в STUCK состояние из-за какой-либо проблеме на ресурсе (на удаленном сервере или в БД, не важно)?
Выходят ли потоки из этого состояния сами, после того, как ресурс снова восстановит свою работоспособность?
Если нет, то можем ли мы не рестартуя manage-сервер переинициализировать потоки в пуле Work Manager ?
Здравствуйте. Да, потоки сами выходят из состояния STUCK, если код взаимодействия с ресурсом написан правильно. По крайней мере я лично видел, как уменьшалось число потоков в STUCK состоянии. Другое дело, что из-за ошибок в коде или самого WLS, или приложения, бывает так, что например БД стала доступна, но поток продолжает висеть на какой-то операции.
Переинициализировать пул потоков без перезагрузки сервера нельзя, но можно настроить автоматическое выключение сервера при превышении некоторого порога числа потоков в состоянии STUCK.
Спасибо за ответ. Примерно так себе и представлял ситуацию.
Автоматический рестарт настроен, но это в сущности остановка сервиса на какое-то время. Хотелось бы без таким мер обойтись.
Будем искать обходное решение.
Лучше всего конечно разобраться с причиной зависания потоков, возможно не хватает настройки таймаутов на операции ввода-вывода (по моим наблюдениям - 80% проблем с зависанием потоков решаются настройками таймаутов, но это на OSB, допускаю, что если писать код самому, то проблемы могут быть более хитрыми).
Если решить проблему зависания потоков нельзя, то нужно попробовать настроить поднятие остановленного аварийно сервера с помощью NodeManager'а и организовать кластеризацию нескольких экземпляров WebLogic'а, хоть и на одной машине.
Добрый день. А если у меня несколько серверов, которые объединены в кластер, то можно как-то настроить WorkManager, чтобы была балансировка между серверами?
Я, если честно, не понял вопроса. Балансировку между серверами делает или HTTP Server (если речь идет о доступе по HTTP-протоколу) или EJB-клиент, если речь идет о t3.
Отправить комментарий
Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!