Часть 2

Продолжение, часть 1 см. # 1, 1996

Г.М. Ладыженский

1. Режимы взаимодействия клиента и сервера
1.1. Асинхронный режим
2. Диалоговый режим
3. Рассылка сообщений
4. Управление транзакциями
2. Основные возможности Tuxedo System
2.1. Баланс загрузки
2.2. Маршрутизация запросов
2.3. Динамическое распределение загрузки
2.4. Мультиплексирование запросов к базам данных
Заключение
Благодарности
Литература

В статье продолжается обсуждение менеджера распределенных транзакций Tuxedo System, начатое в предыдущем номере журнала. Напомним, что в первой части статьи были рассмотрены основные понятия Tuxedo и интерфейс прикладного программирования Tuxedo ATMI. Вторая часть посвящена детальному рассмотрению режимов взаимодействия Клиента и Сервера, а также некоторым ключевым возможностям Tuxedo System, не требующим программирования, но достигаемым чисто административными действиями (баланс загрузки, маршрутизация запросов и т.д.).

Несколько слов о средствах разработки систем с трехзвенной архитектурой "клиент-сервер" на основе Tuxedo System. Читатель обратит внимание на приводимые ниже примеры, которые представляют собой программы на языке C. Предваряя замечания со стороны разработчиков: "Это что же, нам придется все писать на C?", автор должен сделать разъяснение.

Действительно, если мы планируем использование только Tuxedo System, то Приложение придется разрабатывать на одном из трех языков - C, C++ или COBOL. В то же время Клиенты Приложения можно создавать с помощью любых средств разработки (PowerBuilder, SQL Windows, MS VisualBasic, CA-VisualObjects) - важно, чтобы из программ можно было бы вызывать C-функции. Программы-клиенты Приложения можно разрабатывать для использования в операционной среде DOS, MS Windows, MacOS, OS/2, UNIX, Windows NT.

Таким образом, проблема состоит только в выборе систем проектирования и реализации Серверов Приложения.

1. Режимы взаимодействия клиента и сервера

1.1. Асинхронный режим

Более естественным, по сравнению с режимом "запрос/ответ", является асинхронный режим. Так же, как и в синхронном режиме, Клиент запрашивает необходимый ему Сервис (посредством вызова функции tpacall()), передавая ему данные в буфере, однако он не ожидает ответа, но продолжает выполнять действия, определенные в программе. Ответ на запрос Клиент получает с помощью вызова tpgetreply(). Таким образом, Клиент и Сервер работают асинхронно. Вызов tpacall() определен следующим образом:

int tpacall(svc, ibuf, ilen, flags)
char *svc, ibuf;
long ilen, flags;

Смысл параметров такой же, что и для вызова tpcall(). Отметим, что среди параметров отсутствуют результирующий буфер и его размер. Это понятно, поскольку в рамках tpcall() происходит только передача исходных данных. Вызов tpacall() возвращает целое значение, которое затем используется как дескриптор вызова в функции tpgetreply(). Ее синтаксис выглядит следующим образом:

int tpgetreply (cd, obuf, olen, flags)
int *cd;
char *obuf;
long olen, flags;

где cd - это дескриптор вызова, полученный как результат предшествующего вызова tpacall(), а остальные параметры означают то же самое, что и для функции tpcall().

Используя функцию tpgetreply(), Клиент ожидает (некоторое время или бесконечно долго - в зависимости от того, каково значение или комбинация значений параметра flags) ответа от Сервиса, который был вызван одним из предшествующих вызовов tpacall(). Если для вызова tpgetreply() был установлен некоторый тайм-аут, и по его прошествии Клиент не получает ответа, tpgetreply() возвращает ошибку.

2. Диалоговый режим

В диалоговом (conversational) режиме System/T открывает канал для взаимодействия между Клиентом и конкретным Сервисом. Канал функционирует в полудуплексном режиме. Клиент и Сервис могут посылать запросы в канал, но не одновременно. В каждый конкретный момент времени контроль над каналом принадлежит либо Клиенту, либо Сервису. Тот, кто имеет контроль над каналом (инициатор), направляет в него запросы, которые получает противная сторона (субординатор). Инициатор может вместе с запросом передать и контроль над ним субординатору, тогда они меняются ролями. Открыть и закрыть канал может только Клиент. Диалог продолжается до тех пор, пока инициатор не закроет канал, либо не произойдет ошибка и System/T не закроет канал самостоятельно. Клиент может открыть и поддерживать одновременно до 10 каналов. Каждый канал имеет дескриптор, который и используется при передаче и приеме данных.

Диалоговый режим взаимодействия обеспечивается следующими вызовами: tpconnect() - открыть канал, tpsend() - передать данные, tprecv() - получить данные, tpdiscon() - закрыть канал. Их детали разъясняются ниже.

int tpconnect (name, data, len, flags)
char *name, *data;
long len, flags;

где name - имя сервиса, выставленное на Доску Объявлений диалоговым сервером, data - указатель буфера с передаваемыми данными (дело в том, что во время установления связи можно передать данные сервису), len - размер буфера, flags - флаги, значение которых то же, что и для вызова tpcall(). Вызов tpconnect() возвращает целое число - дескриптор открытого канала, по которому и производится дальнейшие запись в канал и чтение из него.

int tpsend (cd, data, len, flags, revent)
int cd;
char *data;
long len;
long flags;
long *revent;

Здесь cd - дескриптор канала, data - указатель буфера данных, len - размер буфера, flags - флаги, revent - значение "события", инициированного вызовом tpsend() (что называется событием, будет разъяснено ниже). Значения флагов такие же, как и для вызова tpcall(), плюс еще два значения: TPSENDONLY - контроль над каналом остается у инициатора соединения (как в примере, приведенном ниже); TPRECVONLY - контроль над каналом и инициатива передачи данных передается субординатору.

