Оптимизация Glassfish - настройка пула потоков

Одной из самых важных этапов оптимизации сервера приложений 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)
|#]
Для выявления причин и исправления ситуации были проведены следующие действия:
  1. Снятие дампа потоков сервера приложений 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.
    
  2. Анализ дампа потоков.
    Анализ выявил, что из 5 потоков в пуле 4 были заняты ожиданием ответа от СУБД, а 1 поток был занят ожиданием ответа от запроса к серверу приложений Glassfish, что и вызывало взаимную блокировку. Обращение к сервере приложений уже не могло быть обработано, поскольку все 5 потоков были заняты и поэтому зависание продолжалось до истечения срока пребывания запроса в очереди.
  3. Выявление запросов в СУБД, результатов которых ожидали потоки.
  4. Исправление возникшей ситуации:
    1. Настройка максимального количества одновременно выполняющихся потоков.
    2. Оптимизация запросов к БД.
P.S. Исключение java.nio.channels.ClosedChannelException, выбрасываемое в лог сервера приложений возникало в случае, когда пользователь, не дождавшись ответа, закрывал браузер, а сервер приложений по истечении таймаута пытался закрыть соединение, которое уже было закрыто пользователем.

2. О потоках

Под потоком понимается поток выполнения в виртуальной Java машине.
Glassfish, в целях повышения производительности, управляет несколькими пулами потоков.
Пул потоков — это группа потоков с уникальным наименованием. Каждый пул может быть назначен различным коннекторам и обработчикам. Например, один пул для обработки http запросов, другой для обработки запросов к консоли администрирования и т.п.

3. Обработка запросов

Обработка запросов в сервере Glassfish строится по следующей схеме:
  1. Glassfish получает запрос от клиента
  2. Проверяется наличие свободного потока выполнения в пуле потоков.
  3. Свободный поток — это поток, который находится в состоянии ожидания. Его состояние обозначается статусом TIMED_WAITING.
  4. Если свободный поток обнаруживается, то обработка запроса отдается этому потоку, и его статус меняется на RUNNING.
  5. Иначе запрос ставится в очередь ожидания свободного потока. Если по истечении определенного периода времени запросу так и не будет выделен поток выполнения, то этот запрос удаляется из очереди.
  6. После обработки запроса поток вновь переходит в состояние ожидания. Стоит отметить, что если во время обработки запроса происходят какие-либо время затратные операции: обработка запросов БД, ожидание ввода/вывода, то поток остается в состоянии RUNNING и не освобождается для выполнения других запросов.

4. Настройка пула потоков

Настройку пула потоков можно производить двумя способами:
  • либо через файл описания настроек домена domain.xml.
  • либо динамически через утилиту asadmin.
Описание команд динамической настройки находится в руководстве администратора:
http://docs.oracle.com/cd/E18930_01/html/821-2416/abluc.html#scrolltoc

5. Опции пула потоков:

Опция Описание По умолчанию
maxthreadpoolsize Максимальное количество параллельно выполняющихся потоков 5
minthreadpoolsize Минимальное количество потоков, находящихся в пуле 2
idletimeout Количество секунд, которое неактивные потоки находятся в пуле. По прошествии этого времени, неактивные потоки удаляются из пула 900
maxqueuesize Максимальное количество запросов, которое может находится в очереди, ожидая пока они будут обработаны потоками пула 4096

6. Оптимизация

Задача оптимизации заключается в выборе подходящего под наше приложение набора значений опций.

В принципе, основной опцией, которую следует настраивать, является опция «maxthreadpoolsize».
Опция «maxthreadpoolsize» описывает максимальное количество запросов, которые могут быть обработаны параллельно.
Значение данной опции зависит от характера обработки запросов нашим приложением:

  • Если запросы выполняются длительное время и во время обработки выполняют обращения к системе ввода/вывода или к СУБД, то может потребоваться увеличение количества максимального количества параллельно выполняющихся запросов.
  • Если же мы имеем дело с большим количеством запросов, обработка, которых ложится целиком на сам сервер приложений, то параллельное выполнение большого количества может вызвать высокую нагрузку на CPU и может понадобиться уменьшение максимального количества параллельно выполняющихся запросов.

В документации по настройке производительности сервера приложений Glassfish говорится о допустимом интервале значения данной опции от 100 до 500, в зависимости от нагрузки.
Также разумно выставлять значение максимального количества параллельно выполняющихся запросов кратным числу процессоров.
Просмотреть число процессоров можно командой:
bash> cat /proc/cpuinfo | grep processor
processor       : 0
processor       : 1
processor       : 2
processor       : 3

7. Ход оптимизации

В задачу оптимизации в прочем входит настройка параметра максимального количества параллельно выполняющихся запросов и последующий мониторинг состояния сервера с последующей регулировкой значения.

Ход работ по оптимизации может состоять из следующих действий:
  1. Включение мониторинга:
    asadmin> enable-monitoring --modules http-service=HIGH
    
  2. Проверка значения количества доступных потоков в пуле и количества работающих потоков в данный момент времени соответственно:
    asadmin> get –monitor server.network.http-listener-1.thread-pool.currentthreadcount-count
    asadmin> get –monitor server.network.http-listener-1.thread-pool.currentthreadsbusy-count
    
  3. Проверка нагрузки на 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» указывает на процент времени нахождения процессора в состоянии покоя.
  4. Изменение значения параметра maxthreadpoolsize в зависимости от полученных результатов на предыдущих шагах:
    asadmin> set server.thread-pools.thread-pool.http-thread-pool.max-thread-pool-size=16
    Command set executed successfully
    

8. Мониторинг

После первоначальной настройки сервера приложений Glassfish необходимо настроить регулярный мониторинг его состояния и реагировать на критические моменты, но это уже материал для другой статьи.