当前位置首页 > 百科> 正文

Heartbeat(Linux-HA工程的一个组件)

2020-01-16 09:25:54 百科
Heartbeat(Linux-HA工程的一个组件)

Heartbeat(Linux-HA工程的一个组件)

Heartbeat 项目是 Linux-HA 工程的一个组成部分,它实现了一个高可用集群系统。心跳服务和集群通信是高可用集群的两个关键组件,在 Heartbeat 项目里,由 heartbeat 模组实现了这两个功能。下面描述了 heartbeat 模组的可靠讯息通信机制,并对其实现原理做了一些介绍。

基本介绍

  • 外文名:Heartbeat
  • 属于:Linux-HA 工程
  • 实现:高可用集群系统
  • 出现时间:1999年

项目简介

Heartbeat是Linux-HA工程的一个组件,自1999年开始到现在,发布了众多版本,是目前开源Linux-HA项目最成功的一个例子,在行业内得到了广泛的套用,这里分析的是2007年1月18日发布的版本2.0.8。
heartbeatheartbeat
随着Linux在关键行业套用的逐渐增多,它必将提供一些原来由IBM和SUN这样的大型商业公司所提供的服务,这些商业公司所提供的服务都有一个关键特性,就是高可用集群。

原理

heartbeat (Linux-HA)的工作原理:heartbeat最核心的包括两个部分,心跳监测部分和资源接管部分,心跳监测可以通过网路链路和串口进行,而且支持冗 余链路,它们之间相互传送报文来告诉对方自己当前的状态,如果在指定的时间内未收到对方传送的报文,那幺就认为对方失效,这时需启动资源接管模组来接管运 行在对方主机上的资源或者服务。

高可用集群

高可用集群是指一组通过硬体和软体连线起来的独立计算机,它们在用户面前表现为一个单一系统,在这样的一组计算机系统内部的一个或者多个节点停止工作,服务会从故障节点切换到正常工作的节点上运行,不会引起服务中断。从这个定义可以看出,集群必须检测节点和服务何时失效,何时恢复为可用。这个任务通常由一组被称为“心跳”的代码完成。在Linux-HA里这个功能由一个叫做heartbeat的程式完成。

讯息通信模型

Heartbeat包括以下几个组件:
heartbeat – 节点间通信校验模组
CRM - 集群资源管理模组
CCM - 维护集群成员的一致性
LRM - 本地资源管理模组
StonithDaemon - 提供节点重启服务
logd - 非阻塞的日誌记录
apphbd - 提供应用程式级的看门狗计时器
Recovery Manager - 套用故障恢复
底层结构–包括外挂程式接口、进程间通信等
CTS – 集群测试系统,集群压力测试
这里主要分析的是Heartbeat的集群通信机制,所以这里主要关注的是heartbeat模组。
heartbeat模组由以下几个进程构成:
master进程(masterprocess)
FIFO子进程(fifochild)
read子进程(readchild)
write子进程(writechild)
在heartbeat里每一条通信通道对应于一个write子进程和一个read子进程,假设n是通信通道数,p为heartbeat模组的进程数,则p、n有以下关係:
p=2*n+2
在heartbeat里,master进程把自己的数据或者是客户端传送来的数据,通过IPC传送到write子进程,write子进程把数据传送到网路;同时read子进程从网路读取数据,通过IPC传送到master进程,由master进程处理或者由master进程转发给其客户端处理。
Heartbeat启动的时候,由master进程来启动FIFO子进程、write子进程和read子进程,最后再启动client进程。

可靠讯息通信