int tprecv (cd, data, len, flags, revent)
int cd;
char *data;
long len;
long flags;
long *revent;

Где смысл параметров тот же, что и для вызова tpsend(). Параметр revent необходим для уведомления процесса-собеседника о дальнейших действиях. Так, в приведенном ниже примере в результате вызова tprecv() переменная revent может получить значение TPEV_SVCSUCC, что означает, что субординатор (в нашем случае - Сервис "DIALOG" - успешно завершил свою работу). Реакцией на это инициатора (Клиента) является завершение цикла беседы.

Диалоговый режим необходим в тех случаях, когда Клиенту срочно потребовалось "побеседовать" с конкретным Сервисом таким образом, чтобы никакие другие Клиенты не могли их прервать в этой беседе. Клиент захватывает на монопольное использование Сервис, и в течение всего сеанса диалога (от открытия канала до его закрытия) никакие другие Клиенты не могут запрашивать данный Сервис. В качестве примера (рис.1) приведем простейший случай диалогового взаимодействия, имитирующий беседу Клиента и Сервиса. Клиент последовательно задает вопросы (передавая их Сервису в строковом буфере), Сервис отвечает на них, возвращая ответ также в виде текстовой строки в том же самом буфере.

/****************************************/
/* Диалоговый режим - Клиент Приложения */
/****************************************/
#include /* UNIX */
#include /* UNIX */
#include /* Tuxedo */
#include /* Tuxedo */
#include /* Tuxedo */
#define LEN 80 /* Размер буфера ввода */
main(argc, argv)
int argc;
char *argv[];
{
int cd; /* номер канала */
char *line; /* буфер для передачи */
char reply[LEN+1]; /* буфер для ввода */
long len; /* размер буфера */
long revent; /* номер события */
Подключить Клиента к System/T
Распределить память под буфер line;
/* Открыть канал - подключиться к сервису DIALOG. Флаг TPSENDONLY */
/* означает, что инициатива передачи данных остается у Клиента и */
/* далее последует вызов tpsend() */
if ((cd = tpconnect("DIALOG", NULL, 0, TPSENDONLY)) ==- 1) {
Вызвать функцию обработки ошибок;
Освободить память, выделенную под буфер;
Терминировать связь с System/T;
Завершить программу;
}
/* Цикл получения и посылки строк */
while (1) {
(void) gets(reply); /* Получить строку */
if (strcmp(reply, "q")==0) break; /* Завершить цикл */
(void) strcpy(line, reply); /* Скопировать в буфер */
(void) printf("%sИсходная строка\n",line);
/* Послать данные в канал. Использование флага TPRECVONLY означает, */
/* что инициатива диалога передается субординатору (Сервису DIALOG) */
/* и что далее последует вызов tprecv() */
if (tpsend(cd,line,0,TPRECVONLY,&revent) ==- 1) {
Вызвать программу обработки ошибок;
Освободить память под буфер line;
Терминировать связь с System/T;
Завершить программу;
}
/* Получить данные из канала */
if (tprecv (cd, &line, &len, TPNOCHANGE, &revent) !=- 1) {
Вызвать программу обработки ошибок;
Освободить память под буфер line;
Терминировать связь с System/T;
Завершить программу;
}
(void) printf("%sРезультирующая строка\n",line);
/* Если произошла ошибка (tperrno == TPEEVENT), либо переменная */
/* revent приняла значение, отличное от TPEV_SENDONLY или */
/* TPEV_SVCSUCC, завершить выполнение программы */
if (tperrno! = TPEEVENT&&revent! = TPEV_SENDONLY&&revent!=TPEV_SVCSUCC) {
Вызвать функцию обработки ошибок;
Освободить память, выделенную под буфер;
Терминировать связь с System/T;
Завершить программу;
}
/* Завершить цикл, если диалог закончен */
if (revent==TPEV_SVCSUCC) break;
/* Продолжить цикл диалога: значение revent == TPEV_SENDONLY, */
/* следовательно, далее в цикле должен быть использован tpsend() */
}
Освободить память, выделенную под буфер;
Терминировать связь с System/T;
}
/***************************************/
/* Диалоговый режим - Сервер Приложения */
/***************************************/
#include /* UNIX */
#include /* UNIX */
#include /* Tuxedo */
#include /* Tuxedo */
#include /* Tuxedo */
#define LEN 80
void DIALOG(TPSVCINFO *svcinfo)
{
char *line; /* Буфер для передачи данных */
long len; /* Размер буфера */
long revent; /* Номер события */
Распределить память под буфер line;
while (1) {
/* Получить данные из канала; дескриптор канала - svcinfo->cd */
if(tprecv(svcinfo->cd, &line, &len, TPNOCHANGE, &revent) !=- 1 ) {
Вызвать программу обработки ошибок;
Освободить память, выделенную под буфер line;
/* Вернуть управление Клиенту с кодом ошибки TPFAIL */
(void) tpreturn( TPFAIL, 0, line, strlen( line ), 0 );
}
/* Имело ли место получение данных? Да, если tperno == TPEEVENT */
if (tperno == TPEEVENT)
/* TPEV_SENDONLY означает завершение цикла */
if ((int)revent == TPEV_SENDONLY) break;
/* TPEV_DISCONIMM означает завершение диалога */
else
if ((int)revent == TPEV_DISCONIMM) {
Освободить память, выделенную под буфер line;
(void) tpreturn(TPSUCCESS, 0, NULL, 0, 0 );
}
/* Во всех остальных случаях имеет место ошибка */
else {
Вызвать программу обработки ошибок;
Освободить память, выделенную под буфер line;
/* Завершить выполнение Сервиса с кодом ошибки TPFAIL */
(void) tpreturn (TPFAIL, 0, line, strlen(line), 0);
}
else
Обработка исходной строки в буфере и получение результирующей;
/* Если строка получена и обработана - направить обработанную */
/* строку в канал, передав контроль над каналом Клиенту */
if ( tpsend (svcinfo->cd, line, 0, TPRECVONLY, &revent) ==- 1)
if (tperrno == TPEVENT)
if ((int)revent == TPEV_DISCONIMM) {
Освободить память, выделенную под буфер line;
(void)tpreturn(TPSUCCESS,0,NULL,0,0);
}
else {
Вызвать программу обработки ошибок;
Освободить память, выделенную под буфер line;
/* Вернуть управление Клиенту с кодом ошибки TPFAIL */
(void)tpreturn (TPFAIL, 0, line, strlen(line), 0);
}
} /* Конец цикла */
}

