摘要:介绍了基于网络的双机热备系统的设计思路以及通过在主备服务器中配置脚本程序,将阅文站服务器升级至双机热备系统的过程。
关键词:双机热备,心跳监测,同步
Abstract: This
paper introduce a design method of network based hot standby service system as
well as the procedure of upgrade from single document server to hot standby
service system by configuring script files on both active and standby server.
Keyword: hot
standby service, heartbeat monitor, synchronization
1
:升级方案建立的起因
目前的内部文件阅读服务器由于使用年代较长,经常出现无响应甚至死机的状况。由于内部文件阅读服务器是我台员工了解并获取规章制度内容和近期活动消息的一个重要途径,所以该服务器的稳定性显得十分重要。就目前的管理情况而言,除了定期的检查之外,一般都是通过阅读者的通报获知服务器处于无响应的状况,很难做到及时发现并处理。
单纯通过增加检查次数的方法很难收到理想的效果。一来即便达到一天十次检查的量,对于服务器而言仍然是小时单位的数量级,而且只能在工作时间内施行;再者,由于没有配套的同步备份方案,即便发现服务器崩溃,恢复重建所消耗的时间依然相当可观。长时间停机导致信息传达的滞后将直接影响到工作的开展,试想如果这种情况发生在关键性服务器如新闻文稿系统上的话,将造成不可估量的严重后果。
2
:网络双机热备的思路
所要采用的采用双机热备的办法,双机热备即是目前通常所说的
active/standby
方式。当
active
服务器出现故障的时候,通过软件诊测(一般是通过心跳诊断)将
standby
机器激活,保证应用在短时间内完全恢复正常使用。数据同步的方案一般在设计双机热备服务器时制定,比方说使用共享存储器就是个简单方便的解决办法;更加有效的方法的是在编写配置与数据输入输出相关的服务程序,使数据同时写入主备服务器中相关的数据库。但是在本文的例子中,由于文件服务器在建立时是一个单机的形式,所以其中的程序和数据都是针对单机进行设置的,如果要实现传统意义上双机热备的数据同步,需要对原先的网站程序和存储方案做较大的的修改,除开
standby
服务器的费用外,可能还需要另外购买存储设备。在对源文件无法进行修改或者预算有限的情况下,这种代价的方案并不是完全适用。
为了克服以上的几个局限,我们利用网络代替传统心跳线和传输数据来同步数据,不但利用了现有的网络资源来节省投入的成本,而且在必要的情况下通过建立在不同端口的监听,
standby
服务器甚至可以同时为多台提供不同服务的
active
服务器的备用支持。
首先,
standby
服务器中配置监控脚本,通过循环心跳检测
active
服务器的生存状况,达到实时监控的目的,一旦发现出错状况就可以采取预先设置的方案及时接管
active
服务器服务。通常情况下,一旦服务器崩溃,其网络功能也将停止,所以可以简单采用
ping
的方法监测
active
服务器;但是更一般的情况是,服务器仅仅是停止了某一项服务,比如网站发布服务或者数据库服务,而其网络联通性依然良好,针对这种情况需要在主备双方配置监测特定服务的信息问答脚本。比方说,
active
服务器提供数据库查询服务,在每次接受到来自备服务器的心跳包后,
active
服务器执行一次特定的
sql
查询并将查询所得与预期的比对,得到的结果以布尔值的形式传回备服务器,最后由备服务器决定是否接管服务。
接着,为了保持
active
服务器与
standby
服务器中数据的一致性,需要将
active
服务器上的文件或数据库的更新内容,定期地通过一定的途径同步到备服务器中。对于数据处理不是很频繁的服务器,可以采用定期备份的方式。如需要采取更加细致的做法的话,在被服务器定期备份的基础上,
active
服务器可以配置数据更新检测脚本,一旦检测到有数据更新就将其写入同步数据包中,当数据包达到规定大小之后就可用它来同步
standby
服务器。同步数据包大小的设置视乎网络的通畅度和服务器的响应能力而定。在两个条件都较好的情况下,可以采用小数据包的形式,这样做的好处在于主备的同步度较高,
standby
服务器在接管服务时可以最大限度再现
active
服务器内容。而在有任何一个条件或者两者都不理想的情况下,需要适当增加数据包的大小。另外,由于目前一些大型的数据库普遍支持触发器,在同步这类数据库时我们可以利用触发器来保存添加、删除和更新的记录操作,很大程度上简化了同步的过程。
3
:具体的升级方案和主要脚本分析
3.1
:心跳监视
现在将以上的思路与具体的情况想结合,实现文稿阅读服务器升级至双机热备。由于该服务器提供的是
http
的网页浏览服务,所以在
standby
服务器中只需要配置定期具备获取网页功能的脚本即可以达到判断
active
服务器服务是否正常,换一句换说在做这个特例中
active
服务器的心跳应答是建立在已有的
http
服务上的,无需再建立额外的监听端口。
以下是备份服务器上的心跳脚本
heartbeat.bat
,心跳间隔为一个小时即
3600
秒,
active
服务器域名是
file.zsgd.com
,
standby
服务器为
file-bak.zsgd.com
。
脚本首先建立了一个无穷循环,在该循环中每隔一个小时使用
LWP::Simple
下载一次阅文站首页的内容,如果下载失败则表示
active
服务器
http
服务失效,
standby
服务器将调用
iisweb.vbs
来启动本身的
iis
网站服务。然后用
Net::FTP
登录
zsgd.com
主站的
ftp
下载默认页面文件
default.asp
,修改其中的阅文站链接,使其指向
standby
服务器。最后将修改完的
default.asp
上传至
zsgd.com
主站
ftp
,覆盖原先的文件。
@rem = '--*-Perl-*--
@echo off
if
"%OS%" == "Windows_NT" goto WinNT
perl -x -S
"%0" %1 %2 %3 %4 %5 %6 %7 %8 %9
goto endofperl
:WinNT
perl -x -S %0
%*
if NOT
"%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto
endofperl
if
%errorlevel% == 9009 echo You do not have Perl in your PATH.
if errorlevel
1 goto script_failed_so_exit_with_non_zero_val 2>nul
goto endofperl
@rem ';
#!perl
#line 15
use strict;
use
LWP::Simple;
use Net::FTP;
my ( $none,
$interval, $ftp, $backserver) = ( '', 3600, undef, 'file-bak.zsgd' );
my $content =
$none;
whil(1){
$content = get 'http://file.zsgd.com/';
if( $content eq $none ){
system ' cscript.exe %systemroot%/system32/iisweb.vbs
/stop file ';
system 'cscript.exe %systemroot%/system32/iisweb.vbs
/start file';
$ftp = Net::FTP->new('file.zsgd.com',
debug => 0);
$ftp->login('***', '***');
$ftp->get('default.asp');
open FL, '<default.asp';
open TMP, '>tmp';
while(<FL>){
chomp;
$_ =~
s/(.*)(file\.zsgd)(.*)/$1$backserver$3/;
print TMP
$_."\n";
}
close FL; close TMP;
system
'del
default.asp';
system 'ren tmp default.asp';
$ftp->put('default.asp');
$ftp->quit;
}
$content = $none;
sleep $interval;
}
__END__
:endofperl
heatbeat.bat
3.2
:数据同步
在数据同步方面,
msdn
上有关于基于
Microsoft Jet
数据库引擎的
internet
数据库同步的具体方案
,为
windows
平台上大型数据库的同步提供了良好的途径。但是这次升级的服务器中配置的是小型
access
数据库,而且数据上载周期较长,没有
频繁的删除或者更新数据项的操作。所以出于实验和学习的目的,这里通过配置脚本来实现
internet
中
access
数据库的监视与同步。
在
active
服务器中配置
datamonitor.bat
脚本监视数据库的更新情况。首先读入参数文件中的数据,该参数文件保存的是前一次脚本运行得到的
access
数据库文件的最后修改时间和其中各个表的数据项
id
值,通过比对本次脚本运行得到的数据库修改时间与最后一次修改时间即可判断数据库在两次脚本运行期间是否更新了数据。一旦发现有更新,脚本将使用
DBD::ADO
连接
access
数据库。首先,检测各个表中
id
值大于前次查询最大
id
的数据记录,这些就是新插入的数据项;再在小于最大
id
值的
id
中检索不重复项,这些是被删除的数据项;接着用本次查询所得的
id
值替换参数文件中的内容;然后将这些数据分类打包,发送给
standby
服务器的数据同步监听脚本;最后做一些必要清理和初始化工作,一个小时后进入下一个周期重复以上的步骤。
这么做的一个缺点是很难检测更新数据项,虽然理论上可以通过在表中设置更新标记的方法提示数据更新,但是更新标记的修改程序必须嵌入网站程序,这在加密的网页程序中将很难实现,退一步讲如果服务是基于编译了的程序的话,这样的修改将无从谈起。所以从某种程度上表明,重要的数据服务不要建立在文本型的数据库上,对于有同步要求的服务,选择的数据库至少要支持触发过程。在资金允许的前提下,最好使用如
oracle
、
db2
等大型的数据库系统,另外免费的
mysql
和
postgresql
也是不错的选择。
@rem =
'--*-Perl-*--
@echo off
if
"%OS%" == "Windows_NT" goto WinNT
perl -x -S
"%0" %1 %2 %3 %4 %5 %6 %7 %8 %9
goto endofperl
:WinNT
perl -x -S %0
%*
if NOT
"%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto
endofperl
if
%errorlevel% == 9009 echo You do not have Perl in your PATH.
if errorlevel
1 goto script_failed_so_exit_with_non_zero_val 2>nul
goto endofperl
@rem ';
#!perl
#line 15
use strict;
use DBI;
my ($PF_INET,
$port, $SOCK_DGRAM) = (2, 2345, 2);
my
$remote_addr = pack('SnC4x8',$PF_INET,$port,192,168,138,228);
socket(CLIENT,
$PF_INET, $SOCK_DGRAM, getprotobyname('udp'));
my $dsn =
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:/temp/tp.mdb";
my $dbh =
DBI->connect("dbi:ADO:$dsn",
{PrintError => 0});
my $tbh =
$dbh->prepare('select * from pic'); $tbh->execute;
my (@data,
@tbl_id, $max_tbl_id, $del_id, @insert_data_ref)= undef;
my ($index,
$id, $index_id) = (0, undef, undef);
while(1){
open TBL, '<tbl-pic';
open TMP, '>tmp';
while(<TBL>){ chomp; push @tbl_id,
$_ ; } #$_ = atoi $_;
close TBL;
$max_tbl_id = $tbl_id[(scalar @tbl_id) -
1];
while(@data = $tbh->fetchrow_array()){
$id = $data[0];
print TMP $id."\n"
if($id <= $max_tbl_id){
$index_id =
$tbl_id[$index];
while($index_id <= $id){
if($index_id <
$id){ $del_id .= $index_id."\t"; }
$index += 1;
if($index <
(scalar @tbl_id)){ $index_id = $tbl_id[$index]; }else{ last; }
}
}else{ my @temp = @data; push
@insert_data_ref, \@temp; }
}
close TMP; system 'del tbl-pic'; system 'ren tmp tbl-pic';
undef $tbh;
$dbh->disconnect;
send(CLIENT,$del_id, 0, $remote_addr);
foreach my $ref(@insert_data_ref){
my $item = undef;
foreach my $field(@$ref){ $item .=
$field."\t"; }
send(UDP_CLIENT, $item, 0,
$remote_addr);
}
close CLIENT;
sleep 3600;
$dbh = DBI->connect("dbi:ADO:$dsn", {PrintError
=> 0});
socket(CLIENT, $PF_INET, $SOCK_DGRAM,
getprotobyname('udp'));
(@tbl_id, $del_id, @insert_data_ref) =
undef;
$index = 0;
}
__END__
:endofperl
tbl-pic-monitor.bat
在
standby
服务器中配置监听服务脚本用以检查并接受来自
active
服务器更新数据包,接着将接收到的数据中的前缀指示判断该数据是用于插入或是删除,从而再运行下一步的数据库更新操作,最后脚本进入下一个周期的等待监听。
@rem =
'--*-Perl-*--
@echo off
if
"%OS%" == "Windows_NT" goto WinNT
perl -x -S
"%0" %1 %2 %3 %4 %5 %6 %7 %8 %9
goto endofperl
:WinNT
perl -x -S %0
%*
if NOT
"%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto
endofperl
if
%errorlevel% == 9009 echo You do not have Perl in your PATH.
if errorlevel
1 goto script_failed_so_exit_with_non_zero_val 2>nul
goto endofperl
@rem ';
#!perl
#line 15
my $dsn =
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:/temp/tp.mdb";
my ($dbh, $sign,
$content) = undef;
my ($buffer,
$PF_INET, $port, $SOCK_DGRAM) = (undef, 2, 2345, 2);
my $local_addr
= pack('SnC4x8', $PF_INET, $port, 192, 168, 138, 228);
socket(SERVER,
$PF_INET,$SOCK_DGRAM, getprotobyname('udp')) or die("$!");
bind(SERVER,
$local_addr) or die("$!");
listen(SERVER,
10);
while(1){
next unless my $remote_addr =
recv(UDP_SERVER, $buffer, 100, 0);
chomp($buffer);
$dbh = DBI->connect("dbi:ADO:$dsn",
{PrintError => 0});
($sign, $content) = split $buffer, '::';
if($sign eq 'add'){
$dbh->prepare('insert into pic
values(?, ?, ?, ?)');
$dbh->execute(split($content,
"\t"));
$dbh->close;
}else{
$dbh->prepare('delete from pic
where id = ?');
foreach my $tmp(split $content,
"\t"){ $dbh->execute($tmp); }
}
$dbh->disconnect;
}
__END__
:endofperl
tbl-pic-recv.bat
4
:建立跟踪系统运行的系统日志
一个成功服务器解决方案的产生,需要后期长时间的跟踪和调试。在建立了基于网络的双机热备系统之后,为了方便了解和检查
active
和
standby
服务器中各个脚本的执行情况,需要对脚本做一些修改,使其能将执行的结果输出并存储到特定的日志文件中,方便管理员通过查询其中的内容来判断系统运行的状况。对于严重的状况,比如
active
服务器
down
机,系统还可以通过发送
email
的方式通知管理员,使其能及时做出反应。
在心跳监测脚本中,当每个周期运行结束后,向日志文件里记录本周期检测启动成功与否、启动或失效的日期时间和检测得到的
http
反馈内容,如果没有得到内容则发送
email
给指定的管理员。对于数据同步,
active
服务器中的脚本将记录本周期同步启动日期时间、数据库文件的最后一次修改时间、数据库连接状况、
socket
客户端启动成功与否及数据发送的结果;
standby
服务器中的脚本将记录收到更新数据的时间和所有的数据内容以便于在必要的时候恢复数据库。
参考文献:
[1] perldoc5.8.8 www.perl.org
2006