数据库连接池的计数器设计
设计过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
多线程模式测试结果
单线程模式测试结果