讨论:win2000+sql2000开发中大型系统到底是用二层好还是多层好!(100分)

沙隆巴斯的主人

Unregistered / Unconfirmed
GUEST, unregistred user!
to zhousi:
1、datamodel还是removete datamodel都是无关紧要的,所有的datamodel都只是个容器(HELP里说得很清楚了)。
2、我的设计是只有一个datamodel。
3、datamodel里有个一个connection池和一个query池。关于这两个池可以见下面帖出的代码。(池的清理算法较简陋,希望同行给以改进建议)
**************************connection池*****************************************

unit UDataConnPool;

interface

uses
SysUtils, Classes, DB, ADODB, Contnrs, Windows, ExtCtrls, UBase;

type

TDataConnectionPool = class(TComponent) //数据库连接池类
private
fConnParameter : RConnParameter;
fConnList : TComponentList;
fCleanTimer : TTimer;

procedure fCleanOnTime(sender : TObject);

function fMakeConnStr : String;

function fCreateADOConn : TADOConnection;
//创建新的空闲连接

procedure fClean;
//清理 (清理长时间不用的和长时间不归还的(死的)连接)

{ Private declarations }
protected
function getConnCount: Integer;
public
{ Public declarations }
property ConnCount: Integer read getConnCount;
constructor Create(owner : TComponent; connParam : RConnParameter);overload;
function getConn : TADOConnection;
//取得空闲连接
procedure returnConn(conn : TADOConnection);
//归还连接
end;


implementation

constructor TDataConnectionPool.Create(owner : TComponent; connParam : RConnParameter);
var
index: Integer;
begin
inherited Create(owner);
fConnParameter.ConnMin := connParam.ConnMin;
fConnParameter.ConnMax := connParam.ConnMax;
fConnParameter.RefreshTime := connParam.RefreshTime;
fConnParameter.dbUser := connParam.dbUser;
fConnParameter.dbPass := connParam.dbPass;
fConnParameter.dbSource := connParam.dbSource;

if fConnList = nil then
begin
fConnList := TComponentList.Create; //创建数据库连接列表
try
for index := 1 to fConnParameter.ConnMin do //创最小连接个数个建数据库连接
begin
fConnList.Add(fCreateADOConn);
end;
except

end;
end;

if fCleanTimer = nil then
begin
fCleanTimer := TTimer.Create(Self);
fCleanTimer.Name := 'MyCleanTimer1';
fCleanTimer.Interval := fConnParameter.RefreshTime * 1000; //清理程序启动的时间间隔
fCleanTimer.OnTimer := fCleanOnTime;
fCleanTimer.Enabled := True;
end;




end;

procedure TDataConnectionPool.fClean;
var
iNow : Integer;
iCount : Integer;
index : Integer;
begin
iNow := GetTickCount;
iCount := fConnList.Count;
for index := iCount - 1 downto 0 do
begin
if TADOConnection(fConnList[index]).Tag > 0 then
begin
if fConnList.Count > fConnParameter.ConnMin then
begin
if iNow - TADOConnection(fConnList[index]).Tag > 600000 then //超过10分钟不使用的、大于连接池最小数目的空闲连接将被释放
begin
fConnList.Delete(index);
end;
end;
end
else if TADOConnection(fConnList[index]).Tag < 0 then
begin
if iNow + TADOConnection(fConnList[index]).Tag > 3600000 then //被连续使用超过1小时的连接(很可能是死连接将被)释放
begin
fConnList.Delete(index);
if fConnList.Count < fConnParameter.ConnMin then //若小于连接池最小数目,则创建新的空闲连接
begin
fConnList.Add(fCreateADOConn);
end;
end;
end
end;
end;

procedure TDataConnectionPool.fCleanOnTime(sender: TObject);
begin
fClean;
end;

function TDataConnectionPool.fCreateADOConn: TADOConnection;
begin
Result := TADOConnection.Create(Self);
Result.ConnectionString := fMakeConnStr;
Result.LoginPrompt := False;
Result.Open;
Result.Tag := GetTickCount;
end;

function TDataConnectionPool.fMakeConnStr: String;
begin
Result := 'Provider=MSDAORA.1;Password=' + fConnParameter.dbPass +
';User ID=' + fConnParameter.dbUser +
';Data Source=' + fConnParameter.dbSource + ';Persist Security Info=True';
end;

