数据库连接池的计数器设计

王朝学院·作者佚名  2016-08-27  
宽屏版  字体: 小 | 中 | 大 | 超大  

设计过ORM的攻城狮们或多或少都应该考虑过连接池,考虑过连接池就或多或少会想过计数器....

<计数器在连接池中的应用>曾经在我设计一套ORM的时候想过这样一种连接池的管理方式:

0.连接池中的所有连接都对应存在一个计数器,指示连接目前被多少对象引用;

当计数器从0变成1的时候,打开连接Connection.Open();

当计数器从1变成0的时候,关闭连接Connection.Close();1.连接池中有一个默认连接DefaultConnection,这个连接被所有的非事务操作共用,比如查(select);2.当发生一个查询操作的时候,先获得DefaultConnection,同时对应计数器+1,使用完之后DefaultConnection的计数器-1;3.当发生事务操作的时候,会从连接池中申请一个连接数为0的Connection(但不会是DefaultConnection);

如果连接池中不存在这样的连接,则会新建一个并加入到连接池中;

获得Connection后对应计数器+1,使用完之后对应计数器-1;4.如果申请事务操作时连接池已达到上限,且所有连接的计数器都大于1,则请求进入队列,直至得到Connection或超时;<计数器1.0>第一版的设计非常的简单,直接就是类似于这样的

ps:以下为示例代码,用意是便于理解,请不要太较真

classMyConnection{publicIDbConnectionConnection {get;PRivateset; }int_linkedCount;publicvoidAdd()

{vari =Interlocked.Increment(ref_linkedCount);if(i ==1)

{

Connection.Open();

}

}publicvoidRemove()

{vari =Interlocked.Decrement(ref_linkedCount);if(i ==0)

{

Connection.Close();

}

}

}classORM{publicMyConnectionConnection {get;privateset; }publicintExecuteNonQuery(stringsql)

{try{

Connection.Add();varcmd =Connection.Connection.CreateCommand();

cmd.CommandText=sql;returncmd.ExecuteNonQuery();

}finally{

Connection.Remove();

}}

}

使用

using(ORMdb =newORM())

{

db.ExecuteNonQuery("insert xxx,xxx,xx");

}

<设计缺陷>但是紧接着就出现一个问题了

如果我有一个方法,需要同时进行多个操作

比如

using(ORMdb =newORM())

{

db.ExecuteNonQuery("insert aaa");

db.ExecuteNonQuery("insert bbb");

db.ExecuteNonQuery("insert ccc");

db.ExecuteNonQuery("insert ddd");

}

这样其实已经开启关闭了4次数据库

这样的性能损耗是非常大的

所以我考虑这样的模式

using(ORMdb =newORM())

{db.Open();

db.ExecuteNonQuery("insert aaa");

db.ExecuteNonQuery("insert bbb");

db.ExecuteNonQuery("insert ccc");

db.ExecuteNonQuery("insert ddd");db.Close();

}

这样有经验的朋友一眼就可以看出更大的问题

如果insert ddd的报错了怎么办 Close就无法关闭了

换一种方式说,如果coder忘记写Close(),或者某个分支中忘记写Close()怎么办?

难道我要求所有coder都要写try..finally..?

也许你会说把Close放到using的Dispose方法中去

classORM:IDisposable{publicMyConnectionConnection {get;privateset; }publicintExecuteNonQuery(stringsql)

{try{

Connection.Add();varcmd =Connection.Connection.CreateCommand();

cmd.CommandText=sql;returncmd.ExecuteNonQuery();

}finally{

Connection.Remove();

}}publicvoidOpen()

{

Connection.Add();

}publicvoidClose()

{

Connection.Remove();

}publicvoidDispose()

{

Close();

}

}

但是,如果这样 coder已经写了Close()或者根本没写Open()不是会多触发一个Remove()?

那岂不是会出现计数器=-1,-2...-N

<计数器 N.0>其实我也不记得我尝试过多少种方案了,我只记得最终我是这样实现我想要的效果的:

0.首先,每个Add()加增的计数只有对应的Remove()可以减少为了实现这一目标,每个Add()将会返回一个对象,而Remove(token)将接受这个对象,以便于控制-1这样的操作;

vartoken = Connection.Add();//计数器+1...

...

Connection.Remove(token);//计数器-1Connection.Remove(token);//无效果Connection.Remove(token);//无效果

为了更加优化这样的效果,我将Add()的返回值设置为IDisposable

也就是说可以这样写

using(Connection.Add())

{//...//...}

或者这样写

vartoken =Connection.Add();//...//...token.Dispose();

1.在同一个线程中,只有第一次执行Add会让计数器增加,同样,只有第一次执行Add的返回对象可以减少计数器;vartoken1 = Connection.Add();//计数器+1vartoken2 = Connection.Add();//无效果vartoken3 = Connection.Add();//无效果//...//...Connection.Remove(token3);//无效果Connection.Remove(token2);//无效果Connection.Remove(token1);//计数器-1

需要实现这个效果,就必须利用LocalDataStoreSlot对象

///<summary>用于储存多线程间的独立数据///</summary>privateLocalDataStoreSlot_dataSlot =Thread.AllocateDataSlot();///<summary>增加引用,并获取用于释放引用的标记///</summary>publicIDisposableAdd()

{//如果已经存在,则不计数if(Thread.GetData(_dataSlot) !=null)//如果变量值已经存在,则说明当前线程已经执行Add方法,则返回null{returnnull;

}Thread.SetData(_dataSlot,string.Empty);//在当前线程中保存一个变量值returnnewCounterToken(this);

}///<summary>减少引用///</summary>///<param name="token">通过Add方法获取的标记对象</param>publicvoidReomve(IDisposabletoken)