Рисунок 1.
Пример диалогового режима.

3. Рассылка сообщений

Выше уже говорилось о возможности рассылки так называемых незатребованных сообщений. Рассмотрим пример широковещательной рассылки, которая инициируется Клиентом и направляется всем подключенным к System/T Клиентам Приложения. Рассылается текстовое сообщение (помещенное в буфер типа STRING), получаемое из командной строки при запуске клиента. На рис. 2 приведен пример инициатора и получателя сообщений (оба - Клиенты Приложения). Операция выполняется функцией tpbroadcast(), имеющей следующий синтаксис:

int broadcast (lmid, usrname, cltname, data, len, flags)
char *lmid, *usrname, *cltname, *data;
long len, flags;

Здесь lmid - идентификатор узла, usrname и cltname - опознавательные знаки Клиента-получателя сообщения. Использование NULL для всех трех аргументов означает рассылку сообщения всем клиентам, подключенным к System/T. Сообщение пересылается в буфере, указатель на который есть data, а len - размер буфера (для FML-буферов len равно 0). Значение flag, равное TPSIGRSTRT, означает, что, когда сигнал прерывает любой системный вызов, то последний повторяется.

/*******************************************/
/* Рассылка сообщений - инициатор рассылки */
/*******************************************/
#include 
#include /* Tuxedo */
#include /* Tuxedo */
#include /* Tuxedo */
main(argc, argv)
int argc;
char *argv[];
{
char *sendbuf; /* Буфер для рассылки сообщений */
long sendlen; /* Размер буфера */
if(argc != 2) {
Сообщение об ошибке: неверный формат командной строки;
Завершить программу;
}
Подключить Клиента к System/T;
sendlen = strlen(argv[1]);
/* Выделить память под буфер для рассылки сообщений */
if((sendbuf = (char *) tpalloc("STRING", NULL, sendlen+1)) == NULL) {
Вызвать функцию обработки ошибок;
Терминировать связь с System/T;
Завершить программу;
}
/* Разместить текст сообщения в буфере */
strcpy(sendbuf, argv[1]);
/* Направить широковещательное сообщение */
if( tpbroadcast( NULL, NULL, NULL, sendbuf, 0, TPSIGRSTRT ) ==- 1 ) {
Вызвать функцию обработки ошибок;
Освободить память, выделенную под буфер;
Терминировать связь с System/T;
Завершить программу;
}
Освободить память, выделенную под буфер;
Терминировать связь с System/T;
Завершить программу;
}
/***********************************/
/* Рассылка сообщений - получатель */
/***********************************/
#include 
#include /* Tuxedo */
#include /* Tuxedo */
#include /* Tuxedo */
/* Индикатор завершения обработки сообщения */
static int quit = 0;
/* Функция обработки сообщения */
void mproc (string, len, flag)
char *string,
long len,
long flag
{
printf( "Незатребованное сообщение: %s\n", string );
if( string[ 0 ] == q ) quit = 1;
return;
}
main(argc, argv)
int argc;
char *argv[];
{
Подключить Клиента к System/T;
/* Указать функцию обработки сообщения */
tpsetunsol( mproc );
/* Выполнять что-либо до завершения обработки сообщения */
while( quit != 1 );
Терминировать связь с System/T
}

Рисунок 2.
Пример рассылки сообщений.

4. Управление транзакциями

Tuxedo System полностью удовлетворяет стандарту обработки распределенных транзакций консорциума X/Open (X/Open DTP - Distributed Transaction Processing). Модель X/Open DTP (рис. 3) описывает взаимодействие трех субъектов обработки транзакций: прикладной программы (в качестве прикладной программы фигурирует как Клиент, так и Сервер Приложения), менеджера транзакций (в нашем случае - это System/T) и менеджера ресурсов, конкретно - СУБД. Роль менеджера транзакций в модели X/Open DTP - роль диспетчера, главного координатора транзакций. Он обладает полным набором управления как локальными, так и глобальными, распределенными транзакциями. В последнем случае транзакция может обновлять данные на нескольких узлах, причем управление данными на них осуществляется различными СУБД. Обработка распределенных транзакций обеспечивается за счет использования протокола двухфазовой фиксации транзакций, который гарантирует целостность данных в информационной системе, распределенной по нескольким узлам, независимо от того, какая СУБД управляет обработкой данных на каждом таком узле.

Picture 3

Рисунок 3.
Модель X/Open DTP.

