当前位置: 首页 > 科技观察

Tomcat的数据源(一)

时间:2023-03-11 23:18:08 科技观察

接上一篇《LimitLatch 在 Tomcat 中的应用》在Tomcat8之前,tomcat使用的默认数据源实现为DBCP,tomcat8之后实现的默认数据源为DBCP2。本文基于Tomcat7.0.78(DBCP1.4)分析tomcat7数据源的源码实现,TomcatJDBC连接池和DBCP2的实现将在后续文章中分析。首先我们看一下tomcat文档在宣传TomcatJDBC连接池时指出的DBCP(1.x)的不足:单线程,为了保证线程安全,需要整个连接池获取和返回对象时锁定。慢,随着CPU数量和获取、返回对象的并发线程数的增加,性能堪忧,对高并发系统影响很大。60多个类,不易维护。不支持异步取链接等1.DBCP连接的生命周期想要了解DBCP,首先要了解一个连接生命周期的各个阶段,存在于连接工厂,对象池和连接使用过程。简要说明如下:Born,对象池调用连接工厂的makeObject方法产生连接。验证,通过执行验证SQL,判断当前连接是否可用。激活,即连接的初始化,设置连接的默认值,如autoCommit等,在获取连接时调用。借用,调用对象池的borrowObject,从池中获取(或创建)一个对象实例。使用,应用在获取连接后创建Statement,提交交易等。返回,当调用连接的close方法关闭连接时,实际上是调用对象池的returnObject方法返回连接。钝化,返回连接时调用,回滚未提交的事务,清除连接警告,关闭Statement等未关闭的资源。Destroy,当返回连接时关闭连接,验证失败,或者发生异常,应该销毁连接而不是返回连接池,清理连接对应的资源,物理应该关闭连接。2.连接池的初始化当我们通过JNDI获取数据源,调用它的getConnection方法时,实际获取到的数据源实现类是BasicDataSource。BasicDataSource的主要工作是完成数据源的初始化功能。这个工作在第一次调用数据源的getConnection方法时完成。一旦这部分工作完成,获取连接的功能实际上就由PoolingDataSource类完成了。先贴一段代码:protectedsynchronizedDataSourcecreateDataSource()//同步方法,防止并发请求时创建多个连接池throwsSQLException{if(closed){thrownewSQLException("Datasourceisclosed");}//如果连接池已经初始化,直接返回toPoolingDataSource//Returnthepoolifwehavealreadycreateditif(dataSource!=null){return(dataSource);}//1.创建用于产生物理连接的连接工厂//createfactory,它返回原始物理连接ConnectionFactorydriverConnectionFactory=createConnectionFactory();//2。创建并配置连接池,即GenericObjectPool对象//createapoolforourconnectionscreateConnectionPool();//3.statement绑定存储池//Setupstatementpool,ifdesiredGenericKeyedObjectPoolFactorystatementPoolFactory=null;if(isPoolPreparedStatements()){statementPoolFactory=newGenericKeyedObjectPoolFactory(null,-1,//unlimitedmaxActive(perkey)GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,0,//maxWait1,//maxIdle(perkey)maxOpenPreparedStatements);}//4.另一个为G生产物理连接封装对象的连接工厂genericObjectPool调用//SetupthepoolableconnectionfactorycreatePoolableConnectionFactory(driverConnectionFactory,statementPoolFactory,abandonedConfig);//5。封装//CreateandreturnthepoolingdatasourcetomanagetheconnectionscreateDataSourceInstance();//6.连接初始化try{for(inti=0;igetMaxActive()-3)){removeAbandoned();//当可用连接数为toosmallor快到最大连接数时,遍历trace队列,看是否有连接超时返回}Objectobj=super.borrowObject();//从父类中获取一个连接,GenericObjectPoolif(objinstanceofAbandonedTrace){((AbandonedTrace)obj).setStackTrace();//记录堆栈,方便排错}if(obj!=null&&config!=null&&config.getRemoveAbandoned()){synchronized(trace){trace.add(obj);//获取连接成功,添加到跟踪队列}}returnobj;}GenericObjectPool有两个重要的属性:_factory和_pool。属性_factory是接口PoolableObjectFactory的一个实例,它管理对象生命周期的五个阶段:生产、销毁、激活、钝化和验证。DBCP中PoolableObjectFactory的实现类是PoolableConnectionFactory,连接池就保存在这个类中。步骤1中的所有配置和物理连接工厂等;属性_pool存储了所有实际空闲的连接,它的实现类CursorableLinkedList是CommonsCollections中的实现,是一个双向链表。GenericObjectPool获取_pool头部的对象,返回连接时根据是否使用后进先出策略将对象添加到_pool的头部或尾部。3.语句缓存池使用GenericKeyedObjectPoolFactory实现,与GenericObjectPool的方法主要思想相同,不同的是在获取和返回对象时,对应一个key,即一个key对应一个pool,而一个Connection对象对应多个A语句缓存。4.对象池工厂前面提到GenericObjectPool需要一个工厂来管理对象生命周期的一部分。在此步骤中,将生成一个PoolableConnectionFactory实例作为对象池工厂。准备好后,BasicDataSource还会调用对象池工厂的5个生命周期方法来验证整个过程是否完整无误。5.封装在这一步中,将前面准备好的GenericObjectPool池封装成一个PoolingDataSource,后续的连接获取通过PoolingDataSource的getConnection方法返回。连接实际上是在前面提到的GenericObjectPool池中获取的,然后封装为PoolGuardConnectionWrapper,在调用createStatement、commit等方法时会检查连接是否已经关闭。同理,语句在创建时也封装为DelegatingPreparedStatement、DelegatingStatement、DelegatingCallableStatement等,用于检查是否关闭,回收资源等。6.***初始化连接数,根据生成相应的连接到配置的最小连接数。3.获取连接下面重点介绍连接池中获取连接的过程,即CommonsPool中GenericObjectPool的borrowObject方法。latchlatch=newLatch();...synchronized(this){..._allocationQueue.add(latch);...allocate();}我们看到在获取池中对象的时候,不是直接从对应的_pool中取(里面存放的是空闲对象),创建一个Latch对象,然后把这个对象放到一个LinkedList中,然后调用allocate方法。LinkedList中的每一个Latch代表一个要连接的线程。allocate是一个同步方法,它做了两部分工作:1.如果有空闲对象并且等待获取对象的_allocationQueue不为空,则将两者中和。//Firstuseanyobjectsinthepooltoclearthequeuefor(;;){if(!_pool.isEmpty()&&!_allocationQueue.isEmpty()){Latchlatch=(Latch)_allocationQueue.removeFirst();//取出第一个等待线程latch.setPair((ObjectTimestampPair)_pool.removeFirst());//将池中的空闲连接分配给线程_numInternalProcessing++;synchronized(latch){latch.notify();//通知等待连接的线程}}else{break;}}2.如果还在等待获取对象的_allocationQueue不为空,并且池中的对象数还没有达到最大值,就可以创建一个新的对象。//第二次利用任何备用容量为(;;){if((!_allocationQueue.isEmpty())&&(_maxActive<0||(_numActive+_numInternalProcessing)<_maxActive)){Latchlatch=(Latch)_allocationQueue.removeFirst();latch.setMayCreate(true);//标识可以创建新的连接_numInternalProcessing++;synchronized(latch){latch.notify();}}else{break;}}这里执行,Latch实例中有三种情况:pair属性有得到了需要的对象;没有拿到对象,但是mayCreate属性为true,返回后直接新建对象;没有拿到对象,mayCreate属性为false。如果是场景3,则根据配置的策略,进行异常抛出或阻塞处理。阻塞会调用latch的wait方法,等待下一次allocate触发时的notify通知,超时失败则抛出异常。4.返回链接受篇幅限制。下面简单看下函数的主要流程。有兴趣的童鞋一定要看源码。当调用连接的close方法时,实际上是调用了PoolableConnection的close方法。检查连接是否已经关闭,如果是则直接返回。检查连接内部的实际物理连接是否已经关闭。如果是,则需要销毁连接,清理资源(语句),更新监控量。如果一切正常,连接工厂的passivateObject方法将被钝化并重置,然后返回到对象池中。5.语句缓存前面提到语句缓存池是使用GenericKeyedObjectPoolFactory实现的。当对象池真正创建连接(makeObject)时,底层的DriverConnectionFactory被PoolableObjectFactory调用创建物理连接,然后进行封装。如果配置了usage语句缓存,中间会多包一层PoolingConnection。PoolingConnection重载了prepareStatement等方法,负责在创建语句时首先获取语句缓存池。可见DBCP的语句缓存是通过层层封装(装饰模式)实现的。6.综上所述,DBCP1.X是一个古老的数据源实现,1.2版本甚至可以追溯到10年前,但时至今日,笔者在很多项目(主要是Springhosting)中依然能看到他的身影,虽然在一个另一方面,原因是项目没有开创性,这也从侧面印证了DBCP确实可以满足大部分项目的需求。在后续的数据源系列文章中,我们将继续分析Tomcat中其他数据源的实现,并进行性能测试。【本文为专栏作家“侯树成”原创稿件。转载请通过作者微信获得授权公众号《Tomcat那些事》】点此查看作者更多好文