高吞吐量的一个日志函数类_用于IOCP (Delphi)

 

      在开发服务器端程序的时候,日志是必须的一个功能。由于服务器端的要频繁的把数据写入日志,开始的时候用了一个很简单日志函数

就是直接把日志字符写入文件中。然后关闭连接。一直也应用良好。但做压力测试的时候,因为要每个连接的数据都要写入日志,发现运行的一段时间后,频繁掉线,CPU占用率,居高不下,优化了可以想到的很多地方,有一定的效果,仔细观察发现,硬盘灯狂闪不止,说明硬盘I/0操作过于紧张。但测试的时候,基本是不读写硬盘的,恍然发现,是日志函数影响到整个系统的性能。每一个日志数据的时候,就要打开文件,写入文件,关闭文件。哈,这些都是相对昂贵的I/0操作。优化的方法很简单,缓存数据,定期的批量写入磁盘。基于此设计思路就开发了一个新的日志类。以空间换取时间。 

     内部采用双缓冲算法,写入信息的时候,是直接写入到 内存中,然后线程根据一定的时间间隔,将内存中的数据写到磁盘文件中,里面开辟了两块缓冲内存队列,采用了生产者===》消费者模式,WriteLog 是写入日志数据,算是数据的生产者,TFileStream对象,将内存中的数据写入磁盘是消费者角色,由于采用了双缓冲方式,减少了生产与消费间的干扰. 提高了性能,减少日志的写入时间。也勉强算是个双缓冲队列的实际应用.

   此日志类是基于线程实现的。为了方便使用。内部采用了锁定机制。是线程安全的类。当数据量比较大或者为了便于日志文件的管理

我们会把数据按一定规则生成不同的日志文件名,最常见的就是按日期作日志文件的名称。例如 20110702.log, 20110701.log 等

此日志类中考虑到此情况,可以随时更改日志文件名。

  property FileName:string read getLogFileName write setLogFileName; 要修改日志文件名,直接赋值即可。也是线程安全的。

最后要说明下,此日志类的设计思路也可以用于其它方面。缓冲,空间换时间是软件设计中常用的方法。

在后来的 IOCP 模式下开发的服务器程序(ECHO 测试 12,000连接 OK),应用了此日志类。效果良好


 

//实现的代码

unit uSfLog;



interface



uses

  Windows, Messages, SysUtils, Variants, Classes;



type

  TsfLog=class(TThread)

  private

     FLF:string;//#13#10;

     FS:TFileStream;

     FCurFileName:string;

     FFileName:string;

     FBegCount:DWord;

     FBuffA,FBuffB:TMemoryStream;

     FCS:TRTLCriticalSection;

     FCS_FileName:TRTLCriticalSection;

     FLogBuff:TMemoryStream;

     procedure WriteToFile();

     function getLogFileName: string;

     procedure setLogFileName(const Value: string);

   protected

      procedure Execute();override;

   public

      constructor Create(LogFileName:string);

      destructor  Destroy();override;

      procedure WriteLog(const InBuff:Pointer;InSize:Integer);overload;

      procedure WriteLog(const Msg:string);overload;

  public

      property FileName:string read getLogFileName write setLogFileName;

  end;





implementation



{ TsfLog }



constructor TsfLog.Create(LogFileName:string);

begin

   if Trim(LogFileName) = '' then

       raise exception.Create('Log FileName not ""');



   inherited Create(TRUE);

   //\\

   InitializeCriticalSection(FCS);  //初始化

   InitializeCriticalSection(FCS_FileName);//日志文件名



  //队列缓冲区A,B运行的时候,交替使用



   Self.FBuffA := TMemoryStream.Create();

   Self.FBuffA.Size := 1024 * 1024; //初始值可以根据需要自行调整

   Self.FBuffB := TMemoryStream.Create();

   Self.FBuffB.Size := 1024 * 1024; //初始值可以根据需要自行调整

   Self.FLogBuff := Self.FBuffA;



  



   if FileExists(LogfileName) then

   begin

      FS := TFileStream.Create(LogFileName,fmOpenWrite or fmShareDenyWrite);

      FS.Position := FS.Size; //如果文件已经存在,数据进行追加

  end

  else

    FS := TFileStream.Create(LogFileName,fmCreate or fmShareDenyWrite);



   FCurFileName := LogFileName;

   FFileName    := LogFileName;



   FLF  := #13#10;



   //启动执行

   Self.Resume();

  //\\

end;



destructor TsfLog.Destroy;

begin

   FBuffA.Free();

   FBuffB.Free();

   FS.Free();

   inherited;

end;



procedure TsfLog.Execute();

begin

   FBegCount := GetTickCount();

   while(not Self.Terminated) do

   begin

       //2000ms 可以根据自己的需要调整,数据写入磁盘的间隔

      if (GetTickCount() - FBegCount) >= 2000 then

     begin

        WriteToFile();

        FBegCount := GetTickCount();

    end

    else

       Sleep(200);

   end;

    WriteToFile();

end;



function TsfLog.getLogFileName: string;

begin

   EnterCriticalSection(FCS_FileName);

   try

      Result := FCurFileName;

    finally

      LeaveCriticalSection(FCS_FileName);

    end;

end;



