高吞吐量的一个日志函数类(Delphi)

http://blog.csdn.net/lwm08106542000/article/details/6585425

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

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

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

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

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

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

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

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


//实现的代码

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.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值