Понятие транзакции в Tuxedo System и в традиционных СУБД отличаются. Суть остается одной ("все или ничего"), но в понимании СУБД транзакция - это атомарное действие над базой данных, в то время как в Tuxedo System транзакция трактуется гораздо шире. Она включает не только операции над базой данных, но и любые другие действия - передачу сообщений, запись в индексированные файлы, опрос датчиков и т.д. Это позволяет реализовать при помощи Tuxedo прикладные транзакции (бизнес - транзакции), что в СУБД, вообще говоря, сделать невозможно.

Вначале рассмотрим, как происходит взаимодействие Сервера Приложения с СУБД. Приложение может обращаться к нескольким СУБД. Взаимодействие может осуществляться в двух режимах. В первом, простейшем, режиме управление транзакциями берет на себя сама СУБД - тогда для указания границ транзакции используются SQL-операторы конкретной СУБД (BEGIN TRANSACTION, COMMIT TRANSACTION, ROLLBACK TRANSACTION). Tuxedo System не принимает никакого участия в обработке такой транзакции. Ясно также, что в этом режиме можно задать распределенную транзакцию, но только в однородной среде (на узлах, затрагиваемых транзакцией, обработку данных выполняет одна и та же СУБД, например Oracle). Для нас этот случай неинтересен, поскольку Tuxedo System используется не в полную меру и фактически устраняется от управления транзакциями.

В другом режиме управление транзакциями берет на себя Tuxedo System. Любая СУБД, с которой взаимодействует прикладная программа, рассматривается как RM. Для того чтобы можно было бы работать с СУБД в этом режиме, необходимо, чтобы это была СУБД, удовлетворяющая стандарту X/Open XA. В настоящий момент этому стандарту соответствуют Informix-OnLine Dynamic Server, Sybase, CA-OpenIngres, Oracle. В самое последнее время к этому списку присоединился Microsoft SQL Server.

Стандарт XA описывает небольшой набор вызовов, предоставляющий интерфейс к данной СУБД для Менеджеров Транзакций, а сам XA-интерфейс есть библиотека функций, поставляемая либо непосредственно с сервером конкретной СУБД (например с Oracle), либо с компонентом ESQL/C (так сделано в Informix), либо как отдельный компонент (CA-OpenIngres DTP).

В любом случае Сервер Приложения обращается к СУБД путем использования одного из диалектов ESQL/C. Ниже (рис. 4) для иллюстрации приводится текст Сервиса "Подсчет баланса" (BALANCE). Сервис получает от клиента сообщение в VIEW-буфере, извлекает из него номер филиала, обращается к СУБД с запросом на подсчет баланса для данного филиала, помещает результат в буфер и возвращает его Клиенту.

/*************************************/
/* Обращение к СУБД - Сервис BALANCE */
/**************************************/
#include /* UNIX */
#include /* UNIX */
#include "fml.h"/* Tuxedo */
#include "UBB.h" /* Tuxedo */
#include "atmi.h" /* Tuxedo */
#include "Usysflds.h" /* Tuxedo */
#include "stdmain.h" /* Tuxedo */
#include "esql.h" /* Tuxedo */
#include "bank.h" /* Application */
#include "bank.flds.h" /* FML definition */
#include "aud.h" /* VIEW definition */
EXEC SQL begin declare section;
static long branch_id; /* номер филиала */
static float bal; /* баланс */
EXEC SQL end declare section;
void BALANCE (TPSVCINFO *transb)
{
struct aud *transv; /* Данные, переданные в VIEW-буфере */
/* Установить указатель на сообщение */
transv = (struct aud *)transb->data;
/* Получить из сообщения номер филиала,выполнить запрос */
EXEC SQL declare acur cursor for
select SUM(balance) from ACCOUNT;
EXEC SQL open acur;
EXEC SQL fetch acur into :bal
if (SQLCODE != SQL_OK) {
/* Если произошла ошибка при подсчете баланса - направить */
/* в VIEW-буфер соответствующее сообщение об ошибке */
(void) strcpy (transv->ermsg, "balance failed in sql aggregation");
EXEC SQL close acur;
tpreturn (TPFAIL, 0, transb->data, sizeof(struct aud), 0);
}
else {
EXEC SQL close acur;
transv->balance = bal;
tpreturn (TPSUCCESS, 0, transb->data, sizeof(struct aud), 0);
}
}

Рисунок 4.

Пример иллюстрирует фундаментальное различие между технологией "SQL-клиент - SQL-сервер" и технологией менеджеров транзакций. В первом случае Клиент явным образом запрашивает данные, зная структуру базы данных (имеет место так называемый data shipping, то есть "поставка данных" Клиенту). Клиент передает SQL-запрос, в ответ получает данные. Имеет место жесткая связь типа "точка-точка", для реализации которой все СУБД используют закрытый SQL-канал. Он строится двумя процессами: SQL/Net на компьютере-клиенте и SQL/Net на компьютере-сервере и порождается по инициативе клиента оператором CONNECT. Канал закрыт в том смысле, что невозможно, например, написать программу, которая будет шифровать SQL-запросы по специальному алгоритму (стандартные алгоритмы шифрования, используемые, например, в Oracle SQL*Net, вряд ли будут сертифицированы ФАПСИ).

В случае трехзвенной схемы Клиент явно запрашивает Сервис, передавая ему в буфере сообщение и получает ответ также в буфере. Клиент направляет запрос в информационную шину (которую строит System/T), ничего не зная о месте расположения Сервиса. Имеет место так называемый function shipping (то есть "поставка функций" Клиенту). Важно, что для Клиента база данных закрыта слоем Сервисов. Более того, он вообще ничего не знает о ее существовании, так как все операции над базой данных выполняются внутри Сервисов. В Заключении мы сравним оба подхода и попытаемся понять, где их лучше всего использовать.

