Использование клиентской библиотеки восстановления

Рассматриваются стандартные практики использования клиентской библиотеки обеспечения высокой готовности приложений

Оглавление:

Общие сведения
Функции семейства MsgSend()
Другие функции-обертки и вспомогательные функции
Функции управления отказоустойчивыми подключениями
Обертки для функций ввода/вывода
Вспомогательные функции
Простой пример
Пример восстановления состояния

Общие сведения

Библиотека восстановления клиентов включает в себя расширенные версии многих стандартных операций ввода/вывода библиотеки libc. Функции-обертки это библиотеки позволяют создавать механизмы автоматического восстановления нарушенных подключений.

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

Основная идея библиотеки высокой готовности заключается в замене всех функций передачи данных (таких как MsgSend*()). API позволяет клиенту создавать отказоустойчивые подключения; все остальные подключения работают в стандартном режиме.

Если в обычной системе происходит сбой сервера либо сеть работает нестабильно, функции MsgSend*() возвращают ошибку EBADF, которая указывает на то, что идентификатор подключения или дескриптор файла устарел или недействителен.

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

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

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

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

Функции семейства MsgSend()

Если подключение к серверу становится неактивным или закрывается (например, при завершении сервера), обычные функции MsgSend*() возвращают ошибку EBADF или ESRCH. В системах высокой готовности работоспособность серверов часто восстанавливается почти моментально (например, посредством перезапуска). Иногда можно не прерывать передачу сообщения и не возвращать ошибку, а восстанавливать подключение и продолжать передачу.

Такой механизм реализован в функциях библиотеки высокой готовности, которые являются «обертками» для всех разновидностей функций MsgSend*(). Если при выполнении функции MsgSend*() происходит ошибка, вызывается функция восстановления, указанная клиентом. Она может попытаться заново установить соединение, а затем вернуть управление функции MsgSend*() библиотеки высокой готовности. Если идентификатор подключения, который возвращает функция восстановления, совпадает с прежним идентификатором подключения (как правило, этого легко достичь с помощью последовательности операций close()/open()/dup2()), функции MsgSend*() могут попытаться повторно передать данные.

Если функция MsgSend*() возвращает ошибку, которая отличается от EBADF и ESRCH, эта ошибка передается клиенту. Следует иметь в виду, что если подключение не является отказоустойчивым, либо клиент не указал функцию восстановления, либо она не может повторно получить идентификатор подключения, можно возвращать ошибку клиенту для дальнейшей обработки.

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

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

Другие функции-обертки и вспомогательные функции

Помимо функций-оболочек стандартных вызовов MsgSend*(), библиотека высокой готовности предоставляет клиентам две функции, которые позволяют обозначать подключение как отказоустойчивое и удалять это обозначение:

Функции управления отказоустойчивыми подключениями

ha_attach()
Связывает функцию восстановления с подключением и объявляет его отказоустойчивым.
ha_detach()
Удаляет созданную ранее связь между функцией восстановления и подключением. Подключение перестает работать в отказоустойчивом режиме.
ha_connection_ctrl()
Контролирует работу отказоустойчивого подключения.

Обертки для функций ввода/вывода

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

ha_open()
ha_open64()
Открытие подключения и его присоединение к библиотеке высокой готовности. Эти функции вызывают стандартную функцию open(), а также автоматически объявляют подключения отказоустойчивыми с помощью функции ha_attach(). Таким образом, использование этих вызовов эквивалентно вызову open() или open64() с последующим вызовом ha_attach().
ha_creat()
ha_creat64()
Создание подключения и его присоединение к библиотеке высокой готовности. Эти функции вызывают стандартную функцию creat(), а также автоматически объявляют подключения отказоустойчивыми с помощью функции ha_attach(). Таким образом, использование этих вызовов эквивалентно вызову creat() или creat64() с последующим вызовом ha_attach().
ha_ConnectAttach()
ha_ConnectAttach_r()
Создание подключения с помощью функции ConnectAttach() и его присоединение к библиотеке высокой готовности. Эти функции вызывают стандартную функцию ConnectAttach(), а также автоматически объявляют подключения отказоустойчивыми с помощью функции ha_attach(). Таким образом, использование этих вызовов эквивалентно вызову ConnectAttach() или ConnectAttach_r() с последующим вызовом ha_attach().
ha_ConnectDetach()
ha_ConnectDetach_r()
Отсоединение присоединенного fd с последующим закрытием подключения при помощи функции ConnectDetach(). Эти функции вызывают стандартную функцию ConnectDetach(), а также автоматически объявляют подключения отказоустойчивыми с помощью функции ha_attach(). Таким образом, использование этих вызовов эквивалентно вызову ConnectDetach() или ConnectDetach_r() с последующим вызовом ha_attach().
ha_fopen()
Открытие файлового потока и его присоединение к библиотеке высокой готовности. Эта функция вызывает стандартную функцию fopen(), а также автоматически объявляет подключения отказоустойчивыми с помощью функции ha_attach(). Таким образом, использование этого вызова эквивалентно вызову fopen() с последующим вызовом ha_attach().
ha_fclose()
Отсоединение присоединенного отказоустойчивого дескриптора fd файлового потока и его закрытие. Эта функция вызывает стандартную функцию fclose(), а также автоматически объявляет подключения отказоустойчивыми с помощью функции ha_attach(). Таким образом, использование этого вызова эквивалентно вызову fclose() с последующим вызовом ha_attach().
ha_close()
Отсоединяет подсоединенный отказоустойчивый файловый дескриптор fd с последующим закрытием. Эта функция вызывает стандартную функцию close(), а также автоматически объявляет подключения отказоустойчивыми с помощью функции ha_attach(). Таким образом, использование этого вызова эквивалентно вызову close() с последующим вызовом ha_attach().
ha_dup()
Дублирование отказоустойчивого подключения. Эта функция вызывает стандартную функцию dup(), а также автоматически объявляет подключения отказоустойчивыми с помощью функции ha_attach(). Таким образом, использование этого вызова эквивалентно вызову dup() с последующим вызовом ha_attach().