procedure TsfLog.setLogFileName(const Value: string);

begin

  EnterCriticalSection(FCS_FileName);

  try

    FFileName := Value;

  finally

    LeaveCriticalSection(FCS_FileName);

  end;

end;



procedure TsfLog.WriteLog(const Msg: string);

begin

  WriteLog(Pointer(Msg),Length(Msg));

end;



procedure TsfLog.WriteLog(const InBuff: Pointer; InSize: Integer);

var

    TmpStr:string;

begin

    TmpStr := FormatDateTime('YYYY-MM-DD hh:mm:ss zzz ',Now());

    EnterCriticalSection(FCS);

    try

      FLogBuff.Write(TmpStr[1],Length(TmpStr));

      FLogBuff.Write(InBuff^,InSize);

      FLogBuff.Write(FLF[1],2);

    finally

      LeaveCriticalSection(FCS);

  end;

end;



procedure TsfLog.WriteToFile;

var

    MS:TMemoryStream;

    IsLogFileNameChanged:Boolean;

begin

    EnterCriticalSection(FCS);

    //交换缓冲区

    try

      MS := nil;

      if FLogBuff.Position > 0 then

     begin

         MS := FLogBuff;

          if FLogBuff = FBuffA then FLogBuff := FBuffB

          else

             FLogBuff := FBuffA;

         FLogBuff.Position := 0;

     end;

  finally

     LeaveCriticalSection(FCS);

  end;

  //\\

    if MS = nil then

    Exit;



   //写入文件

   try

      FS.Write(MS.Memory^,MS.Position);

    finally

       MS.Position := 0;

    end;



     //检测文件名称是否变化

    EnterCriticalSection(FCS_FileName);

    try

       IsLogFileNameChanged := (FCurFileName <> FFileName);

    finally

       LeaveCriticalSection(FCS_FileName);

    end;



     //日志文件名称修改了

     if IsLogFileNameChanged then

    begin

        FCurFileName :=  FFileName;

        FS.Free();

       if FileExists(FFileName) then

      begin

         FS := TFileStream.Create(FFileName,fmOpenWrite or fmShareDenyWrite);

         FS.Position := FS.Size;

     end

     else

        FS := TFileStream.Create(FFileName,fmCreate or fmShareDenyWrite);

    end;



end;

end.



 



 



 



 



//日志类函数的测试代码



//主要测试三个功能



1)日志的写入速度是否足够快



2)日志类在多线程情况下,能稳定运行吗?



3)运行中,更换输出日志的文件名称



 



为了产生大量的数据及多线程下的稳定性,测试中产生了 120个线程,同时写日志函数。



同时用一个定时器,定期的修改输出日志的文件名。写入日志的信息是随机产生的GUID字符串。



 



 



unit uMain;



interface



uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs,uSfLog, StdCtrls, ExtCtrls,ActiveX;



type

  TfrmMain = class(TForm)

    Button1: TButton;

    Edit1: TEdit;

    Timer1: TTimer;

    procedure FormCreate(Sender: TObject);

    procedure Button1Click(Sender: TObject);

    procedure FormClose(Sender: TObject; var Action: TCloseAction);

    procedure Timer1Timer(Sender: TObject);

  private

    { Private declarations }

    FList:TList;

    LogObj:TsfLog;

  public

    { Public declarations }

  end;



  TsfLogTest=class(TThread)

  protected

    procedure Execute();override;

  end;



var

  frmMain: TfrmMain;



implementation



{$R *.dfm}



function GetGUID():string;

var

  ID:TGUID;

begin

  CoCreateGuid(ID);

  Result := GUIDToString(ID);

end;





procedure TfrmMain.FormCreate(Sender: TObject);

begin

  LogObj := TsfLog.Create('C:\temp\0001.TXT');

  FList := TList.Create();

end;



 



//启动测试



procedure TfrmMain.Button1Click(Sender: TObject);

var

  Obj:TsfLogTest;

  Index:Integer;

begin

  for Index := 1 to 120 do

  begin

    Obj := TsfLogTest.Create(FALSE);

    FList.Add(Obj);

  end;

  Self.Timer1.Enabled := TRUE;

end;



{ TsfLogTest }



procedure TsfLogTest.Execute;

var

  Msg:string;

begin

  while(not self.Terminated) do

  begin

    Msg := IntToStr(Self.ThreadID) + #09 + 

             GetGUID() + GetGUID() + GetGUID() + GetGUID() +

             GetGUID() + GetGUID() + GetGUID() + GetGUID();

    frmMain.LogObj.WriteLog(Msg);

    Sleep(10);

  end;

end;



procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);

var

  Index:Integer;

  Obj:TsfLogTest;

begin

  for Index := 0 to FList.Count - 1 do

  begin

    Obj:= TsfLogTest(FList.Items[Index]);

    Obj.Terminate();

  end;

  Sleep(100);

end;



procedure TfrmMain.Timer1Timer(Sender: TObject);

var

  AFileName:string;

begin

  AFileName :=  'C:\Temp\' +  FormatDateTime('YYYYMMDD_hhmmss_zzz',Now()) + '.TXT';

  LogObj.FileName :=  AFileName;

end;



end.

 

 

 

 

你可能感兴趣的:(Delphi)