受欢迎的博客标签

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

Published
在开发服务器端程序的时候,日志是必须的一个功能。由于服务器端的要频繁的把数据写入日志,开始的时候用了一个很简单日志函数 就是直接把日志字符写入文件中。然后关闭连接。一直也应用良好。但做压力测试的时候,因为要每个连接的数据都要写入日志,发现运行的一段时间后,频繁掉线,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.  .