вторник, 22 сентября 2009 г.

Shared server - это очень просто

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

Эта схема хороша для чистого клиент-сервера, при условии, что достаточно памяти для запуска всех без исключения процессов и при этом соединения держатся достаточно долго, для того, чтобы не приходилось сессии разрывать и устанавливать заново.

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

Это Web-приложения, работающие с базой. mod_plsql работает как Oracle*Net клиент, и, при каждом HTTP-вызове устанавливает и разрывает соединение с базой. Это ведет к большому количеству запусков выделенных процессов, высокому переключению контекстов и, в конечном итоге, ухудшает отклик системы.

Все бы ничего, но при резких скачках нагрузки и фиксированных настройках Web-листенера и базы статистика фиксирует достаточно часто появляющиеся ошибки "503: Server busy". Что в принципе говорит о том, что база не успевает реагировать на запросы ввиду того, что приходится быстро запускать серверные процессы в достаточно большом количестве.

Технически Оракл иногда рекомендует в подобных случаях (для Web-приложений) использовать Shared server. (Раньше в Oracle*Net была возможность в DEDICATED-режиме установить некоторое количество prespawned-серверных процессов, сейчас ее нет).

Считается, однако, что Shared-сервер непонятно, как сконфигурировать и как использовать.

На самом деле все очень просто.

Возьмем реальный пример базы, которая находится на одном хосте с Web-листенером. Допустим, что мы хотим ее перевести для Web-соединений (и только для них) в режим Shared server. Оставив сервис DEDICATED для административных целей.

Как мы это сделаем?

Во-первых, выключим существующие диспетчеры по умолчанию:

alter system set dispatchers='';

Делаем мы это для того, чтобы не плодить толпу слушающих диспетчеров, потому что нижеприведенная команда добавляет диспетчеры к существующим. По умолчанию в десятке есть диспетчер XDB, он нам не больно-то и нужен. Посему сбросим его и будем задавать собственные диспетчеры.

Затем приступим к конфигурированию собственно Shared-сервиса базы:

alter system set large_pool_size=8m scope=both;

alter system set dispatchers="(ADDRESS=(PROTOCOL=tcp)(PORT=16384))(DISPATCHERS=2)" scope=both;

alter system set shared_server_sessions=100 scope=both;

Параметр shared_server_sessions требуется задать для скорости, причем его значение меньше, чем значение processes (в Oracle Reference объясняется, почему это нужно сделать). В нашем случае processes = 150. Порт диспетчеров мы зафиксировали специально, потому что в противном случае он будет при каждом запуске инстанса меняться.

Запустим два серверных процесса начально:

alter system set shared_servers=2 scope=both;

и ограничим максимальное количество процессов-диспетчеров и серверных процессов разумной величиной:

alter system set max_dispatchers=5 scope=both;
alter system set max_shared_servers=5 scope=both;

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

oracle @ hostname1 ~ $ lsnrctl status

LSNRCTL for Solaris: Version 10.2.0.4.0 - Production on 22-СЕН-2009 12:58:03

Copyright (c) 1991, 2007, Oracle. All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=hostname1)(PORT=1521)(QUEUESIZE=10)(SEND_BUF_SIZE=65536)(RECV_BUF_SIZE=65536)))
STATUS of the LISTENER
------------------------
Alias LISTENER
Version TNSLSNR for Solaris: Version 10.2.0.4.0 - Production
Start Date 21-СЕН-2009 11:49:01
Uptime 1 days 1 hr. 9 min. 2 sec
Trace Level off
Security ON: Password or Local OS Authentication
SNMP OFF
Listener Parameter File /export/home/OraHome1/app/oracle/product/10.2.0/network/admin/listener.ora
Listener Log File /export/home/OraHome1/app/oracle/product/10.2.0/network/log/listener.log
Listening Endpoints Summary...
(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=www.yvoinov.com)(PORT=1521)))
Services Summary...
Service "PLSExtProc" has 1 instance(s).
Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
Service "SUN10.domain.com" has 2 instance(s).
Instance "SUN10", status UNKNOWN, has 1 handler(s) for this service...
Instance "SUN10", status READY, has 2 handler(s) for this service...
Service "SUN10_XPT.domain.com" has 2 instance(s).
Instance "SUN10", status UNKNOWN, has 1 handler(s) for this service...
Instance "SUN10", status READY, has 2 handler(s) for this service...
The command completed successfully

DEDICATED-сервис назывался SUN10, Shared-сервис называется SUN10_XPT, имя присвоено автоматически.

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

SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = SUN10.domain.com)
(SID_NAME = SUN10)
(ORACLE_HOME = /export/home/OraHome1/app/oracle/product/10.2.0)
)
(SID_DESC =
(GLOBAL_DBNAME = SUN10_XPT.domain.com)
(SID_NAME = SUN10)
(ORACLE_HOME = /export/home/OraHome1/app/oracle/product/10.2.0)
)
(SID_DESC =
(SID_NAME = PLSExtProc)
(ORACLE_HOME = /export/home/OraHome1/app/oracle/product/10.2.0)
(PROGRAM = extproc)
)
)

