Одной из самых важных этапов оптимизации сервера приложений Glassfish является настройка пула потоков обработки запросов (Thread Pool).
1. Предисловие
Толчком к написанию данной статьи стало появление назойливого сбоя в работе приложения.
Признаки сбоя заключались в следующем:
временами при обращении к веб-приложению запрос подвисал на неопределенный промежуток времени, потом все начинало работать нормально до следующего зависания;
в логе сервера приложений временами проскакивала следующая ошибка:
[#|2013-04-09T11:26:37.615+0400|FINE|glassfish3.1|com.sun.grizzly.config.GrizzlyServiceListener|_ThreadID=48;_ThreadName=Thread-1;ClassName=com.sun.grizzly.filter.ReadFilter;MethodName=log;|ReadFilter.execute
java.nio.channels.ClosedChannelException
at sun.nio.ch.SocketChannelImpl.ensureReadOpen(SocketChannelImpl.java:236)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:279)
at com.sun.grizzly.filter.ReadFilter.execute(ReadFilter.java:165)
at com.sun.grizzly.filter.ReadFilter.execute(ReadFilter.java:107)
at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
at java.lang.Thread.run(Thread.java:722)
|#]
Для выявления причин и исправления ситуации были проведены следующие действия:
Снятие дампа потоков сервера приложений Glassfish в момент зависания приложения:
bash> asadmin --port=9010 --user=admin –passwordfile=path generate-jvm-report --type=thread
Full Java Thread Dump Java HotSpot(TM) 64-Bit Server VM 23.1-b03 Oracle Corporation
Number of threads: 64
Number of daemon threads: 53
Peak live thread count since the Java virtual machine started or peak was reset: 88
…
Thread "http-thread-pool-8080(5)" thread-id: 41 thread-state: RUNNABLE Running in native
… org.springframework.jdbc.core.JdbcTemplate$1QueryStatementCallback.doInStatement(JdbcTemplate.java:441)
…
Thread "http-thread-pool-8080(4)" thread-id: 40 thread-state: RUNNABLE Running in native
… org.springframework.jdbc.core.JdbcTemplate$1QueryStatementCallback.doInStatement(JdbcTemplate.java:441)
…
Thread "http-thread-pool-8080(3)" thread-id: 39 thread-state: RUNNABLE Running in native
… org.springframework.jdbc.core.JdbcTemplate$1QueryStatementCallback.doInStatement(JdbcTemplate.java:441)
…
Thread "http-thread-pool-8080(2)" thread-id: 38 thread-state: RUNNABLE Running in native
… sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1322)
at: java.net.URL.openStream(URL.java:1035)
…
Thread "http-thread-pool-8080(1)" thread-id: 37 thread-state: RUNNABLE Running in native
… org.springframework.jdbc.core.JdbcTemplate$1QueryStatementCallback.doInStatement(JdbcTemplate.java:441)
…
Command generate-jvm-report executed successfully.
Анализ дампа потоков.
Анализ выявил, что из 5 потоков в пуле 4 были заняты ожиданием ответа от СУБД, а 1 поток был занят ожиданием ответа от запроса к серверу приложений Glassfish, что и вызывало взаимную блокировку. Обращение к сервере приложений уже не могло быть обработано, поскольку все 5 потоков были заняты и поэтому зависание продолжалось до истечения срока пребывания запроса в очереди.
Выявление запросов в СУБД, результатов которых ожидали потоки.
Исправление возникшей ситуации:
Настройка максимального количества одновременно выполняющихся потоков.
Оптимизация запросов к БД.
P.S. Исключение java.nio.channels.ClosedChannelException, выбрасываемое в лог сервера приложений возникало в случае, когда пользователь, не дождавшись ответа, закрывал браузер, а сервер приложений по истечении таймаута пытался закрыть соединение, которое уже было закрыто пользователем.
2. О потоках
Под потоком понимается поток выполнения в виртуальной Java машине.
Glassfish, в целях повышения производительности, управляет несколькими пулами потоков.
Пул потоков — это группа потоков с уникальным наименованием. Каждый пул может быть назначен различным коннекторам и обработчикам. Например, один пул для обработки http запросов, другой для обработки запросов к консоли администрирования и т.п.
3. Обработка запросов
Обработка запросов в сервере Glassfish строится по следующей схеме:
Glassfish получает запрос от клиента
Проверяется наличие свободного потока выполнения в пуле потоков.
Свободный поток — это поток, который находится в состоянии ожидания. Его состояние обозначается статусом TIMED_WAITING.
Если свободный поток обнаруживается, то обработка запроса отдается этому потоку, и его статус меняется на RUNNING.
Иначе запрос ставится в очередь ожидания свободного потока.
Если по истечении определенного периода времени запросу так и не будет выделен поток выполнения, то этот запрос удаляется из очереди.
После обработки запроса поток вновь переходит в состояние ожидания.
Стоит отметить, что если во время обработки запроса происходят какие-либо время затратные операции: обработка запросов БД, ожидание ввода/вывода, то поток остается в состоянии RUNNING и не освобождается для выполнения других запросов.
4. Настройка пула потоков
Настройку пула потоков можно производить двумя способами:
либо через файл описания настроек домена domain.xml.
Максимальное количество параллельно выполняющихся потоков
5
minthreadpoolsize
Минимальное количество потоков, находящихся в пуле
2
idletimeout
Количество секунд, которое неактивные потоки находятся в пуле. По прошествии этого времени, неактивные потоки удаляются из пула
900
maxqueuesize
Максимальное количество запросов, которое может находится в очереди, ожидая пока они будут обработаны потоками пула
4096
6. Оптимизация
Задача оптимизации заключается в выборе подходящего под наше приложение набора значений опций.
В принципе, основной опцией, которую следует настраивать, является опция «maxthreadpoolsize».
Опция «maxthreadpoolsize» описывает максимальное количество запросов, которые могут быть обработаны параллельно.
Значение данной опции зависит от характера обработки запросов нашим приложением:
Если запросы выполняются длительное время и во время обработки выполняют обращения к системе ввода/вывода или к СУБД, то может потребоваться увеличение количества максимального количества параллельно выполняющихся запросов.
Если же мы имеем дело с большим количеством запросов, обработка, которых ложится целиком на сам сервер приложений, то параллельное выполнение большого количества может вызвать высокую нагрузку на CPU и может понадобиться уменьшение максимального количества параллельно выполняющихся запросов.
В документации по настройке производительности сервера приложений Glassfish говорится о допустимом интервале значения данной опции от 100 до 500, в зависимости от нагрузки.
Также разумно выставлять значение максимального количества параллельно выполняющихся запросов кратным числу процессоров.
В задачу оптимизации в прочем входит настройка параметра максимального количества параллельно выполняющихся запросов и последующий мониторинг состояния сервера с последующей регулировкой значения.
Ход работ по оптимизации может состоять из следующих действий:
Проверка значения количества доступных потоков в пуле и количества работающих потоков в данный момент времени соответственно:
asadmin> get –monitor server.network.http-listener-1.thread-pool.currentthreadcount-count
asadmin> get –monitor server.network.http-listener-1.thread-pool.currentthreadsbusy-count
Проверка нагрузки на CPU:
bash> vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
1 0 11916 469756 4039780 3478788 0 0 25 110 0 1 0 24 72 3
Параметр «id» указывает на процент времени нахождения процессора в состоянии покоя.
Изменение значения параметра maxthreadpoolsize в зависимости от полученных результатов на предыдущих шагах:
asadmin> set server.thread-pools.thread-pool.http-thread-pool.max-thread-pool-size=16
Command set executed successfully
8. Мониторинг
После первоначальной настройки сервера приложений Glassfish необходимо настроить регулярный мониторинг его состояния и реагировать на критические моменты, но это уже материал для другой статьи.