{if(token ==null)

{return;

}if(tokenisCounterToken==false)

{thrownewArgumentException("参数不是一个有效的引用标记","token");

}if(token.Equals(this) ==false)//CounterToken已经重写Equals方法{thrownewArgumentOutOfRangeException("token","此标记不属于当前计数器");

}

token.Dispose();

}

其中CounterToken就是计数器的标记,实现IDisposable接口,是一个内部类

<问题解决>通过这样2部步设置,就可以实现之前无法完成的效果了

而ORM部分的代码需要稍微修改下

classORM:IDisposable{publicMyConnectionConnection {get;privateset; }publicintExecuteNonQuery(stringsql)

{//try//{//Connection.Add();using(Connection.Add())

{varcmd =Connection.Connection.CreateCommand();

cmd.CommandText=sql;returncmd.ExecuteNonQuery();

}//}//finally//{//Connection.Remove();//}//return -1;}IDisposable_counterToken;publicvoidOpen()

{if(_counterToken ==null)

{

_counterToken=Connection.Add();

}

}publicvoidClose()

{

Connection.Remove(_counterToken);

_counterToken=null;

}publicvoidDispose()

{

Close();

Connection=null;

}

}

调用的时候

using(ORMdb =newORM())

{

db.Open();

db.ExecuteNonQuery("insert aaa");

db.ExecuteNonQuery("insert bbb");

db.ExecuteNonQuery("insert ccc");

db.ExecuteNonQuery("insert ddd");

db.Close();

}

完全没有问题,只有一个Open()会增加计数器,最后一个Close()会减少计数器(如果有必要的话,他们会自动打开和关闭Connection());

关键的是,这样做我得到了一个额外的好处;

即使coder即忘记了using,也忘记了Close...

没关系,因为GC的存在,一旦CounterToken没有被任何人应用而释放掉了,那么计数器仍然会将他减掉;

<最后的封装>最后的最后,我把这个计数器从MyConection中独立出来了(其实根本就不存在什么MyConection,都是我瞎编的,只是这样说比较好理解而已,哈哈~~)

计数器分为2个模式 ,之前文章中介绍的都是多线程模式,单线程模式只是附带的一个功能而已

单线程模式:无论在任何线程中每次执行Add方法都会增加引用数,执行Remove或者token.Dispose都会减少引用数

多线程模式:在相同线程中,只有第一次执行Add方法时增加引用数,也只有此token被Remove或Dispose才会减少引用数

ps:为了使计数器和数据库组件解耦,所以我在计数器中设计了一个ValueChaged事件

usingSystem;usingSystem.Collections.Generic;usingSystem.Text;usingSystem.Threading;namespaceblqw

{///<summary>计数器,具有单线程模式和多线程模式///</summary>publicsealedclassCounter

{///<summary>构造一个计数器,默认单线程模式///<para>无论在任何线程中每次执行Add方法都会增加引用数,执行Remove或者token.Dispose都会减少引用数</para>///</summary>publicCounter()

:this(false)

{

Console.WriteLine();

}///<summary>构造一个计数器,根据参数multiThreadMode确定是否使用多线程模式///<para>多线程模式:在相同线程中,只有第一次执行Add方法时增加引用数,也只有此token被Remove或Dispose才会减少引用数</para>///</summary>///<param name="multiThreadMode"></param>publicCounter(boolmultiThreadMode)

{if(multiThreadMode)

{

_dataSlot=Thread.AllocateDataSlot();

}

}///<summary>当前引用数///</summary>privateint_value;///<summary>值改变事件///</summary>privateEventHandler<CounterChangedEventArgs>_valueChanged;///<summary>用于储存多线程间的独立数据,多线程模式下有值///</summary>privateLocalDataStoreSlot _dataSlot;///<summary>增加引用,并获取用于释放引用的标记///</summary>publicIDisposable Add()

{if(_dataSlot !=null)

{//获取当前线程中的值,此方法每个线程中获得的值都不同,不需要线程同步//如果已经存在,则不计数if(Thread.GetData(_dataSlot) !=null)

{returnnull;

}

Thread.SetData(_dataSlot,string.Empty);

}returnnewCounterToken(this);

}///<summary>减少引用///</summary>///<param name="token">通过Add方法获取的标记对象</param>publicvoidRemove(IDisposable token)

{if(token ==null)

{return;

}if(tokenisCounterToken ==false)

{thrownewArgumentException("参数不是一个有效的引用标记","token");

}if(token.Equals(this) ==false)

{thrownewArgumentOutOfRangeException("token","此标记不属于当前计数器");

}

token.Dispose();

}///<summary>当前计数值///</summary>publicintValue

{get{return_value; }

}///<summary>增加记数///</summary>privatevoidOnIncrement()

{varval = Interlocked.Increment(ref_value);

OnValueChanged(val, val-1);

}///<summary>减少计数///</summary>privatevoidOnDecrement()

{if(_dataSlot !=null)

{

Thread.SetData(_dataSlot,null);

}varval = Interlocked.Decrement(ref_value);

OnValueChanged(val, val+1);

}///<summary>触发ValueChaged事件///</summary>///<param name="value">触发Value事件时Value的值</param>///<param name="oldValue">触发Value事件之前Value的值</param>privatevoidOnValueChanged(intvalue,intoldValue)

{varhandler =_valueChanged;if(handler !=null)

{vare =newCounterChangedEventArgs(value, oldValue);

handler(this, e);

}

}///<summary>计数器值改变事件///</summary>publiceventEventHandler<CounterChangedEventArgs>ValueChanged

{

add

{

_valueChanged-=value;

_valueChanged+=value;

}

remove

{

_valueChanged-=value;

}

}///<summary>计数器引用标记,调用计数器的Add方法可获得该对象,释放对象时,减少计数器的计数值///</summary>sealedclassCounterToken : IDisposable

{///<summary>宿主计数器///</summary>privateCounter _counter;///<summary>释放标记,0未释放,1已释放,2执行了析构函数///</summary>privateint_disposeMark;///<summary>构造函数,创建引用标记并增加宿主计数器的值///</summary>///<param name="counter">宿主计数器</param>publicCounterToken(Counter counter)

{if(counter ==null)

{thrownewArgumentNullException("counter");

}

_counter=counter;

_counter.OnIncrement();

_disposeMark=0;

}///<summary>析构函数///</summary>~CounterToken()

{//如果尚未释放对象(标记为0),则将标记改为2,否则标记不变Interlocked.CompareExchange(ref_disposeMark,2,0);

Dispose();

}///<summary>释放引用标记,并减少宿主计数器的值///</summary>publicvoidDispose()

{//如果已释放(标记为1)则不执行任何操作if(_disposeMark ==1)

{return;

}//将标记改为1,并返回修改之前的值varmark = Interlocked.Exchange(ref_disposeMark,1);//如果当前方法被多个线程同时执行,确保仅执行其中的一个if(mark ==1)

{return;

}//释放Counter引用数try{

_counter.OnDecrement();

}catch{

}

_counter=null;//如果mark=0,则通知系统不需要执行析构函数了if(mark ==0)

{

GC.SuppressFinalize(this);

}

}///<summary>重新实现比较的方法///</summary>///<param name="obj"></param>///<returns></returns>publicoverrideboolEquals(objectobj)

{if(objisCounter)

{returnobject.ReferenceEquals(this._counter, obj);

}returnobject.ReferenceEquals(this, obj);

}

}

}///<summary>计数器值改变事件的参数///</summary>publicclassCounterChangedEventArgs:EventArgs

{internalCounterChangedEventArgs(intvalue,intoldValue)

{

Value=value;

OldValue=oldValue;

}///<summary>当前值///</summary>publicintValue {get;privateset; }///<summary>原值///</summary>publicintOldValue {get;privateset; }

}

}