function TDataConnectionPool.getConn: TADOConnection;
var
index : Integer;
begin
Result := nil;
for index := 0 to fConnList.Count - 1 do
begin
if TADOConnection(fConnList[index]).Tag > 0 then
begin
Result := TADOConnection(fConnList[index]);
Result.Tag := - GetTickCount; //使用开始计时 (负数表示正在使用)
end;
end;

if (Result = nil) and (index < fConnParameter.ConnMax) then //无空闲连接,而连接池数目小于允许最大数目(fMax),创建新的连接
begin
try
Result := fCreateADOConn;
Result.Tag := - GetTickCount; //使用,开始计时 (负数表示正在使用)
fConnList.Add(Result);
except

end;
end;

end;

function TDataConnectionPool.getConnCount: Integer;
begin
Result := fConnList.Count;
end;

procedure TDataConnectionPool.returnConn(conn: TADOConnection);
begin
if fConnList.IndexOf(conn) > -1 then
begin
conn.Tag := GetTickCount;
end;


end;

end.


************************************query池************************************
unit UAdoQueryPool;

interface

uses
SysUtils, Classes, DB, ADODB, Contnrs, Windows, ExtCtrls, UBase;

type
TAdoQueryPool = class(TComponent) //AdoQuery缓冲池类
private
fAdoQueryMin : Integer;

fAdoQueryMax : Integer;

fAdoQueryList : TComponentList;

fCleanTimer : TTimer;

procedure fCleanOnTime(sender : TObject);
//按时整理缓冲池

function fCreateADOQuery : TADOQuery;
//创建新的AdoQuery

procedure fClean;
//整理 (清理长时间不用的和长时间不归还的AdoQuery)

{ Private declarations }
protected

public
{ Public declarations }
constructor Create(owner : TComponent); override;
function getAdoQuery : TADOQuery;
//取得空闲连接
procedure returnAdoQuery(qry : TADOQuery);
//归还连接
end;


implementation

{ TAdoQueryPool }

constructor TAdoQueryPool.Create(owner: TComponent);
var
index : Integer;
aAdoQuery : TADOQuery;
begin
inherited ;
fAdoQueryMin := 10;
fAdoQueryMax := 100;
fAdoQueryList := TComponentList.Create(False);
for index := 1 to fAdoQueryMin do
begin
fAdoQueryList.Add(fCreateADOQuery);
end;

if fCleanTimer = nil then
begin
fCleanTimer := TTimer.Create(Self);
fCleanTimer.Name := 'MyCleanTimer1';
fCleanTimer.Interval := 600 * 1000; //清理程序启动的时间间隔(10分钟)
fCleanTimer.OnTimer := fCleanOnTime;
fCleanTimer.Enabled := True;
end;
end;

procedure TAdoQueryPool.fClean;
var
iNow : Integer; //当前时刻
iCount : Integer; //List大小
index : Integer;
begin
iNow := GetTickCount;
iCount := fAdoQueryList.Count;
for index := iCount - 1 downto 0 do
begin
if TADOQuery(fAdoQueryList[index]).Tag > 0 then //若空闲
begin
if fAdoQueryList.Count > fAdoQueryMin then //若AdoQuery个数大于最小值
begin
TADOQuery(fAdoQueryList[index]).Free;
end;
end
else if TAdoQuery(fAdoQueryList[index]).Tag < 0 then
begin
if iNow + TADOQuery(fAdoQueryList[index]).Tag > 10800000 then //被连续使用超过3小时的AdoQuery(很可能是死的),释放
begin
TADOQuery(fAdoQueryList[index]).Free;
if fAdoQueryList.Count < fAdoQueryMin then //若小于缓冲池最小数目,则创建新的空闲AdoQuery
begin
fAdoQueryList.Add(fCreateADOQuery);
end;
end;
end
end;
end;

procedure TAdoQueryPool.fCleanOnTime(sender: TObject);
begin
fClean;
end;

function TAdoQueryPool.fCreateADOQuery: TADOQuery;
begin
Result := TADOQuery.Create(Self);
Result.Tag := GetTickCount; //空闲,开始计时(正数表示空闲)
end;

function TAdoQueryPool.getAdoQuery: TADOQuery;
var
index : Integer;
begin
Result := nil;
for index := 0 to fAdoQueryList.Count - 1 do
begin
if TADOQuery(fAdoQueryList[index]).Tag > 0 then
begin
Result := TADOQuery(fAdoQueryList[index]);
Result.Tag := - GetTickCount; //使用开始计时 (负数表示正在使用)
end;
end;