Для управления транзакциями используются вызовы tpbegin(), tpcommit(), tpabort(). Начало транзакции отмечается вызовом tpbegin(). Получив его, System/T добавляет в таблицы транзакций информацию о данной транзакции, и связывает с ней все дальнейшие действия (действия после tpbegin()), инициируемые прикладной программой. То есть любые операции над базой данных, предпринятые после вызова tpbegin(), осуществляются в рамках данной транзакции (одной из многих, одновременно контролируемых System/T). Транзакция может завершиться одним из двух способов. Использование tpcommit() влечет за собой фиксацию всех изменений, достигнутых в рамках текущей транзакции, то есть изменений в базе данных, полученных в результате выполнения операторов SQL, которые следовали после tpbegin(). Анатомия tpcommit() такова. Функция tpcommit() обращается к System/T. System/T издает вызов XA-функции, ответственной за фиксацию транзакций, - таким образом, System/T заставляет СУБД зафиксировать транзакцию, после чего удаляет информацию о данной транзакции из своих внутренних таблиц. Напротив, вызов tpabort() приводит к откату всех изменений, сделанных в рамках текущей транзакции. Итак, если транзакция локальная, то вызовы tpbegin(), tpcommit(), tpabort() попросту заменяют соответствующие операторы SQL BEGIN TRANSACTION, COMMIT TRANSACTION, ROLLBACK TRANSACTION или их аналоги.

На самом деле функция tpcommit() устроена несколько сложнее. Дело в том, что вызов tpcommit() инициирует двухфазный протокол фиксации текущей транзакции. Первая фаза состоит в том, что все RM (читай СУБД) опрашиваются о готовности зафиксировать транзакцию (изменения в базы данных уже внесены, но еще не зафиксированы). Получив от всех RM отклик о готовности, System/T инициирует фиксацию, направляя всем им соответствующую команду через XA-интерфейс. Если в момент выполнения первой фазы кто-либо из RM не откликнулся, System/T повторяет опрос, и в случае неудачи направляет RM-команду на откат изменений.

System/T ведет собственный журнал транзакций, куда отображает всю информацию о транзакциях, находящуюся в ее собственных таблицах. Транзакционные таблицы, кстати, размещаются на Доске Объявлений. После любого серьезного сбоя, разрушившего внутренние структуры System/T (или вообще приведшего к очистке оперативной памяти компьютера) при загрузке Приложения, вся информация о транзакциях восстанавливается на Доске Объявлений из журнала транзакций, и, используя ее, System/T откатывает все незавершенные на момент сбоя транзакции.

Общая схема управления распределенными транзакциями выглядит следующим образом. Во-первых, разработчик приложения указывает в Конфигурационном файле в секции *GROUPS RM, которые будут принимать участие в транзакции. Для этого используется параметр OPENINFO. В строке OPENINFO указывается информация, необходимая для инициации RM: тип менеджера ресурсов, расположение и имя базы данных, режим работы с базой данных - только чтение или чтение и запись. Связь между System/T и RM обеспечивает так называемый сервер управления транзакциями (Transaction Management Server, TMS). Имя TMS указывается параметром TMSNAME секции *GROUPS, а само имя совпадает с именем файла, который порождается утилитой buildtms(). В момент загрузки Приложения в числе прочих запускается процесс TMS, который и инициирует подключение Приложения к RM, для которого этот TMS был построен. Для каждого RM запускается свой TMS, который и управляет транзакциями, направляемыми к данному Менеджеру Ресурсов.

Обычно транзакция определяется в Клиенте Приложения и содержит вызов нескольких Сервисов, которые и обращаются к базе данных. Схема определения распределенной транзакции представлена на рис. 5. При этом Сервис SRV1 содержит Informix-ESQL/C-операторы и обращается с Informix-OnLine Dynamic Server, в то время как операции с данными Сервиса SRV2 определены операторами Oracle Pro*C и манипулируют с базой данных Oracle.

/*************************************************/
/* Распределенная транзакция - Клиент Приложения */
/*************************************************/
....
tpbegin();
if ( tpcall(¦SRV1¦,...) ==- 1 ) {tpabort();
Вызвать программу обработки ошибок;
Завершить выполнение программы;
}
if ( tpcall(¦SRV2¦,...) ==- 1 ) {
/* Откат всех изменений, достигнутых на данный момент */
tpabort();
Вызвать программу обработки ошибок;
Завершить выполнение программы;
}
...
/* Здесь фиксируются все изменения в базах */
/* данных, выполненные в сервисах SRV1 и SRV2 */
tpcommit();
....
}

Рисунок 5.

2. Основные возможности Tuxedo System

Tuxedo - многоплановый продукт. Помимо разнообразных режимов взаимодействия "клиент-сервер" в нем существует еще множество возможностей для создания распределенных Приложений. Ниже мы рассмотрим некоторые из этих возможностей.

2.1. Баланс загрузки

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

Для иллюстрации механизма баланса загрузки рассмотрим пример. Пусть Сервер Приложения составляют три процесса - S1 (SRVA, SRVB, SRVC), S2 (SRVA, SRVB, SRVC), S3 (SRVB, SRVC) (в скобках указаны Сервисы, предоставляемые каждым процессом). Каждый Сервис имеет некоторый показатель загрузки, отражающий объем вычислительных ресурсов, необходимых для его выполнения (для SRVA он составляет 50, для SRVB - 60, для SRVC - 70). Каждый процесс в составе Сервера Приложения также имеет показатель его загрузки; каждый раз, когда запрос передается на обработку данному процессу, показатель загрузки процесса увеличивается на показатель загрузки Сервиса, которому этот запрос направлен.