ADMIN_RESTRICTIONS_LISTENER = ON

LOGGING_LISTENER = ON

LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = www.domain.com)(PORT = 1521)(QUEUESIZE = 10)(SEND_BUF_SIZE = 65536)(RECV_BUF_SIZE = 65536))
)
)
)

Заметьте две вещи - мы не добавили строку прослушивания для порта 16386 shared-сервера и добавили статическую регистрацию для него же. Первое - не нужно (листенер не запустится), второе нужно дабы исключить таймауты при автоматической регистрации сервисов.

Во-вторых, нужно настроить mod_plsql для соединения не с DEDICATED, а с SHARED-сервером, ну и для остальных клиентов базы разделить сервисы, чтобы, скажем, администраторы всегда ходили на DEDICATED-сервис.

Изменим одну запись в dads.conf и перезапустим Web-сервер:

#PlsqlDatabaseConnectString localhost:1521:SUN10.domain.com ServiceNameFormat
PlsqlDatabaseConnectString localhost:16384:SUN10_XPT.domain.com ServiceNameFormat

Для решения последней задачи добавим в tnsnames.ora запись о shared-сервисе и изменим запись dedicated-сервиса:

SUN10 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = SUN10.domain.com)
(SERVER=dedicated)
)
)

SUN10_XPT =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 16384))
)
(CONNECT_DATA =
(SERVICE_NAME = SUN10_XPT.domain.com)
(SERVER=shared)
)
)

В общем-то, это все. Осталось проверить, работает ли сервис:

oracle @ hostname ~ $ lsnrctl services

LSNRCTL for Solaris: Version 10.2.0.4.0 - Production on 22-СЕН-2009 13:06:51

Copyright (c) 1991, 2007, Oracle. All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=www.domain.com)(PORT=1521)(QUEUESIZE=10)(SEND_BUF_SIZE=65536)(RECV_BUF_SIZE=65536)))
Services Summary...
Service "PLSExtProc" has 1 instance(s).
Instance "PLSExtProc", status UNKNOWN, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0
LOCAL SERVER
Service "SUN10.domain.com" has 2 instance(s).
Instance "SUN10", status UNKNOWN, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0
LOCAL SERVER
Instance "SUN10", status READY, has 2 handler(s) for this service...
Handler(s):
"D000" established:0 refused:0 current:8 max:992 state:ready
DISPATCHER
(ADDRESS=(PROTOCOL=tcp)(HOST=www.domain.com)(PORT=16384))
"DEDICATED" established:0 refused:0 state:ready
LOCAL SERVER
Service "SUN10_XPT.domain.com" has 2 instance(s).
Instance "SUN10", status UNKNOWN, has 1 handler(s) for this service...
Handler(s):
"DEDICATED" established:0 refused:0
LOCAL SERVER
Instance "SUN10", status READY, has 2 handler(s) for this service...
Handler(s):
"D000" established:0 refused:0 current:8 max:992 state:ready
DISPATCHER
(ADDRESS=(PROTOCOL=tcp)(HOST=www.domain.com)(PORT=16384))
"DEDICATED" established:0 refused:0 state:ready
LOCAL SERVER
The command completed successfully

Работает. Посмотрим представления базы данных:

SQL> select dispatcher, status, queue from v$circuit;

DISPATCHER STATUS QUEUE
---------------- ---------------- ----------------
00000003BFE619B0 NORMAL NONE
00000003BFE619B0 NORMAL NONE
00000003BFE619B0 NORMAL NONE
00000003BFE619B0 NORMAL NONE
00000003BFE619B0 NORMAL NONE
00000003BFE619B0 NORMAL NONE

6 rows selected.

SQL> select name, circuit, idle, busy, requests from v$shared_server;

NAME CIRCUIT IDLE BUSY REQUESTS
---- ---------------- ---------- ---------- ----------
S000 00 9300193 6556 7667
S001 00 9299953 6842 7872

Как видно, все, что мы хотели - мы и получили. Со временем можно подкорректировать количество процессов.

Что до результатов - того, ради чего это все затевалось - то субъективные замеры показывают минимум двукратное улучшение отклика, что, в общем, легко объяснимо - постоянно запущенные диспетчеры и серверные процессы всегда готовы принять сессию из Web, и почти прекратились ошибки 503 Server Busy.

PS. В Oracle 10R2 на платформах x86/x64 не советую включать на диспетчерах connection pooling - из-за ошибки в ядре база начинает дампить с ошибками 7445 и 600. Конечно, pooling сильно увеличивает нагрузочную способность диспетчеров - однако стабильность будет никакая.