if (Result = nil) and (index < fAdoQueryMax) then //无空闲AdoQuery,而缓冲池数目小于允许最大数目(fAdoQueryMax),创建新的Adoquery
begin
try
Result := fCreateADOQuery;
Result.Tag := - GetTickCount; //使用,开始计时 (负数表示正在使用)
fAdoQueryList.Add(Result);
except

end;
end;

end;

procedure TAdoQueryPool.returnAdoQuery(qry: TADOQuery);
begin
if fAdoQueryList.IndexOf(qry) > -1 then
begin
qry.Tag := GetTickCount; //开始空闲计时
end;

end;

end.
 
M

mrzj

Unregistered / Unconfirmed
GUEST, unregistred user!
三层做小的应用和二层的差距还不大,甚至看不出来,数据量大了、使用的人多了,差距就出来了。

To:沙隆巴斯的主人

你提到borland公司不同意,广大delphi开发人员不同意,我只想知道,你是怎么在这么短的时间内咨询了borland公司和广大delphi人员的?你别是自己做在家里想出来的吧,现在已经很少有人说自己有特异功能了!

能用delphi开发的中间层产品好过weblogic和webshare?这些正版的中间层没个几十万下不来吧,可能世界上可能有某个人能凭一已之力做出性能超群的中间层产品来,这样的人世界上会很多吗?当然了,你可能觉得自己就是其中的一个。

我没说com不好,我讲的是用com做中间件操作数据库不好,我的意思讲了三遍了,这次应该明白我的意思了吧。

“对待厂商对自己产品的宣传,要特别小心......”
“大富翁里的问题是大部分人都被李维的那几本书误导了......”

上面这几句话是"沙隆巴斯的主人"讲过的,也是我要表达的意思,用delphi开发的中间层产品性能不好,不要相信borland公司的宣传(就连borland公司自己的delphi8的默认安装里已经没有midas了),不要被borland公司的专职作者李维写过的那几本书所误导!
我估计李维的下几本书,中间层肯定一带而过,甚至不提中间层了!

另:90年代中期的com和现在的com一样吗,肯定是不一样,但是他们都叫com,但为什么.net就不叫com了呢?DOS->win31->win95->win2000,一代一代的逐渐的发展。我们要是不会分辨,就用大多数人认可的,不要把.net叫成com什么的。在举个非常简单的例子吧,没有汽车能有飞机吗,但是汽车叫汽车,飞机叫飞机,要是某人非把飞机叫汽车,也只能自己一个人偷偷的在家叫来满足一下自己!
 
L

liuyongsheng092

Unregistered / Unconfirmed
GUEST, unregistred user!
二层比较好些,多层不太好
 

沙隆巴斯的主人

