第五章 客户端恢复库的使用
1. 引言介绍
客户端恢复库为许多标准的libc I/O操作提供了一个完全增强的解决方案。HA库的覆盖函数为失败的连接提供了自动恢复机制,可以在HA场景中恢复这些连接。
其目标是为高可用性I/O提供一个API,该API可以透明地为客户端提供恢复,特别是在服务器也必须具有高可用性的环境中。恢复是可配置的,以适应特定的客户需求;我们提供了开发更复杂的恢复机制的方法示例。
HA库的主要原则是为所有“传输”函数(例如MsgSend*())提供完全替换。该API允许客户端选择它希望提供的高可用性的特定连接——所有其他连接都将作为普通连接进行操作。
通常,当客户端正在与之通信的服务器发生故障,或者出现临时网络故障时,MsgSend*()函数会返回一个错误,指示连接ID(或文件描述符)失效或无效(EBADF)。
在支持HA的场景中,这些临时故障通常几乎是立即(在服务器端)恢复的,从而使服务再次可用。不幸的是,使用标准I/O产品的客户端可能无法最大限度地受益于此,除非他们提供从这些错误中恢复的机制,然后重新传输信息/数据,这通常可能涉及到客户端程序的nontrivial rework。
通过在HA库内部提供/实现恢复,我们可以自动利用HA感知的服务,这些服务可以自己重新启动或自动重新启动,也可以利用以透明集群/冗余方式提供的服务。
由于恢复本身是一个特定于连接的任务,所以我们允许客户端提供恢复机制,以便在连接失败时恢复它们。不可恢复的错误被可靠地传播回来,因此任何不希望恢复的客户机都将获得它所期望的I/O库语义。
恢复机制可以是任何内容,从简单的重新打开连接到更复杂的场景(包括重新传输/重新协商特定于连接的信息)。
2. MsgSend()函数
通常,MsgSend*()函数在服务器端连接失效或关闭时返回EBADF/ESRCH(例如,因为服务器死亡)。在许多情况下,服务器本身会返回(例如重新启动),并几乎立即开始正确地提供服务(在HA场景中)。在某些情况下,可以执行恢复并继续消息传输,而不是仅仅使用错误终止消息传输。
“覆盖”所有MsgSend()变体的HA库函数正是为此而设计的。当某个特定的MsgSend()函数调用失败时,将调用客户端提供的恢复函数。此恢复函数可以尝试重新建立连接并将控制返回到HA库的MsgSend()函数。只要恢复函数返回的连接ID与旧的连接ID相同(在许多情况下很容易通过close/open/dup2()序列来确保这一点),那么MsgSend()函数现在就可以尝试重新传输数据。
如果MsgSend*()返回的错误不是EBADF/ESRCH,那么这些错误会传播回客户端。还请注意,如果连接ID不是ha感知的连接ID,或者如果客户机没有提供恢复函数,或者该函数无法重新获得相同的连接ID,则允许错误传播回客户端,以其喜欢的任何方式进行处理。
客户端可以改变他们的恢复功能。由于客户机还可以传递“恢复/连接”信息(由HA库传递给恢复函数),因此客户机可以构造复杂的恢复机制,这些机制可以动态修改。
客户端恢复库允许客户端在重新连接到同一服务器或不同服务器后重建继续消息传输所需的状态。客户机负责确定必须重构的状态由什么组成,并在调用恢复函数时适当地执行此任务。
3. 其他涵盖以及方便的函数(Other covers and convenience functions)
除了标准MsgSend*()调用的覆盖函数之外,HA库还为客户端提供了两个“HA-aware”函数,让您将连接指定为HA-aware,或者类似地为一个已经存在的HA-aware连接移除这样的指定:
HA-awareness功能
ha_attach ()
将恢复函数与连接关联,使其具有HA-aware。
ha_detach ()
删除恢复函数和连接之间先前指定的关联。这使得连接不再具有HA-aware。
ha_connection_ctrl ()
控制HA-aware连接的操作。
3.1 I/O convers
HA库还提供了以下覆盖函数,它们的行为本质上与所覆盖的原始函数相同,但在连接也是HA-aware的地方略有扩展:
ha_open(), ha_open64 ()
这些函数除了调用底层的Open调用外,还通过自动调用ha_attach()使连接具有HA-aware。因此,使用这些调用相当于调用open()或open64(),然后调用ha_attach()。
ha_creat(), ha_creat64 ()
这些函数除了调用底层的creat调用外,还通过自动调用ha_attach()使连接具有HA-aware。因此,使用这些调用相当于调用creat()或creat64(),然后调用ha_attach()。
ha_ConnectAttach(), ha_ConnectAttach_r()
使用ConnectAttach()创建一个连接,并将其附加到HA库中。因此,使用这些调用相当于调用ConnectAttach()或ConnectAttach_r(),然后调用ha_attach()。
ha_ConnectDetach(), ha_ConnectDetach_r()
分离一个附加的fd,然后使用ConnectDetach()关闭连接。这些函数除了调用底层的ConnectDetach调用外,还通过自动调用ha_attach()使连接具有HA-aware。因此,使用这些调用相当于调用ConnectDetach()或ConnectDetach_r(),然后调用ha_attach()。
ha_fopen ()
打开一个文件流并将它附加到HA库中,这个函数除了调用底层的fopen()调用外,还通过自动调用ha_attach()使连接具有HA-aware。因此,使用这个调用相当于调用fopen(),然后调用ha_attach()。
ha_fclose ()
为一个文件流分离一个附加的HA fd,然后关闭它。这个函数除了调用底层的fclose()调用外,还通过自动调用ha_attach()使连接具有HA-aware。因此,使用这个调用相当于调用fclose(),然后调用ha_attach()。
ha_close ()
拆开附加的HA fd,然后关闭它。这个函数除了调用底层的close()调用外,还通过自动调用ha_attach()使连接具有HA-aware。因此,使用这个调用相当于调用close(),然后调用ha_attach()。
ha_dup ()
复制一个HA连接。这个函数除了调用底层的dup()调用外,还通过自动调用ha_attach()使连接具有HA-aware。因此,使用这个调用相当于调用dup(),然后调用ha_attach()。
3.2 Convenience functions
除了covers库还提供这另外两个方便的功能,重新打开连接恢复:
ha_reopen ()
在执行恢复时重新打开连接。
ha_ReConnectAttach ()
在执行恢复时重新打开连接。
[](javascript:;) 有关所有HA库函数的描述,请参阅本指南中的客户端恢复库参考一章。
4. 简单示例
下面是一个简单的客户端示例,该客户端打开了到服务器的连接,并试图从中读取数据。从描述符中读取之后,客户机将执行其他操作(可能会导致延迟),然后返回再次读取。
在此延迟期间,服务器可能已经死亡并返回,在这种情况下,到服务器的初始连接(已死亡)现在已经失效。但是,由于连接已经被ha感知,并且已经关联了恢复功能,所以连接能够重新建立自己。
#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);
/* re-open the connection */
newfd = ha_reopen(oldfd, SERVER, O_RDONLY);
/* perform any other kind of state re-construction */
(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);
/* re-open the connection */
newfd = ha_reopen(oldfd, SERVER, O_RDONLY);
/* perform any other kind of state reconstruction */
(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;
/* open a connection and make it HA aware */
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);
/* Dup the FD. the copy will also be HA aware */
fd2 = ha_dup(fd);
printf("dup-ped fd2 = %d\n",fd2);
printf("before sleeping first time\n");
/*
Go to sleep...
Possibly the SERVER might die and return in this little
time period.
*/
sleep(15);
/*
reading from dup-ped fd
this should work just normally if SERVER has not died.
But if the SERVER has died and returned, the
initial read will fail, but the recovery function
will be called, and it will re-establish the
connection, and then re-establish the current
file position and then re-issue the read call
which should succeed now.
*/
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 and fd2 are dup-ped fd's
changing the recovery function for fd2
From this point forwards, the recovery (if at all)
will performed using "recover_conn2" as the recovery
function.
*/
status = ha_attach(fd2, recover_conn2, (void *)&hdl, HAREPLACERECOVERYFN);
ha_close(fd); /* close fd */
/* open a new connection */
fd = open(SERVER, O_RDONLY);
printf("New fd = %d\n",fd);
/* make it HA aware. */
status = ha_attach(fd, recover_conn, (void *)&hdl, 0);
printf("before sleeping again\n");
/* copy it again */
fd3 = ha_dup(fd);
/* go to sleep...possibly another option for the server to fail. */
sleep(15);
/*
get rid of one of the fd's
we still have a copy in fd3, which must have the
recovery functions associated with it.
*/
ha_close(fd);
printf("trying to read from %s using fd %d\n",SERVER, fd3);
/*
if it fails, the call will generate a call back to the
recovery function "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);
/*
if this call fails, recovery will be via the
second function "recover_conn2", since we replaced
the function for fd2.
*/
status = read(fd2,buf,30);
if (status < 0)
printf("error: %s\n",strerror(errno));
/* close the fd2, and detach it from the HA lib */
ha_close(fd2);
/*
finally print out our local statistics that we have been
retaining along the way.
*/
printf("total recoveries, %d\n",hdl.nr);
exit(0);
}
5. 状态重建示例
在下面的示例中,除了重新打开到服务器的连接外,客户机还通过查找当前文件(连接)偏移量来重构连接的状态。
这个示例还展示了客户端如何维护状态信息,恢复函数可以使用这些信息在故障发生前返回到以前的检查点状态,以便消息传输能够正常继续。
#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);
/* re-open the file */
newfd = ha_reopen(oldfd, REMOTEFILE , O_RDONLY);
/* re-construct state, by seeking to the correct offset. */
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;
/* open a connection */
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");
/*
update state of the connection
this is a kind of checkpointing method.
we remember state, so that the recovery functions
have an easier time.
*/
hdl.curr_offset += status;
}
fd3 = ha_dup(fd);
sleep(18);
/*
sleep for some arbitrary period
this could be some other computation
or some other blocking operation, which gives
a window within which the server might fail
*/
/* reading from dup-ped fd */
printf("trying to read from file using fd %d\n",fd);
printf("after sleeping\n");
/*
if the read initially fails
it will recover, re-open and seek to the right spot!!
*/
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);
/*
try it again.. this time using the copy.
recovery will again happen upon failure,
automatically re-connecting/seeking etc.
*/
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);
}