| 
				 一、前言 
随着互联网的发展以及电子商务应用的兴起,多层分布式系统的开发和应用已成为企业和开发者关注的焦点之一。在一个执行关键作业的多层应用系统中,系统的稳定性是非常重要的,尤其是当客户端正在执行一些重要程序时,应用程序服务器或数据库服务器发生的任何故障都将造成多层应用系统无法正常运作。另外,如果应用程序服务器的负荷过重也会影响整个系统的执行效率,因此对于企业级关键程序,系统的容错能力和负载平衡能力是开发人员必须重视的首要问题。 
一般来说,容错能力和负载平衡能力就是让应用程序服务器在多个机器上执行,当客户端应用程序执行时,可以连接到任何一台机器中的应用程序服务器要求服务,以确保系统稳定,同时平衡每一台服务器的负荷。 
然而,当应用程序服务器执行于多台机器时,又给数据库服务器提出了新的挑战:一方面,如果所有的应用程序服务器都通过同一远程数据库服务器访问数据,那么数据库服务器的故障同样会影响整个系统的稳定性,而且可能超负荷工作;另一方面,如果每一个应用程序服务器都拥用各自的数据库服务器或本地数据库,这样虽能提高系统的稳定性,但不能保证数据的一致性。 
基于以上情况,本文提出了基于MIDAS的应用程序服务器镜像技术方案和实现方法,用以开发安全坚固的分布式系统。 
二、安全坚固的多层应用系统 
1.多层分布式系统结构 
一个多层分布式应用系统被分割成在不同机器上协同运行的逻辑单元。各逻辑单元通过局域网或Internet共享数据和通信,这种结构具有集中控制的商业逻辑、瘦客户端应用程序等许多优点。多层分布式系统最典型的模式为三层结构,各部分的名称和功能如下:客户端应用程序在用户机器上提供用户界面;应用程序服务器位于可连接到所有客户端的中央网络位置,并提供公共的数据服务。应用程序服务器可运行于多个机器上;远程数据库服务器提供数据库管理系统(RDBMS)。 
MIDAS(Multi-tier Distributed Application Services Suite)即多层分布式应用程序服务器,是Inprise提供的集成了多种技术规范的多层分布式数据库解决方案,是由Borland C++Builder(BCB)/Delphi用来开发多层应用系统使用的中介透明引擎,它具有在客户端无需任何数据库工具可以读取远程数据、网络通信量小、多线程、数据库自动约束及平衡负载的特点。如图1所示的三层结构是多层分布式系统的典型代表。 
2. 开发具备容错能力的应用系统 
多层应用系统的容错能力就是让应用程序服务器在多个机器上执行,当客户端应用程序执行时可以连接到任何一台机器中的应用程序服务器要求服务。如果客户端应用程序连接的服务器发生任何问题而无法继续执行时,客户端应用程序可以立刻连接到其他机器中的服务器继续要求服务。 
   
 
  
   
图1   基于MIDAS的三层应用系统的结构 
开发具备容错能力的多层应用系统,在应用程序服务器端与平常的系统没有什么区别,而在客户端应用程序中必须配合MIDAS编写一些额外的代码来实现容错能力。 
实现容错能力的观念很简单,当客户端应用程序连接到应用程序服务器之后,它就可以向服务器要求服务。但是当应用程序服务器故障时,客户端应用程序如果向服务器要求服务,那么客户端应用程序就会产生一个错误例外。此时客户端可以调用TSimpleObjectBroker的SetConnectStatus方法以设定目前的应用程序服务器成为不堪使用状态,然后再调用GetComputerForProgID方法要求取得另外一个可以使用的机器,以便在这个新的机器中连接提供相同服务的应用程序服务器。最后再调用新的服务器以取得服务。这个切换过程可以用图2来说明。  
  
 
  
   
图2   实现容错能力的流程图 
3.开发具备负载平衡能力的应用系统 
负载平衡的观念是指当有多个能够执行相同应用程序服务器的机器时,当有许多客户端应用程序需要连接应用程序服务器时,多层系统能够分配不同的客户端应用程序到每一个机器中,以便平衡每一个应用程序服务器的负荷。BCB的TSimpleObjectBroker提供了负载平衡的功能,程序员只需将其属性LoadBalanced设为True就可以提供简单的负载平衡能力。 
三、镜像技术 
由于多层应用系统可能运行于不同地域的不同机器上,设计者永远无法预测多层应用系统在执行时会发生什么状况,因此在分布式计算环境中,多层应用系统除了必须能够正确而且有效率地运作之外,系统的负载平衡能力和容错能力也非常重要。 
多层应用系统的容错能力和负载平衡都要求应用程序服务器在多个机器中执行,对于数据库服务器,将有两种可能的连接方式:一种是多个应用程序服务器都与同一个远程数据库服务器连接。采用这种连接方式时,系统的稳定性较低,如果远程数据库服务器故障,整个多层应用系统将无法正常运作;另一种是每一个应用程序服务器均与各自的数据库服务器或本地数据库相连。采用这种方式虽然可以提高系统的稳定性,但各服务器之间的数据互不相同,客户端取得的数据就会不完整或不一致。 
为了使多层应用系统既具有稳定性,又能保持数据的完整性,在实际应用中我们采用第二种连接方式,并在应用程序服务器中添加额外的代码,使各服务器之间的数据保持一致,我们称这种方法为应用程序服务器镜像技术。本文以双服务器为例来说明服务器镜像技术的原理及实现方法。 
1.动态服务器镜像技术 
动态服务器镜像就是当多层应用系统中有多个应用程序服务器提供数据服务时,使各服务器中的数据动态地保持一致。 
动态服务器镜像的原理如图3所示,当连接服务器1的客户端应用程序(如客户A)要求更新数据时,服务器1将数据更新到数据库1,同时还将这些数据传送给服务器2,由服务器2负责更新至数据库2;同理,连接服务器2的客户端应用程序(如客户B)更新的数据也会同时更新到两个数据库管理系统中。这样,两个互相独立的数据库管理系统中的数据就能够保持一致。 
  
  
 
  
  