Counter完整代码

varcounter =newCounter(true);//多线程模式//var counter = new Counter();//单线程模式newThread(() =>{using(counter.Add())//计数器+1 当前计数器=1{

Console.WriteLine("线程a:"+counter.Value);using(counter.Add())//计数器不变 当前计数器=1{

Console.WriteLine("线程a:"+counter.Value);using(counter.Add())//计数器不变 当前计数器=1{

Console.WriteLine("线程a:"+counter.Value);

Thread.Sleep(100);//等待线程b执行,b执行完之后 当前计数器=1}//计数器不变 当前计数器=1Console.WriteLine("线程a:"+counter.Value);

}//计数器不变 当前计数器=1Console.WriteLine("线程a:"+counter.Value);

}//计数器-1 当前计数器=0Console.WriteLine("线程a:"+counter.Value);

}).Start();

Thread.Sleep(50);newThread(() =>{vartoken1 = counter.Add();//计数器+1 当前计数器=2Console.WriteLine("线程b:"+counter.Value);vartoken2 = counter.Add();//计数器不变 当前计数器=2Console.WriteLine("线程b:"+counter.Value);vartoken3 = counter.Add();//计数器不变 当前计数器=2Console.WriteLine("线程b:"+counter.Value);

counter.Remove(token3);//计数器不变 当前计数器=2Console.WriteLine("线程b:"+counter.Value);

counter.Remove(token2);//计数器不变 当前计数器=2Console.WriteLine("线程b:"+counter.Value);

counter.Remove(token1);//计数器-1 当前计数器=1Console.WriteLine("线程b:"+counter.Value);

}).Start();

Console.ReadLine();

测试Demo

多线程模式测试结果

单线程模式测试结果

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
© 2005- 王朝网络 版权所有