Вспомогательные функции

Помимо функций-оболочек, библиотека также включает в себя две вспомогательные функции, которые повторно открывают подключения для восстановления:

ha_reopen()
Повторное открытие подключения при восстановлении.
ha_ReConnectAttach()
Повторное открытие подключения при восстановлении.


Note: Описания всех функций библиотеки высокой готовности см. на странице Менеджер высокой готовности (HAM) настоящего руководства.

Простой пример

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

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

Тем не менее, поскольку это подключение является отказоустойчивым и к нему привязана функция восстановления, подключение может возобновить работу.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <ha/cover.h>
#define SERVER "/path/to/server"
typedef struct handle {
int nr;
} Handle;
int recover_conn2( int oldfd, void *hdl )
{
int newfd;
Handle *thdl;
thdl = (Handle *)hdl;
printf( "recovering for fd %d inside function 2\n", oldfd );
/* повторное открытие подключения */
newfd = ha_reopen( oldfd, SERVER, O_RDONLY );
/* выполнение других действий по восстановлению состояния */
(thdl->nr)++;
return (newfd);
}
int recover_conn( int oldfd, void *hdl )
{
int newfd;
Handle *thdl;
thdl = (Handle *)hdl;
printf( "recovering for fd %d inside function\n", oldfd );
/* повторное открытие подключения */
newfd = ha_reopen( oldfd, SERVER, O_RDONLY );
/* выполнение других действий по восстановлению состояния */
(thdl->nr)++;
return (newfd);
}
int main( int argc, char *argv[] )
{
int status;
int fd;
int fd2;
int fd3;
Handle hdl;
char buf[80];
int i;
hdl.nr = 0;
/* открытие отказоустойчивого подключения */
fd = ha_open( SERVER, O_RDONLY, recover_conn, (void *)&hdl, 0 );
if ( fd < 0 )
{
printf( "could not open %s\n", SERVER );
exit( -1 );
}
printf( "fd = %d\n", fd );
/* Дублирование файлового дескриптора. Копия также является отказоустойчивой */
fd2 = ha_dup( fd );
printf( "dup-ped fd2 = %d\n", fd2 );
printf( "before sleeping first time\n" );
/* Переход в неактивное состояние... В этот короткий период времени сервер может
* завершиться и перезапуститься */
sleep( 15 );
/* чтение из копии файлового дескриптора должно выполняться без ошибок, если сервер
* не завершился. Если сервер завершился и перезапустился, при первой операции чтения
* произойдет ошибка, которая приведет к вызову функции восстановления, повторному
* подключению, восстановлению текущей позиции в файле и повторной попытке чтения,
* которая должна завершиться успешно */
printf( "trying to read from %s using fd %d\n", SERVER, fd2 );
status = read( fd2, buf, 30 );
if ( status < 0 )
printf( "error: %s\n", strerror( errno ) );
/* файловые дескрипторы fd и fd2 одинаковы замена функции восстановления для fd2. С этого
* момента восстановление всегда будет выполняться с помощью функции recover_conn2() */
status = ha_attach( fd2, recover_conn2, (void *)&hdl, HAREPLACERECOVERYFN );
ha_close( fd ); /* закрытие файлового дескриптора */
/* открытие нового подключения */
fd = open( SERVER, O_RDONLY );
printf( "New fd = %d\n", fd );
/* создание отказоустойчивого подключения */
status = ha_attach( fd, recover_conn, (void *)&hdl, 0 );
printf( "before sleeping again\n" );
/* повторное копирование */
fd3 = ha_dup( fd );
/* переход в неактивное состояние... во время которого на сервере может произойти сбой. */
sleep( 15 );
/* уничтожение одного из файловых дескрипторов у нас осталась его копия в файловом
* дескрипторе fd3, с которым связаны функции восстановления */
ha_close( fd );
printf( "trying to read from %s using fd %d\n", SERVER, fd3 );
/* если попытка окажется неудачной, эта функция выполнит обратный вызов функции
* восстановления recover_conn() */
status = read( fd3, buf, 30 );
if ( status < 0 )
printf( "error: %s\n", strerror( errno ) );
printf( "trying to read from %s once more using fd %d\n", SERVER, fd2 );
/* если возникает ошибка, восстановление выполняет вторая функция recover_conn2(),
* поскольку мы заменили функцию восстановления для файлового дескриптора fd2 */
status = read( fd2, buf, 30 );
if ( status < 0 )
printf( "error: %s\n", strerror( errno ) );
/* закрытие файлового дескриптора fd2 и отсоединение от библиотеки высокой готовности */
ha_close( fd2 );
/* печать собранной локальной статистики */
printf( "total recoveries, %d\n", hdl.nr );
exit( 0 );
}