Клиент Приложения формирует запрос и направляет его Серверу Приложения. Tuxedo помещает запрос в очередь запросов, единую для всех трех процессов. Tuxedo извлекает очередной запрос и передает его на исполнение тому из трех процессов, который содержит адресуемый в запросе Сервис. Если два и более процесса содержат один и тот же Сервис, то Tuxedo направляет запрос тому процессу, у которого показатель загрузки меньше (рис. 6). Таким образом происходит автоматическое динамическое выравнивание загрузки процессов. Отметим, что в нашем примере процесс S2 идентичен процессу S1 - он предоставляет те же самые сервисы, что и процесс S1; процесс S2 запущен с целью разгрузки процесса S1; вообще говоря, копий процесса S1 может быть запущено несколько. Цель - увеличить пропускную способность Сервера Приложения, максимально разгрузив очередь запросов к нему. Количество одновременно запущенных копий процесса указывается в Конфигурационном Файле Приложения и может быть увеличено (или уменьшено) администратором системы без его остановки.

Picture 6

Рисунок 6.
Баланс уровня загрузки

Начальные значения показателей загрузки Сервисов устанавливаются в Конфигурационном Файле в секции *SERVICES с помощью параметра LOAD. Для нашего примера эта секция может выглядеть так (рис. 7). По умолчанию для всех Сервисов установлен приоритет, равный 30 (PRIO=30). Параметр ROUTING будет объяснен в следующем разделе.

*SERVICES
DEFAULT: PRIO=30 ROUTING=ACCOUNT_ID
SRVA LOAD=50
SRVB LOAD=60
SRVC LOAD=70

Рисунок 7.
Показатели загрузки Сервисов.

Баланс загрузки позволяет повышать производительность и пропускную способность Системы чисто административными средствами, не переписывая коды приложений. Смысл состоит в том, что все Сервисы наделяются некими весовыми коэффициентами (показателями загрузки), полученными опытным путем. В комбинации с приоритетами Сервисов это составляет эффективный механизм быстрого обслуживания очередей запросов, что особенно важно в системах с большим количеством клиентов и интенсивным потоком запросов.

2.2. Маршрутизация запросов

В системах с распределенными базами данных возникает задача автоматической маршрутизации запроса к базе данных, содержащей необходимую порцию информации. Tuxedo System поддерживает эффективный механизм, называемый data routing (буквально переводится "маршрутизация данных", но, по сути, это именно маршрутизация запросов).

Предположим, что имеется распределенная банковская система, хранящая и обрабатывающая, помимо прочего, некоторые счета клиентов. Информация о счетах хранится в локальных базах данных, рассредоточенных по нескольким городам, где и живут клиенты. При этом информация о счетах с номерами 1-10000 хранится в базе данных в Москве, с номерами 10001-20000 - в Санкт-Петербурге, с номерами 20001-30000 - в Нижнем Новгороде и т.д. В этих городах находятся подразделения банка, идентифицируемые номером (Санкт-Петербург - 2, Москва - 3, Нижний Новгород - 5 и т.д.).

Допустим, необходимо получать информацию о состоянии счета клиента, для чего реализована некоторая программа - Клиент Приложения. Она издает запрос tpcall() - вызов Сервиса ACCOUNT, который и поставляет необходимую информацию. Данные передаются и возвращаются в FML-буфере, одним из полей которого является поле ACCOUNT_ID целого типа (номер счета). Сервис ACCOUNT предоставляется несколькими серверами (по одному в группе), каждый из которых выполняется на компьютере, расположенном в одном из городов. Получив запрос tpcall ("ACCOUNT", &buf,...), System/T, пользуясь информацией о маршрутизации запроса, расположенной на Доске Объявлений, и, исходя из значения поля ACCOUNT_ID FML-буфера, направляет запрос на соответствующий компьютер, к базе данных, в которой хранится информация о счете с искомым номером (рис. 8).

Picture 8

Рисунок 8.
Маршрутеризация запросов в распределенной системе.

Запрос может быть направлен и другим Сервисам (OPEN, CLOSE, WITHDRAW, DEPOSIT, INQUIRY), также предоставляемым серверами на компьютерах, которые находятся в различных городах. Маршрутизация запросов к ним выполняется также по значению поля BRANCH_ID FML-буфера. Например, в запросе tpcall ("OPEN", &buf, ...) на рис. 8. FML-буфер buf содержит поле BRANCH_ID (номер филиала банка), в соответствии со значением которого System/T направляет запрос тому или иному филиалу.

Для того чтобы обеспечить маршрутизацию запросов в нашем примере, необходимо подготовить Конфигурационный файл вида (рис. 9). В секции *MACHINES описаны компьютеры, на которых выполняются Серверы Приложения (comp1, comp2 и т.д.). В секции *GROUPS представлены группы процессов - Серверов (каждая группа выполняется на отдельном компьютере). На каждом таком компьютере работает СУБД (для групп BANK1, BANK3 - это Oracle, для BANK2 - Informix и т.д.) и хранится база данных с информацией о счетах клиентов. Наконец, в секции *SERVERS описан Сервер ACCNT, содержащий Сервисы ACCOUNT, OPEN, CLOSE, INQUIRY, WITHDRAW, DEPOSIT (по сути, этот один и тот же процесс, выполняющийся на компьютерах comp1, comp2, comp3). Секция *SERVICES хранит описание (помимо прочих) сервиса ACCOUNT с параметрами: показатель загрузки (LOAD=50), приоритета Сервиса (PRIO=60) и параметр маршрутизации - имя поля в FML-буфере запроса, по значению которого производится маршрутизация (ACCOUNT_ID). Сами же параметры маршрутизации запроса описываются в секции *ROUTING, где для каждого параметра указывается поле буфера и тип буфера (как правило - FML, хотя может быть и VIEW), а также диапазон значений поля и группа серверов, к которой будет направлен запрос (ключевое поле RANGES).