图3   应用服务器镜像示意图 
在BCB/Delphi中,提供者组件(TdataSetProvider)负责将数据封装进数据包,然后发送给客户端数据集,并更新从客户端数据集接收到的数据。提供者组件的大多数工作是自动进行的,不需要在提供者组件中编写任何程序代码就可创建具有完整功能的应用程序服务器。然而,提供者组件包括一定的事件和属性,它们允许应用程序服务器更直接地控制为客户端封装的信息以及应用服务器如何响应客户端请求。利用其中的OnUpdateData事件或BeforeUpdateRecord事件即可实现动态服务器镜像。使用OnUpdateData事件可以处理完整的Delta包,而使用BeforeUpdateRecord事件可以对更新的记录逐一地进行处理。 
2.静态数据对照 
在多层应用系统的运作过程中,如果一个或多个服务器故障,在容错技术的支持下,虽然系统仍能正常运作,但客户更新的数据不能记载到故障服务器的数据库中,数据的完整性便得不到保障。为此,采用了静态对照的方法,即当故障服务器修复并重新投入运行时,主动与其他服务器对照,以提取最新的数据,从而保证客户端应用程序始终得到一致的数据服务。 
数据对照可以采用自动对照,也可以由管理员根据具体情况,人为地进行对照。自动对照就是,当应用程序服务器启动时,自动与在线的某一服务器对照数据;人为对照则是,开发专用的对照程序,由系统管理人员根据情况,决定何时进行数据对照。 
四、实现方法 
1.动态服务器镜像 
在服务器端,利用提供者组件的OnUpdateData事件可以在服务器更新数据之前将客户端应用程序传来的数据更新到镜像服务器中,以实现应用程序服务器镜像,使各服务器之间的数据动态地保持一致。 
客户端的更新请求包括修改、插入和删除三种。OnUpdateData事件处理函数应分别进行处理,下面给出的代码,以插入操作为例来说明应用程序服务器镜像技术的实现方法: 
void __fastcall TmsRDM::DataSetProvider1UpdateData(TObject *Sender, 
      TCustomClientDataSet *DataSet) 
{ 
  TClientDataSet  *table;  //table连接到镜像服务器 
  TDateTime       dtValue=StrToDateTime(Now());  //当前时间 
  table=mTable; 
  table->Active=false; 
  DataSet->First(); 
  if(Form1->SocketConnection1->Connected) { 
    while(!DataSet->Eof) { 
     switch(DataSet->UpdateStatus()) { 
      case usUnmodified:  //处理修改的数据 
       table->Active=false; 
       //向服务器传递SQL 
table->CommandText=AnsiString("select * from demo where 编号='") 
         +DataSet->FieldByName("编号")->AsString 
         +"' and 姓名='"+DataSet->FieldByName("姓名")->AsString+"'"; 
       table->Active=true;  //查询 
       if(table->RecordCount>0)  //镜像服务器中已存在该记录,则修改 
        table->Edit(); 
       else {  //不存在则添加此记录 
        able->Append(); 
        for(int i=0;i<table->FieldCount;i++) 
         if(DataSet->Fields->Fields[i]->AsString!="") 
         table->Fields->Fields[i]->AsString=DataSet->Fields->Fields[i]->AsString; 
      } 
DataSet->Next(); //修改状态下,DataSet中有两条相邻的记录说明修改情况, 
//前一条是原始数据,后一条是修改的数据 
      DataSet->Edit(); 
     DataSet->FieldByName("DATE_TIME")->AsDateTime=dtValue; //记录修改时间 
     DataSet->Post(); 
     for(int i=0;i<table->FieldCount;i++) 
      if(DataSet->Fields->Fields[i]->AsString!="") 
       table->Fields->Fields[i]->AsString=DataSet->Fields->Fields[i]->AsString; 
      table->FieldByName("DATE_TIME")->AsDateTime=dtValue; //记录修改时间 
      table->Post(); 
      break; 
     case usInserted:  //处理插入记录 
      DataSet->Edit(); 
      DataSet->FieldByName("DATE_TIME")->AsDateTime=dtValue; 
      DataSet->Post(); 
      table->Active=false; 
      table->CommandText=AnsiString("select * from demo where 编号='") 
       +DataSet->FieldByName("编号")->AsString 
       +"' and 姓名='"+DataSet->FieldByName("姓名")->AsString+"'"; 
      table->Active=true; 
      if(table->RecordCount>0) 
       table->Edit(); 
      else 
       table->Append(); 
      for(int i=0;i<table->FieldCount;i++) 
       if(DataSet->Fields->Fields[i]->AsString!="") 
        table->Fields->Fields[i]->AsString=DataSet->Fields->Fields[i]->AsString; 
       table->FieldByName("DATE_TIME")->AsDateTime=dtValue; 
       table->Post(); 
       break; 
      case usDeleted:  //删除记录 
       table->Active=false; 
       table->CommandText=AnsiString("select * from demo where 编号='") 
        +DataSet->FieldByName("编号")->AsString 
        +"' and 姓名='"+DataSet->FieldByName("姓名")->AsString+"'"; 
       table->Active=true; 
       if(table->RecordCount>0)  //镜像服务器中存在此记录则删除 
        table->Delete(); 
       break; 
     } 
     table->ApplyUpdates(-1); 
     table->Active=false; 
     DataSet->Next(); 
    } 
   } 
   //记录修改时间 
   DataSet->First(); 
   while(!DataSet->Eof) { 
     switch(DataSet->UpdateStatus()) { 
      case usUnmodified: 
       DataSet->Next(); 
       DataSet->Edit(); 
       DataSet->FieldByName("DATE_TIME")->AsDateTime=dtValue; 
       DataSet->Post(); 
       break; 
      case usInserted: 
       DataSet->Edit(); 
       DataSet->FieldByName("DATE_TIME")->AsDateTime=dtValue; 
       DataSet->Post(); 
       break; 
     } 
     DataSet->Next(); 
    } 
    DataSet->First(); //实际更新时从第一条记录开始 
} 
2.容错处理 
在客户端更新数据时,若原先连接的服务器故障,用以下代码能自动连接到提供相同服务的机器,继续取得数据服务: 
if (ClientDataSet1->ChangeCount > 0) { 
try { 
   ClientDataSet1->ApplyUpdates(-1);  //数据更新 
   ClientDataSet1->Refresh(); 
  } 
  catch(...) { 
   SimpleObjectBroker1->SetConnectStatus( 
SocketConnection1->Address,false);  //记录服务器失败 
   SocketConnection1->Connected=false; 
   SocketConnection1->Address= 
SimpleObjectBroker1->GetComputerForProgID("mirrorserver.msRDM"); 
   SocketConnection1->Connected=true; 
   updateBtn->Click();  //用新的服务器更新 
 } 
} 
3.静态数据对照 
        Table1->Active=true; 
        ClientDataSet1->Active=true; 
        while(!ClientDataSet1->Eof) { 
         AnsiString     Filter,Num,Name; 
         Num=ClientDataSet1->FieldByName("编号")->AsString; 
         Name=ClientDataSet1->FieldByName("姓名")->AsString; 
         Filter=AnsiString("编号='")+Num 
                +"' and 姓名='"+Name+"'"; 
         Table1->Filter=Filter;  //设置过滤器,用于条件导航 
         if(Table1->FindFirst()) {  //如果存在相同的记录 
          if(Table1->FieldByName("DATE_TIME")->AsString 
             !=ClientDataSet1->FieldByName("DATE_TIME")->AsString) 
           Table1->Edit();  //更新时间不同时,修改 
          else { 
           ClientDataSet1->Next(); //数据相同时,继续处理下一记录 
           continue; 
          } 
         } 
         else {  //不存在时,插入记录 
          Table1->Insert(); 
         } 
         for(int i=0;i<Table1->FieldCount;i++)  //使两个服务器的数据一致 
          Table1->Fields->Fields[i]->AsString 
           =ClientDataSet1->Fields->Fields[i]->AsString; 
         Table1->Post(); 
         ClientDataSet1->Next(); 
        } 
        Table1->Active=false; 
        ClientDataSet1->Active=false; 