Пример восстановления состояния

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

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

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <ha/cover.h>
#define REMOTEFILE "/path/to/remote/file"
typedef struct handle {
int nr;
int curr_offset;
} Handle;
int recover_conn( int oldfd, void *hdl )
{
int newfd;
int newfd2;
Handle *thdl;
thdl = (Handle *)hdl;
printf( "recovering for fd %d inside function\n", oldfd );
/* повторное открытие файла */
newfd = ha_reopen( oldfd, REMOTEFILE, O_RDONLY );
/* восстановление состояния путем установки файлового указателя в правильное положение */
if ( newfd >= 0 )
lseek( newfd, thdl->curr_offset, SEEK_SET );
(thdl->nr)++;
return (newfd);
}
int main( int argc, char *argv[] )
{
int status;
int fd;
int fd2;
int fd3;
Handle hdl;
char buf[80];
int i;
hdl.nr = 0;
hdl.curr_offset = 0;
/* открытие подключения */
fd = ha_open( REMOTEFILE, O_RDONLY, recover_conn, (void *)&hdl, 0 );
if ( fd < 0 )
{
printf( "could not open file\n" );
exit( -1 );
}
fd2 = open( REMOTEFILE, O_RDONLY );
printf( "trying to read from file using fd %d\n", fd );
printf( "before sleeping first time\n" );
status = read( fd, buf, 15 );
if ( status < 0 )
printf( "error: %s\n", strerror( errno ) );
else {
for ( i = 0; i < status; i++ )
printf( "%c", buf[i] );
printf( "\n" );
/* обновление состояния подключения этот метод похож на механизм контрольных точек
* мы запоминаем состояние, чтобы облегчить работу функций восстановления */
hdl.curr_offset += status;
}
fd3 = ha_dup( fd );
sleep( 18 );
/* бездействие произвольной длительности в это время могут выполняться другие вычисления
* или операции, которые приводят к блокировке при выполнении этих действий возможен сбой
* на сервере */
/* чтение из копии файлового дейскриптора */
printf( "trying to read from file using fd %d\n", fd );
printf( "after sleeping\n" );
/* если сначала при чтении происходит ошибка, выполняется восстановление с повторным
* открытием файла и переходом в правильную позицию! */
status = read( fd, buf, 15 );
if ( status < 0 )
printf( "error: %s\n", strerror( errno ) );
else {
for ( i = 0; i < status; i++ )
printf( "%c", buf[i] );
printf( "\n" );
hdl.curr_offset += status;
}
printf( "trying to read from file using fd %d\n", fd2 );
/* повторная попытка с использованием копии. при ошибке снова выполняется восстановление
* с автоматическим повторным подключением, установкой файлового указателя и др. */
status = read( fd2, buf, 15 );
if ( status < 0 )
printf( "error: %s\n", strerror( errno ) );
else {
for ( i = 0; i < status; i++ )
printf( "%c", buf[i] );
printf( "\n" );
}
printf( "total recoveries, %d\n", hdl.nr );
ha_close( fd );
close( fd2 );
exit( 0 );
}




Предыдущий раздел: Менеджер высокой готовности (HAM)