...
# Компьютеры, на которых выполняются серверы ACCOUNT
*MACHINES
comp1 LMID=SITE1
...
comp2 LMID=SITE2
...
comp3 LMID=SITE3
# Группы процессов - серверов и связанные и с ними RM
# группы BANK1, BANK3 - Oracle, группа BANK2 - Informix
*GROUPS
BANKB1 LMID=SITE1 GRPNO=1 TMSNAME=TMSORA
OPENINFO="..."
BANKB2 LMID=SITE2 GRPNO=2 TMSNAME=TMSINF
OPENINFO="..."
BANKB3 LMID=SITE3 GRPNO=3 TMSNAME=TMSORC
OPENINFO="..."
# Описание Серверов
*SERVERS
ACCNT SRVGRP=BANK1 SRVID=1
ACCNT SRVGRP=BANK2 SRVID=2
ACCNT SRVGRP=BANK3 SRVID=3
# Описание Сервисов
*SERVICES
DEFAULT: LOAD=50
ACCOUNT PRIO=60 ROUTING=ACCOUNT_ID
OPEN PRIO=50 ROUTING=BRANCH_ID
CLOSE PRIO=40 ROUTING=BRANCH_ID
INQUIRY PRIO=50 ROUTING=ACCOUNT_ID
WITHDRAW PRIO=60 ROUTING=ACCOUNT_ID
DEPOSIT PRIO=50 ROUTING=ACCOUNT_ID
# Описание параметров маршрутизации запросов
*ROUTING
ACCOUNT_ID FIELD=ACCOUNT_ID
BUFTYPE="FML"
RANGES="1-10000:BANK1,
10001-20000:BANK2,
20001-30000:BANK3,
*:*"
BRANCH_ID FIELD=BRANCH_ID
BUFTYPE="FML"
RANGES="3:BANK1,
2:BANK2,
5:BANK3,
*:*"

Рисунок 9.
Фрагмент Конфигурационного файла для маршрутизации запросов.

Преимущество такого подхода к маршрутизации запросов заключается в том, что искомая конфигурирования Системы происходит без переписывания исходных кодов и достигается чисто административными действиями - внесением изменений в Конфигурационный файл. Более того, при разработке Приложения программист вообще не знает, будет ли база данных и приложение распределенными или централизованными.

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

2.3. Динамическое распределение загрузки

Выше (п. 2.1.) шла речь о балансе загрузки процессов, выполняющихся на одном компьютере. Однако эффективный динамический баланс загрузки также возможен и может быть использован для рационального распределения запросов на обработку данных в кластерных архитектурах.

Рассмотрим следующую задачу. Пусть имеется Sun SPARCcluster PDB из двух узлов на базе компьютеров SPARCcenter 2000E с СУБД Oracle 7.x (Oracle Parallel Server), который используется в системе в трехзвенной архитектурой в качестве Сервера Баз Данных. Также имеется сервер Sun SPARCserver 1000E, связанный с кластером высокоскоростным каналом связи и используемый в качестве компьютера-сервера приложения. Приложение работает под управлением Tuxedo System/T. В качестве рабочих мест конечных пользователей используются персональные компьютеры под управлением MS Windows с компонентом Tuxedo System/WS, на которых выполняются программы-Клиенты Приложения. Необходимо разработать программный механизм рациональной загрузки ОБОИХ узлов кластера, позволяющий динамически перенаправлять SQL-запросы тому или иному узлу в зависимости от его загруженности. То есть если один из узлов кластера загружен на 90%, а другой - только на 10%, то необходимо выравнять загрузку, перенаправив часть SQL-запросов с первого узла на второй.

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

2.4. Мультиплексирование запросов к базам данных

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

Принципы обращения Клиентов Приложения к базам данных в системах, опирающихся на менеджеры транзакций, совершенно иной, чем в двухзвенных системах "SQL-клиент-SQL-сервер". В последних существует понятие одновременных подключений к базе данных (concurrent connection). Процесс клиента считается подключенным к СУБД, начиная с момента открытия сеанса с базой данных (инициируется оператором CONNECT) и заканчивая ее закрытием. Отметим, что операция CONNECT является крайне ресурсоемкой, выполняется медленно и не рекомендуется для частого использования. В течение сеанса СУБД считает клиента активным и вынуждена хранить контекст его подключения даже в том случае, если клиент вообще не направляет запросов СУБД, а выполняет свои внутренние функции либо просто ждет ввода от пользователя (который, быть может, ушел пообедать). Таким образом, парадигма взаимодействия Клиента и Сервера Базы Данных выглядит так: Клиент и Сервер открывают SQL-канал на весь период работы Клиента (инициирует открытие канала Клиент) и обмениваются по нему SQL-запросами и данными в синхронном режиме (другие режимы, аналогичные упомянутым в разделе 6, исключаются). В лицензионном соглашении на использование СУБД под количеством пользователей (от которого, собственно, и зависит стоимость лицензии) подразумевается число одновременно подключенных к СУБД клиентских процессов. Возможны вариации (для различных СУБД лицензионное соглашение выглядит по-разному), но суть остается одной.

