|
数据库连接池(version : 1.2.0)使用说明:
此连接池是基于“异步的生产者消费者模型”,客户扮演的是完完全全的消费者角色,没有把任何建立连接,销毁连接对连接管理的操作交给客户去做,而是完全的让监听线程(生产者)做。无论是生产者还是消费者,在选择池的时候都会让调度程序调度到该池中,客户每次经过一定数量的尝试取得连接后,连接池管理器会根据最近的部分客户的尝试取得连接的次数作为调整因子,对连接池里面的连接作启发式调整。
这个连接池的一些功能:
1. 对过期的连接的回收,防止连接泄露
2. 支持多池提高并发量
3. 连接权限管理,只有申请该连接的客户才可用
4. 对Connection重构,close表示返回到池中
5. 异步的生产者消费者模型,最大量地减少请求者的动作负荷,减少申请释放连接的时间,提高请求连接的并发量
6. 对连接池里面的连接数量做启发式调控
生产者(监听线程)所需要处理的事件:
事件触发器(trap)要触发的事件:
1.检查此池是否被关闭了,是则强制销毁所有连接,并移除该触发器
2.检查忙队列中是否有连接需要被销毁
3.检查忙队列中是否有连接使用次数达到最大值
4.检查忙队列中是否有被关闭的连接
5.忙队列中是否有连接已经超过最大的活动时间
6.忙队列+空闲队列是否超过最大连接数
7.忙队列+空闲队列是否少于最小连接数
8.检查池管理器是否被置失效
9.根据用户作评估和启发式调整
(注意:在任何连接被回收的同时都会先回滚一次以清空之前可能剩余的操作)
(警告:监听者在处理池事件前请先锁定池,之所以用锁这个概念而不用synchronized是为了提高多个监听者效率,当某一个监听者锁定了池后,其他监听者无需要等待,而是直接调度到下一个池中)
消费者(Client)所需要做的事件:
1. 简单的向“池管理器”以一定的次数尝试取得“连接”
配置文件Sample(有某些启发式的属性并没有列明出来):
<ConnectionPool>
<DataBase>
<ListenerAmount>1</ListenerAmount>
<ListenerInterval>500</ListenerInterval>
<Name>MySql</Name>
<MaxConnection>30</MaxConnection>
<MinConnection>5</MinConnection>
<TimeForWait>1000</TimeForWait>
<TimeForConnActivity>10000</TimeForConnActivity>
<Driver>com.XXX.XXX</Driver>
<URL>mysql:jdbc//.......</URL>
<User>root</User>
<password>123456</password>
<MutiPool>5</MutiPool>
<ReConnectTimes>10</ReConnectTimes>
<Scheduler>FIFO</Scheduler>
<ConnMaxUse>20</ConnMaxUse>
<CheckConnAvail>select 1 from user </CheckConnAvail>
</DataBase>
</ConnectionPool>
属性解释:
ListenerAmount:监听者数量(就是前面问题所说的生产者,一般1个就够了)
ListenerInterval:监听者监听间隔(尽量设置在1000毫秒之内,设置的准则是生产效率能跟上消费效率,后文会详细介绍)
Name:数据库名(每一个池的编号会在此名字后面)
MaxConnection:某一个池最大连接数量(不防把这个尽量地设置大点,让池管理者对流量评估的时候作调整)
MinConnection:某一个池最小连接数量(一开始每个连接池都会达到这个连接数量,这个建议设置在你估计的平均流量附近)
TimeForConnActivity:连接最长活动时间,超过后会自动回收,防止连接泄露
Driver:驱动路径
URL:数据库地址
User:数据库用户
Password:数据库用户密码
MutiPool:多池数量(提高并发量,尽量地多点吧,有利于提高并发)
TimeForWait:重连的时候的等待时间(可以尽量少点,我是设置在20毫秒的)
ReConnectTimes:当获取连接失败时重连次数(这里建议设置 > 多池数量)
Scheduler:对用户请求连接的调度算法(负载均衡,暂时支持FIFO和RAMDOM)
EvaluateRange:评估范围(0到1之间)(启发式设置,这里不能设置太大也不能太小,太大的话调整不精确,调整频率太少。设置太小的话,调整过于频繁,增加服务器负担;测试过0.2是个不错的选择)
ConnMaxUse:一个连接最多被分配的次数,之后将会被销毁
CheckConnAvail:检查此连接访问数据库是否正常,这里应该用最简化的语句
AjustGeneRaise:调整因子放大系数(默认是1.2,最好不要改动,否则请重新评估过此时的效率)
Reduce:当调整因子确定要向下调整的时候,新的调整因子对当前评估的影响力,这个是一个int整数,表示1/Reduce的影响力(默认是200,表示200分之1的影响力,详细请看后文的介绍,同样修改过的话请重新评估此时的效率)
理论上的一秒并发量:所有池空闲连接的数量
############################################################################
示例使用:
import java.sql.Connection;
import com.vincentxie.connpool.PoolConfig;
import com.vincentxie.connpool.PoolConfigFactory;
import com.vincentxie.connpool.PoolManager;
import com.vincentxie.connpool.impl.PoolConfigFactoryImpl;
import com.vincentxie.connpool.impl.PoolManagerImpl;
import com.vincentxie.util.Log.Log;
public class test {
public static void main (String args[]) {
Log.setLevel (4); //设置输出级别[1,4],设置在4可以看包括Debug的所有输出
PoolConfigFactory pcf = new PoolConfigFactoryImpl (); //创建配置文件解释工厂
PoolManager pm = null;
try {
PoolConfig poolconfig = pcf.createPoolConfig("test.xml"); //解释配置文件
pm = new PoolManagerImpl (poolconfig); //产生池管理器
//睡眠一定的时间让池管理器把要求的连接都预先建立好
Thread.currentThread().sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
long starttime = System.currentTimeMillis();
for (int i = 1; i <= 3000; i ++) {
try {
Connection conn = pm.getConnection();
if (conn == null)
throw new Exception ("获取连接失败,很可能是现在流量突然大于最大负荷");
System.out.println ("获取连接成功.");
// you can do something here.....
Thread.currentThread().sleep(50);
conn.close(); //让连接池回收此连接
System.out.println ("关闭连接成功.");
} catch (Exception e) {
e.printStackTrace();
}
}
long endtime = System.currentTimeMillis();
endtime -= starttime;
System.out.println ("单线程完成3000次需要的时间 = " + endtime);
pm.close(); //关闭管理器并关闭所有的池和监听者
}
}
启发式部分详细介绍:
涉及到启发式调整的参数包括:
<ListenerInterval>100</ListenerInterval>
<ListenerAmount>1</ListenerAmount>
<AdjustGeneRaise>1.2</AdjustGeneRaise>
<Reduce>200</Reduce>
<EvaluateRange>0.2</EvaluateRange>
<ReConnectTimes>30</ReConnectTimes>
ListenerInterval和LinstenerAmount是保证监听者(前面所说的生产者)的效率,任何情况下都应该保证监听者的效率可以及时对新的客户信息进行评估或者是监听的事件的及时执行,大概的测试信息可以根据输出中的[Notice]启发式调整(队列大小=?)中队列大小数量进行调整,监听者效率应该保证能够及时清空此队列以取得最新的客户信息作评估,否则可能会因为评估不及时而调整有所延迟。
下面是介绍启发式调整中最重要的因素“调整因子”的计算方式:
“调整因子”的思想是:用最近的部分用户尝试取得连接的次数(也就是重连次数)除以这些用户的最大重试数的平均数作为调整因子,把此百分比对应到最小连接数到最大连接数的区间内。
调整因子 = (User1+User2+User3+User4+User5)/(5*ReConnectTimes);
不过“调整因子”只是雏形,中间还有一些调整的过程………
先说明调整因子的大概计算过程:
1)_MaxConn = MaxConn + (MaxConn - MinConn) * EvaluateRange;
2)_MinConn = MinConn - (MaxConn - MinConn) * EvaluateRange;
3)调整因子 = (User1+User2+User3+User4+User5)/(5*ReConnectTimes);
4)_调整因子 = 调整因子的放大缩小处理...........
5)目标连接数 = [(_MaxConn - _MinConn) * _调整因子] + _MinConn;
6)下限 = 目标连接数 - ((_MaxConn - _MinConn) * (EvaluateRange / 2.0));
7)上限 = 目标连接数 + ((_MaxConn - _MinConn) * (EvaluateRange / 2.0));
变量说明:
MaxConn:用户设置的最大连接数量 MinConn:用户设置的最小连接数量
_MaxConn:用于调整的最大连接数量 _MinConn:用户调整的最小连接数量
目标连接数:根据真正的调整因子计算出的应有连接数量
每步说明:
第1步:是根据用户配置的评估范围计算出用于调整的最大值(这是为了让连接数量可以达到最大数量)
第2步:同理计算出用于调整的最小值
第3步:直接得到的调整因子是最近5个用户尝试取得连接的次数的平均值
第4步:根据配置对调整因子作放大缩小处理
第5步:计算出此调整因子所得到的应有目标连接数
第6,7步:根据用户设置的评估范围计算出以“目标连接数”为中心的连接数可波动范围的上限和下限
放大缩小处理说明:
根据得到的上限和下限可以得到连接数量应处于的范围,如果现在连接数量比下限少,则需要增加连接数量,向此范围靠拢,如果大于此范围的上限,则需要作向下调整,减少连接。
如果按照这样的计算方法,对于流量是缓慢改变的曲线则有比较好的效果。但是!如果流量是突发性的呢?那就有可能出现一种情况就是:流量突然剧增,于是连接数量又突然的增加,甚至达到最大负荷,然后突然流量又剧减,那么根据以上的“调整因子”必然会把连接数量减少到很少甚至是最小负荷,这样的流量图在坐标系上就是一个分段函数!如是重复,系统必然不稳定。于是这里我们采取了一个策略:我们实在是希望让最大量的客户可以正常取得连接,而不是吝啬于几条连接的增加,尽量地降低"减少连接"的速度,尽量地减少分段函数形或者折线形的流量图对此系统所造成的不稳定。于是,在这里我们采取了“放大缩小处理”,先对“调整因子”作“放大处理”,乘一个放大系数(前文测试出的1.2),让“调整因子”更加倾向于增加连接来保证尽量大的用户数正常取得连接。当放大后的“调整因子”跟上一次的“调整因子”比较起来呈现出“向下调整”(也就是应该减少连接)的趋势的时候,我们作“缩小处理”,把这次的“调整因子”降低到200分之1的影响度(200是测试出来的,一切都可以根据实际配置调整),计算方法是:新的调整因子 = (这次的调整因子 + 199*上次的调整因子) / 200;这样就可以缓慢了减少连接的速度,让整个系统更加倾向于用户正常取得连接优先。对于当达到最大负荷的时候,生产者某法再向任何一个池增加更多的连接的时候,按照我们的策略,我们更希望系统知道我们负载不够,所以保留趋向于提高“增加连接”的机会。 具体做法是把生产者的最大重试次数(这里生产者和消费者的重试次数设置成一样)提供给池管理者,让它作为下一次评估的一部分。
配置准则:
根据以上说明,很容易就可以知道“调整因子”涉及的几个因素的意义。对于配置,首先要使得生产者(前文的监听者)的效率可以跟得上消费者(此连接池的用户)的效率,可以调节两个属性:ListenerAmount和ListenerInterval,一般ListenerAmount是设置为1的,在调节此属性前应该首选把ListenerInterval(监听频率)尽量地减少,而非增加监听者的数量。使得生产者效率跟上了消费者效率。对于ReConnectTimes(重试次数)和TimeForWait(每次重连的等待时间),应该更倾向于增加“重试次数”而保留一定的“等待时间”,这样可以更加倾向于降低用户无法取得连接的几率。对于其他涉及到的启发式参数,建议在实际环境中测试过。
并发压力测试报告:
使用以下程序得到并发效果:
public class Counter {
public static int c = 0; //出错个数
public static int threadnumber = 0; //某时刻需要处理的并发用户数
}
import java.sql.Connection;
import com.vincentxie.connpool.PoolManager;
public class takeConn extends Thread {
private PoolManager pm = null;
public takeConn (PoolManager _pm) {
pm = _pm;
}
public void run () {
int sleeptime = 1000; //事务长度
Counter.threadnumber ++;
try {
Connection conn = pm.getConnection();
if (conn == null)
throw new Exception ("获取连接失败,很可能是现在流量突然剧增");
// System.out.println ("获取连接成功.");
// you can do something here.....
Thread.currentThread().sleep(sleeptime);//模仿处理事务过程
conn.close(); //让连接池回收此连接
// System.out.println ("关闭连接成功.");
} catch (Exception e) {
Counter.c ++;
e.printStackTrace();
}
Counter.threadnumber --;
}
}
import com.vincentxie.connpool.PoolConfig;
import com.vincentxie.connpool.PoolConfigFactory;
import com.vincentxie.connpool.PoolManager;
import com.vincentxie.connpool.impl.PoolConfigFactoryImpl;
import com.vincentxie.connpool.impl.PoolManagerImpl;
import com.vincentxie.util.Log.Log;
public class test {
public static void main (String args[]) {
int max = 0;
long sleeptime = 13;
Log.setLevel (2); //设置输出级别[1,4],设置在4可以看包括Debug的所有输出
PoolConfigFactory pcf = new PoolConfigFactoryImpl ();//创建配置文件解释工厂
PoolManager pm = null;
try {
PoolConfig poolconfig = pcf.createPoolConfig("test.xml"); //解释配置文件
pm = new PoolManagerImpl (poolconfig); //产生池管理器
//睡眠一定的时间让池管理器把要求的连接都预先建立好
Thread.currentThread().sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 1; i <= 10000; i ++) {
try {
takeConn thread = new takeConn (pm);
thread.start ();
System.out.println ("Client = " + Counter.threadnumber);
if (Counter.threadnumber > max) max = Counter.threadnumber;
Thread.currentThread().sleep(sleeptime); //这里可以设置并发量,平均并发量理论值是1000/sleeptime
} catch (Exception e) {
e.printStackTrace();
}
}
pm.close(); //关闭管理器并关闭所有的池和监听者
System.out.println ("平均并发量理论值是 = " + (1000/sleeptime));
System.out.println ("Client并发量波动范围:[" + ((1000/sleeptime) - (max - (1000/sleeptime)))
+ "," + max + "]");
System.out.println ("共 " + Counter.c + "个客户无法取得连接");
}
}
程序说明:
总共包含了3个类:
Counter(计数器):记录“无法取得连接的客户的个数”和“某时刻的并发量”;
takeConn(模仿事务处理过程):通过取得连接后睡眠一定的时间来模仿事务处理;
test(测试的主程序):通过按照一定的睡眠时间来模仿一定量的并发客户连接。
说明:只需要改变程序内部的一些时间参数后执行test的main方法即可。
测试环境:
闪龙2200+
333MHZ的256M内存
在MyEclipse环境中配置执行
使用MySql数据库
1.5版本JAVA虚拟机(请保证此点)
配置文件:(为了保证测试的公平性,所以用同样的配置文件)
<ConnectionPool>