【QNX】高可用性框架(5)

第五章 客户端恢复库的使用

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);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345