4.软件握手 
软件握手是指服务器向在线的某一服务器发送启动或关闭镜像服务请求,并在收到镜像请求后,进行相应的处理。 
void __fastcall TForm1::Timer1Timer(TObject *Sender) 
{//用定时器监视镜像状态 
        if(Mirror) { 
         Timer1->Enabled=false; 
         Button1->Click();  //连接镜像服务器 
        } 
        else if(SocketConnection1->Connected) { 
         Timer1->Enabled =false; 
         ClientDataSet1->DataRequest(AnsiString("Mirror"));  //发送启动请求 
        } 
} 
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) 
{//服务器关闭时,通知与其有镜像关系的服务器 
        if(SocketConnection1->Connected) { 
         ClientDataSet1->DataRequest(AnsiString("Close"));  //停止镜像服务 
         SocketConnection1->Connected=false; 
         ShowMessage("关闭服务器"); 
        } 
        if(ChangeIp||(!FileExists("ipaddress.txt"))) {  //保存镜像服务的IP地址 
         TFileStream    *fStream=new TFileStream("ipaddress.txt",fmCreate); 
         int    size=Edit1->Text.Length(); 
         fStream->Write(Edit1->Text.c_str(),size); 
         fStream->Size=size; 
         delete fStream; 
        } 
} 
//处理握手信息 
OleVariant __fastcall TmsRDM::DataSetProvider1DataRequest(TObject *Sender, 
      OleVariant &Input) 
{ 
        AnsiString      TmpStr=(WideString)Input; 
        if(TmpStr=="Close")  
Form1->SocketConnection1->Connected=false;  //与镜像服务器断开连接 
        else if(TmpStr=="Mirror") Form1->Mirror=true;  //启动镜像 
} 
五、结语 
通过分析MIDAS容错处理和负载平衡机制,提出了在多服务器系统中的服务器镜像技术,并介绍了服务器镜像技术的原理和实现方法。文中所介绍的服务器镜像技术,只需少量的代码,却能使多层应用系统既具有稳定性,又能保持数据的完整和一致性,在实际应用中加以改进和完善,将有很好的参考和利用价值。 
  			
				 |