Unregistered / Unconfirmed
GUEST, unregistred user!
to mrzj:
>>你是怎么在这么短的时间内咨询了borland公司和广大delphi人员的?>>财力所限,我无法进行现场统计调查。但你所言“用开发工具delphi开发的com组件开发出的中间层产品来操作数据库是不稳定的”,此语无疑是对BORLAND公司与大部分DELPHI程序员的不敬。
对于中间件,IBM的工程师小组专程从上海过来给我们讲解MQ、PROTAL以及websphere(注意:不是webshare),希望能够采购他们的东西,那玩艺要多少钱我还是清楚的。国外符合GNU的中间件一大把,国内也有开源的完整的中间件产品(灰狐动力)。MS的COM+核心开发成员(华裔)现在已经从西雅图回国,在国家科技部支持下现在已经推出了独立的支持COM的嵌入式操作系统(可以跑在X86、ARM、MIPS三种CPU架构,感兴趣的可以去http://www.koretide.com瞧瞧)。而在我的同学里,就有人根据应用的需要在SOCKET之上自己开发一套自己的中间服务器(虽然离完整的中间价还差很远)。说这些,是希望mrzj兄在对MS的ADO佩服得五体投地之余不忘抬头看看,以免有坐井观天之虞。
》》我讲的是用com做中间件操作数据库不好》》这个意思我没有误会。在MS平台上有大把用COM实现的优秀的操作数据库的中间服务器。如果mrzj兄没见过,那真是太可惜了。.
关于.net和COM的关系,就无谓再争了。MS的研究人员在评价COM的时候,认为其唯一过时的就是对自描述支持的缺乏。
 
V

vcanddelphi

Unregistered / Unconfirmed
GUEST, unregistred user!
to 沙隆巴斯的主人:
对于COM我不赶妄下评论。
但DELPHI下的ADO编写数据库程序有些问题,ADO控件封装ADO后,有的功能老是出问题,不知是,DELPHI封装的不好,还是MS没有把ADO真正的核心功能给发布呢?
就那TAdoQuery控件的DELETE功能来说,在D5下打补丁都不行,害的我还的用SQL语句来实现删除功能,具网友说,这个问题在,D6,D7下都有问题。

对了,还有就是问一个幼稚的问题,COM+是不是建立在OLE之上的呢?
我知道ADO是建立在OLE之上封装的。如果“COM+是不是建立在OLE之上”成立的话,那他们有没有什么关系?
 
M

mstar

Unregistered / Unconfirmed
GUEST, unregistred user!
我认为 三层 好,条理清楚,也方便以后修改为支持 远程使用。
 
P

proman

Unregistered / Unconfirmed
GUEST, unregistred user!
沙隆巴斯的主人请继续发言,你的连接池的代码是简约的吗?为什么没有互斥量来保证多线程访问下的安全性?
 

沙隆巴斯的主人

Unregistered / Unconfirmed
GUEST, unregistred user!
to proman:
“为什么没有互斥量来保证多线程访问下的安全性?”通过TAG的正负就知道是在被使用还是空闲。
 
M

myredit

Unregistered / Unconfirmed
GUEST, unregistred user!
顶就顶吧
 
P

proman

Unregistered / Unconfirmed
GUEST, unregistred user!
To: 沙隆巴斯的主人
在多线程的情况下,你如何保证在你读Tag时,别的线程没有去修改它呢?结果是很有可能多个线程同时取得了一个连接.你的读取动作并没有放在同步保护中啊,我看着好象总是有问题的,也许测试中很难发现,但确实是一个问题吧.
 

沙隆巴斯的主人

Unregistered / Unconfirmed
GUEST, unregistred user!
to proman:
你说的确实可能是个隐患。DELPHI没有提供对方法的同步设置能力,不知道有什么好办法可以让某个方法不可重入(如果在方法开头设置互斥量,依然有多线程同时访问此互斥量的隐忧),还请proman兄赐教高招。
 
P

proman

Unregistered / Unconfirmed
GUEST, unregistred user!
使用TCriticalSection就可以做到。
下面是我的连接池的代码,我比较简单,还没有做计时释放的工作。

constructor TConnectionPools.Create;
begin
FConnList := TList.Create;
FCriticalSection := TCriticalSection.Create;
FTimeout := 5000;
FMaxCount := 15;
FSemaphore := CreateSemaphore(nil, FMaxCount, FMaxCount, nil);

end;

function TConnectionPools.CreateNewInstance: TADOConnection;
var
p: PRemoteConnection;
begin
Result := nil;
FCriticalSection.Enter;
try
New(p);
p.Connection := TADOConnection.Create(nil);
p.Connection.ConnectionString := ConnectionString;
p.Connection.LoginPrompt := False;
try
p.Connection.Open(DataBaseUser,DataBasePass);
except
p.Connection.Free;
Dispose(p);
Exit;
end;
p.InUse := True;
FConnList.Add(p);
Result := p.Connection;
finally
FCriticalSection.Leave;
end;
end;

destructor TConnectionPools.Destroy;
var
i: Integer;
begin
FCriticalSection.Free;
for i := 0 to FConnList.Count - 1 do
begin
PRemoteConnection(FConnList).Connection.Free;
Dispose(FConnList);
end;
FConnList.Free;
CloseHandle(FSemaphore);
inherited Destroy;
end;

function TConnectionPools.GetLock(Index: Integer): Boolean;
begin
FCriticalSection.Enter;
try
Result := not PRemoteConnection(FConnList[Index]).InUse;
if Result then
PRemoteConnection(FConnList[Index]).InUse := True;
finally
FCriticalSection.Leave;
end;
end;

function TConnectionPools.LockConnection: TADOConnection;
var
i: Integer;
begin
Result := nil;
if WaitForSingleObject(FSemaphore, Timeout) = WAIT_FAILED then
raise Exception.Create('服务器忙,请稍候再试');
for i := 0 to FConnList.Count - 1 do
begin
if GetLock(i) then
begin
Result := PRemoteConnection(FConnList).Connection;
Exit;
end;
end;
if FConnList.Count < MaxCount then
Result := CreateNewInstance;
if Result = nil then { This shouldn't happen because of the sempahore locks }
raise Exception.Create('Unable to lock Connection');
end;

procedure TConnectionPools.ReleaseLock(Index: Integer;
var Value: TADOConnection);
begin
FCriticalSection.Enter;
try
PRemoteConnection(FConnList[Index]).InUse := False;
//Value := nil;
ReleaseSemaphore(FSemaphore, 1, nil);
finally
FCriticalSection.Leave;
end;
end;

procedure TConnectionPools.SetConnectionString(const Value: string);
begin
FConnectionString := Value;
end;

procedure TConnectionPools.SetDataBasePass(const Value: string);
begin
FDataBasePass := Value;
end;

procedure TConnectionPools.SetDataBaseUser(const Value: string);
begin
FDataBaseUser := Value;
end;

procedure TConnectionPools.UnlockConnection(var Value: TADOConnection);
var
i: Integer;
begin
for i := 0 to FConnList.Count - 1 do
begin
if Value = PRemoteConnection(FConnList).Connection then
begin
ReleaseLock(i, Value);
break;
end;
end;
end;

initialization
ConnectionPools := TConnectionPools.Create;
finalization
ConnectionPools.Free;
end.
 
D

dirk

Unregistered / Unconfirmed
GUEST, unregistred user!
呵呵,我喜欢直接用API做临界区:
CS:TRTLCriticalSection;
InitializeCriticalSection(CS);
EnterCriticalSection(CS);
LeaveCriticalSection(CS);
DeleteCriticalSection(CS);
其实TCriticalSection也就封装了这几个函数。

另外想问一下楼上,用了临界区为什么还要用WaitForSingleObject?好像都是维持临界状态,WaitForSingleObject怎么用我还不太明白,望指教一下。
 
V

vcanddelphi

Unregistered / Unconfirmed
GUEST, unregistred user!
to 沙隆巴斯的主人:
对于COM我不赶妄下评论。
但DELPHI下的ADO编写数据库程序有些问题,ADO控件封装ADO后,有的功能老是出问题,不知是,DELPHI封装的不好,还是MS没有把ADO真正的核心功能给发布呢?
就那TAdoQuery控件的DELETE功能来说,在D5下打补丁都不行,害的我还的用SQL语句来实现删除功能,具网友说,这个问题在,D6,D7下都有问题。

对了,还有就是问一个幼稚的问题,COM+是不是建立在OLE之上的呢?
我知道ADO是建立在OLE之上封装的。如果“COM+是不是建立在OLE之上”成立的话,那他们有没有什么关系?
 
L

ljx072

Unregistered / Unconfirmed
GUEST, unregistred user!
我只会编两层的,但我认为在大型系统中,比如上千个甚至上万个客户端时,三层的应该比两层的有优势,我公司以前有个很大的系统是两层的,感觉速度很慢,后来改为三层,有了明显改善,两层和三层的都是同一公司开发的.这个公司开发软件不是很好(很多人这么说),如果开发得好的话,三层效率可能更高吧.
以上只是凭感觉.
 

沙隆巴斯的主人

Unregistered / Unconfirmed
GUEST, unregistred user!
to vcanddelphi:
原来使用ADO遇到过一些什么BOF、EOF错误,但D6以后就没有遇到过。抄了段文章给你。
ADO:Active数据对象(ActiveDataObjects):ADO实际是一种提供访问各种数据类型的连接机制。ADO设计为一种极简单的格式,通过ODBC的方法同数据库接口。可以使用任何一种ODBC数据源,即不止适合于SQLServer、 Oracle、Access等数据库应用程序,也适合于Excel表格、文本文件、图形文件和无格式的数据文件。ADO是基于OLE-DB之上的技术,因此ADO通过其内部的属性和方法提供统一的数据访问接口方法。

TO proman、dirk:
两位的代码非常有启发意义,以往还没注意到DELPHI自己提供了临界访问控制机制。
 
Z

zhanggeye

Unregistered / Unconfirmed
GUEST, unregistred user!
我觉得说delphi自己提供了临界访问控制机制好象不太恰当吧。
 
0

08782004

Unregistered / Unconfirmed
GUEST, unregistred user!
1000个用户,你见过这样的prg系统吗?IBM在用吗?
 
0

08782004

Unregistered / Unconfirmed
GUEST, unregistred user!
DELPHI说3好,你就说3好!delphi 说delphi 可以开发web,问题是borland 的web是用delphi开发的吗?做事情要动脑筋
 

沙隆巴斯的主人

Unregistered / Unconfirmed
GUEST, unregistred user!
to zhanggeye:
准确的说:TCriticalSection提供的只是一种防止代码段被重入的机制。
 
顶部