Совершенно иначе выполняется подключение клиентов в системах с трехзвенной архитектурой. Во-первых, Клиент вообще не обращается к базе данных - он ее "не видит" и ничего не подозревает о ее существовании. Клиент обращается с запросами к Серверу Приложений в одном из режимов, описанных выше. Взаимодействие Клиента и Сервера Приложения контролирует Tuxedo System/T. Подключение к System/T Клиента (начало сеанса) выполняется вызовом tpinit(); терминирование связи (завершение сеанса) производится вызовом tpterm().

Подключение к СУБД выполняется Сервером Приложений, а взаимодействие с базой данных (выборка, обновление) производится в рамках Сервисов. Схема работы выглядит следующим образом. Клиент Приложения подключается к Tuxedo System/T, формирует исходные данные в буфере и вызывает Сервис, передавая ему данные в буфере. Сервис, в свою очередь, извлекает данные из буфера и формирует SQL-запрос, который направляет соответствующему Менеджеру Ресурсов (СУБД). Получив от него в ответ данные, Сервис размещает их в результирующем буфере и направляет его Клиенту. Клиент после этого не обязан сохранять подключение - он может завершить сеанс работы, отключившись от System/T и дав возможность подключиться к System/T другому Клиенту. Главное заключается в том, что единственные исполнители прикладных действий - Сервисы - не монополизированы каким-либо Клиентом, а доступны для общего использования всеми Клиентами. Скорость обработки запросов Клиентов к Сервисам определяется скоростью обработки System/T очередей запросов; увеличить ее можно при помощи механизмов приоритетов и баланса загрузки, а также увеличением числа копий процессов, предоставляющих данный Сервис (то есть можно свободно наращивать пропускную способность системы).

Таким образом, для систем с трехзвенной архитектурой, работающих под управлением Tuxedo, не существует ограничений на количество Клиентов. Это и является причиной, объясняющей применение менеджеров транзакций (и Tuxedo в том числе) для систем, характеризуемых сверхбольшим числом клиентов (от сотен до тысяч).

Во-вторых, в системах с трехзвенной архитектурой резко сокращается число одновременных подключений к СУБД (что существенно уменьшает стоимость лицензии на ее использование). Как мы видим, Клиенты к СУБД не подключаются. Подключение к СУБД производится процессом Сервером Приложения (от каждого процесса по одному подключению). Каждый такой процесс обрабатывает N запросов, поступающих от Клиентов. Следовательно, количество подключений к СУБД определяется общим числом процессов, включенных в Приложение, и никак не зависит от количества Клиентов. Фактические данные, приведенные, например, в [5], свидетельствуют о том, что количество подключений к СУБД в системах с трехзвенной архитектурой по сравнению с традиционной технологией применения СУБД может быть уменьшено минимум в 4, максимум - в 16 раз, а выигрыш в стоимости от приобретения пары "менеджер транзакций - СУБД" по сравнению с приобретением только СУБД (как это не кажется парадоксальным) может достигать 50%.

Заключение

Мы описали лишь небольшую часть возможностей Tuxedo System. Как мы видим, изложенные идеи и подходы настолько интересны, что выходят за рамки конкретного продукта и представляют собой особый метод разработки информационных систем.

Сравним два подхода. В первом случае мы имеем жесткую схему связи "точка-точка" с передачей открытых SQL-запросов и данных, исключающую возможность модификации и работающую только в синхронном режиме "запрос - ответ". Во втором случае определен гибкий механизм передачи сообщений между Клиентами и Сервисами, позволяющий шифровать сообщения и организовывать взаимодействие между Клиентом и Сервисами многочисленными способами, описанными выше.

Таким образом, речь идет о двух принципиально разных подходах к построению информационных систем "клиент-сервер". Первый из них устарел и явно уходит в прошлое. Дело в том, что SQL (ставший фактическим стандартом общения с реляционными СУБД) был задуман и реализован как декларативный язык запросов, но отнюдь не как средство взаимодействия "клиент-сервер" (об этой технологии тогда речи не было). Только потом он был "притянут за уши" разработчиками СУБД в качестве такого средства. На волне успеха реляционных СУБД в последние годы появилось множество систем быстрой разработки приложений для реляционных баз данных (VisualBasic, PowerBuilder, SQL Windows, JAM и т.д.). Все они опирались на принцип генерации кода приложения на основе связывания элементов интерфейса с пользователем (форм, меню и т.д.) с таблицами баз данных. И если для быстрого создания несложных приложений с небольшим числом пользователей этот метод подходит как нельзя лучше, то для создания корпоративных распределенных информационных систем он абсолютно непригоден.

Для этих задач необходимо применение существенно более гибких систем класса middleware (Tuxedo System, Teknekron, ..., ..., и т.д.).

Благодарности

Выражаю искреннюю признательность Владимиру Елисееву, Олегу Москаленко, Владимиру Галатенко, Сергею Севрюгину и Александру Таранову за полезные консультации, благодаря которым и увидела свет эта работа.

Литература

1. Ладыженский Г.М. Система обработки распределенных транзакций Tuxedo. - "Открытые Системы", весна 1993 года.

2. Ладыженский Г.М. Технология "клиент-сервер" и мониторы транзакций. - "Открытые Системы", лето 1994 года.

3. Опыт разработки автоматизированной банковской системы в АКБ РПБ - "Jet Info", # 6, 1995.

4. Ладыженский Г.М. Системы управления базами данных - коротко о главном. Часть 4. - "СУБД" # 4, 1995.

5. The Standish Group. "Client-Server Goes Business Critical. Transaction Processing Monitors. Key to Next Generation of Applications".


Глеб Михайлович Ладыженский,
Oracle CIS, (+7 095) 258-41-80 , glodigen@ru.oracle.com