Heartbeat通过外挂程式技术实现了集群间的串口、多播、广播和组播通信,在配置的时候可以根据通信媒介选择採用的通信协定,heartbeat启动的时候检查这些媒介是否存在,如果存在则载入相应的通信模组。这样开发人员可以很方便地添加新的通信模组,比如添加红外线通信模组。
对于高可用集群系统,如果集群间的通信不可靠,那幺很明显集群本身也不可靠。Heartbeat採用UDP协定和串口进行通信,它们本身是不可靠的,可靠性必须由上层套用来提供。那幺怎样保证讯息传递的可靠性呢?
Heartbeat通过冗余通信通道和讯息重传机制来保证通信的可靠性。Heartbeat检测主通信链路工作状态的同时也检测备用通信链路状态,并把这一状态报告给系统管理员,这样可以大大减少因为多重失效引起的集群故障不能恢复。例如,某个工作人员不小心拨下了一个备份通信链路,一两个月以后主通信链路也失效了,系统就不能再进行通信了。通过报告备份通信链路的工作状态和主通信链路的状态,可以完全避免这种情况。因为这样在主通信链路失效以前,就可以检测到备份工作链路失效,从而在主通信链路失效前修复备份通信链路。
Heartbeat通过实现不同的通信子系统,从而避免了某一通信子系统失效而引起的通信失效。最典型的就是採用乙太网和串口相结合的通信方式。这被认为是当前的最好实践,有几个理由可以使我们选择採用串口通信:
(1)IP通信子系统的失效不太可能影响到串口子系统。
(2)串口不需要複杂的外部设备和电源。
(3)串口设备简单,在实践中非常可靠。
(4)串口可以非常容易地专用于集群通信。
(5)串口的直连线因为偶然性掉线事件很少。
不管是採用串口还是乙太网IP协定进行通信,heartbeat都实现了一套讯息重传协定,保证讯息包的可靠传递。实现讯息包重传有两种协定,一种是传送者发起,另一种是接收者发起。
对于传送者发起协定,一般情况下接收者会传送一个讯息包的确认。传送者维护一个计时器,并在计时器到时的时候重传那些还没有收到确认的讯息包。这种方法容易引起传送者溢出,因为每一台机器的每一个讯息包都需要确认,使得要传送的讯息包成倍增长。这种现像被称为传送者(或者ACK)内爆(implosion)。
对于接收者发起协定,採用这种协定通信双方的接收者通过序列号负责进行错误检测。当检测到讯息包丢失时,接收者请求传送者重传讯息包。採用这种方法,如果讯息包没有被送达任何一个接收者,那幺传送者容易因NACK溢出,因为每个接收者都会向传送者传送一个重传请求,这会引起传送者的负载过高。这种现像被称为NACK内爆(implosion)。
Heartbeat实现的是接收者发起协定的一个变种,它採用计时器来限制过多的重传,在计时器时间内限制接收者请求重传讯息包的次数,这样传送者重传讯息包的次数也被相应的限制了,从而严格的限制了NACK内爆。

实现

一般集群通信有两类讯息包,一类是心跳讯息包,这类讯息包通告集群内节点的存活情况;另一类是控制讯息包,这类讯息包负责集群的节点和资源管理。heartbeat把心跳讯息包看成是控制讯息包的一个特例,採用相同的通信通道进行传送,这使得协定的实现简单化,而且很有效,并把相应的代码限制在几百行之内。
在heartbeat里,一切流向网路的数据都由master进程传送到write子进程进行传送。master进程调用send_cluster_msg()函式把讯息传送到所有的write子进程。下面通过一些代码片段看看heartbeat是怎幺传送讯息的。在介绍代码之前先介绍相关的重要数据结构
Heartbeat的讯息包数据结构structha_msg{intnfields;/*讯息包数据域的个数*/intnalloc;/*己分配的记忆体块个数*/char**names;/*讯息包数据域的名称*/size_t*nlens;/*各个数据域称的长度*/void**values;/*与数据域名称对应的数据值*/size_t*vlens;/*各个数据域对应的数据值的长度*/int*types;/*讯息包的类型*/};
Heartbeat的历史讯息伫列structmsg_xmit_hist{structha_msg*msgq[MAXMSGHIST];/*历史讯息伫列*/seqno_tseqnos[MAXMSGHIST];/*历史讯息序列号*/longclock_tlastrexmit[MAXMSGHIST];/*上一次重传的时间*/intlastmsg;/*上一次重传到的讯息序列号*/seqno_thiseq;/*最大讯息序列号*/seqno_tlowseq;/*最小讯息序列号*/seqno_tackseq;/*确认了的讯息序列号*/structnode_info*lowest_acknode;/*确认的节点*/};
代码所属档案heartbeat/heartbeat.c
intsend_cluster_msg(structha_msg*msg){...pid_tourpid=getpid();...
if(ourpid==processes[0]){/*来自master进程的讯息*//*添加控制信息,包括源节点名,源节点全局标识符,序列号,代数,时间等*/if((msg=add_control_msg_fields(msg))!=NULL){/*可靠的多播讯息包传递*/rc=process_outbound_packet(&msghist,msg);}}else{/*来自client进程的讯息*/intffd=-1;char*smsg=NULL;
...
/*传送到FIFO进程*/
if((smsg=msg2wirefmt_noac(msg,&len))==NULL){...}elseif((ffd=open(FIFONAME,O_WRONLY|O_APPEND))nodename)==0);
/*把讯息转换成字元串*/smsg=msg2wirefmt(msg,&len);
...
if(cseq!=NULL){/*存放到历史讯息伫列里,通过序列号记录,如果需要,则进行重传*/add2_xmit_hist(hist,msg,seqno);}
...
/*通过write子进程传送到所有的网路接口上*/send_to_all_media(smsg,len);
...
returnHA_OK;}
add2_xmit_hist()函式把传送的讯息发到一个历史讯息伫列里去,伫列的最大长度为200。如果接收者请求重传讯息,传送者通过序列号在该伫列里查找要重传的讯息,如果找到则进行重传。下面是相关代码。
staticvoidadd2_xmit_hist(structmsg_xmit_hist*hist,structha_msg*msg,seqno_tseq){intslot;structha_msg*slotmsg;
...
/*查找伫列里讯息存放的位置*/slot=hist->lastmsg+1;if(slot>=MAXMSGHIST){/*到达队尾,从头开始。在这里实现循环伫列*/slot=0;}
hist->hiseq=seq;slotmsg=hist->msgq[slot];
/*删除伫列中找到的位置上的旧讯息*/if(slotmsg!=NULL){hist->lowseq=hist->seqnos[slot];hist->msgq[slot]=NULL;if(!ha_is_allocated(slotmsg)){...}else{ha_msg_del(slotmsg);}}
hist->msgq[slot]=msg;hist->seqnos[slot]=seq;hist->lastrexmit[slot]=0L;hist->lastmsg=slot;
if(enable_flow_control&&live_node_count>1&&(hist->hiseq–hist->lowseq)>((MAXMSGHIST*3)/4)){/*讯息伫列长度大于告警长度,记录日誌*/...}if(enable_flow_control&&hist->hiseq–hist->ackseq>FLOWCONTROL_LIMIT){/*讯息伫列的长度大于流控限制长度*/if(live_node_counthiseq–(FLOWCONTROL_LIMIT–1));all_clients_resume();}else{/*client进程传送讯息过快,暂停所有的client进程*/all_clients_pause();hist_display(hist);}}
}
当传送者收到接收者的重传请求后,通过回调函式HBDoMsg_T_REXMIT()函式调用process_rexmit()函式进行讯息重传。
#defineMAX_REXMIT_BATCH50/*每次最多重传的讯息包数*/
staticvoidprocess_rexmit(structmsg_xmit_hist*hist,structha_msg*msg){constchar*cfseq;constchar*clseq;seqno_tfseq=0;seqno_tlseq=0;seqno_tthisseq;intfirstslot=hist->lastmsg–1;intrexmit_pkt_count=0;constchar*fromnodename=ha_msg_value(msg,F_ORIG);structnode_info*fromnode=NULL;
...
/*取得要重传的讯息包的起始序列号*/if((cfseq=ha_msg_value(msg,F_FIRSTSEQ))==NULL||(clseq=ha_msg_value(msg,F_LASTSEQ))==NULL||(fseq=atoi(cfseq))lseq){/*无效序列号,记录日誌信息*/...}
...
/*重传丢失的讯息包*/for(thisseq=fseq;thisseqtrack.ackseq){/*该讯息包已经被确认过,可以忽略掉*/continue;}if(thisseqlowseq){/*序列号小于讯息伫列里的最小序列号,该讯息己不存在于历史讯息伫列中*//*告知对方,不重传该讯息*/nak_rexmit(hist,thisseq,fromnodename,“seqnotoolow”);continue;}if(thisseq>hist->hiseq){/*序列号大于讯息伫列中最大序列号*/...continue;}
for(msgslot=firstslot;!foundit&&msgslot!=(firstslot+1);--msgslot){char*smsg;longclock_tnow=time_longclock();longclock_tlast_rexmit;size_tlen;
...
/*重传上一次重传剩下的讯息包*/last_rexmit=hist->lastrexmit[msgslot];
if(cmp_longclock(last_rexmit,zero_longclock)!=0&&longclockto_ms(sub_longclock(now,last_rexmit))<(ACCEPT_REXMIT_REQ_MS)){gotoNextReXmit;}
/*一次不能传送太多数据包,如果数据包太多的话,可能会引起串口溢出*/++rexm
声明:此文信息来源于网络,登载此文只为提供信息参考,并不用于任何商业目的。如有侵权,请及时联系我们:baisebaisebaise@yeah.net