转载

数据源连接池的原理及 Tomcat 中的应用

数据源连接池的原理及Tomcat中的应用

在Java Web开发过程中,会广泛使用到 数据源

我们基本的使用方式,是通过Spring使用类似如下的配置,来声明一个数据源:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">         <property name="driverClassName" value="${jdbc.driverClassName}" />         <property name="url" value="${jdbc.url}" />         <property name="username" value="${jdbc.username}" />         <property name="password" value="${jdbc.password}" />         <property name="maxActive" value="100" />         <property name="maxIdle" value="20" />         <property name="validationQuery" value="SELECT 1 from dual" />         <property name="testOnBorrow" value="true" />     </bean>

在之后应用里对于数据库的操作,都基于这个数据源,但这个 数据源连接池 的创建、销毁、管理,对于用户都是近乎透明的,甚至数据库连接的获取,我们都看不到 Connection 对象了。

这种方式是应用自身的数据库连接池,各个应用之间互相独立。

在类似于Tomcat这样的应用服务器内部,也有提供数据源的能力,这时的数据源,可以为多个应用提供服务。

这一点类似于以前写过关于Tomcat内部的Connector对于线程池的使用,可以各个 Connector 独立使用线程池,也可以共用配置的 Executor 。( Tomcat的Connector组件 )

那么,在Tomcat中,怎么样配置和使用数据源呢?

  1. 先将对应要使用的数据库的驱动文件xx.jar放到TOMCAT_HOME/lib目录下。

  2. 编辑 TOMCAT_HOME/conf/context.xml 文件,增加类似于下面的内容:

<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"           maxTotal="100" maxIdle="30" maxWaitMillis="10000"           username="root" password="pwd" driverClassName="com.mysql.jdbc.Driver"           url="jdbc:mysql://localhost:3306/test"/>
  1. 需要提供数据源的应用内,使用JNDI的方式获取

Context initContext = new InitialContext(); Context envContext  = (Context)initContext.lookup("java:/comp/env"); DataSource ds = (DataSource)envContext.lookup("jdbc/TestDB"); Connection conn = ds.getConnection();
  1. 愉快的开始使用数据库…

我们看,整个过程也并不比使用Spring等框架进行配置复杂,在应用内获取连接也很容易。多个应用都可以通过第3步的方式获取数据源,这使得同时提供多个应用共享数据源很容易。

这背后的是怎么实现的呢?

这个容器的连接池是怎么工作的呢,我们一起来看一看。

在根据 context.xml 中配置的Resouce初始化的时候,会调用具体DataSource对应的实现类,Tomcat内部默认使用的BasicDataSource,在类初始化的时候,会执行这样一行代码 DriverManager.getDrivers() ,其对应的内容如下,主要作用是使用 java.sql.DriverManager 实现的 Service Provider 机制,所有jar文件包含META-INF/services/java.sql.Driver文件的,会被自动发现、加载和注册,不需要在需要获取连接的时候,再手动的加载和注册。

public static java.util.Enumeration<Driver> getDrivers() {         java.util.Vector<Driver> result = new java.util.Vector<>();         for(DriverInfo aDriver : registeredDrivers) {             if(isDriverAllowed(aDriver.driver, callerClass)) {                 result.addElement(aDriver.driver);             } else {                 println("    skipping: " + aDriver.getClass().getName());             }         }         return (result.elements());     }

之后DataSourceFactory会读取 Resouce 中指定的数据源的属性,创建数据源。

在我们的应用内 getConnection 的时候,使用 ConnectionFactory 创建Connection, 注意在创建Connection的时候,重点代码是这个样子:

public PooledObject<PoolableConnection> makeObject() throws Exception {         Connection conn = _connFactory.createConnection();         initializeConnection(conn);         PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName);         return new DefaultPooledObject<>(pc);

这里的 _pool 是GenericObjectPool,连接的获取是通过其进行的。

public Connection getConnection() throws SQLException {         C conn = _pool.borrowObject(); }

在整个pool中包含几个队列,其中比较关键的一个定义如下:

private final LinkedBlockingDeque<PooledObject<T>> idleObjects;

我们再看连接的关闭,

public void close() throws SQLException {     if (getDelegateInternal() != null) {         super.close();         super.setDelegate(null);     } }

这里的关闭,并不会真的调用到Connection的close方法,我们通过上面的代码已经看到,Connection返回的时候,其实是Connection的Wrapper类。在close的时候,真实的会调用到下面的代码

// Normal close: underlying connection is still open, so we            // simply need to return this proxy to the pool            try {                _pool.returnObject(this);            } catch(IllegalStateException e) {}

所谓的 return ,是把连接放回到上面我们提到的idleObjects队列中。整个连接是放在一个LIFO的队列中,所以如果没有关闭或者超过最大空闲连接,就会加到队列中。而允许外的连接才会真实的销毁 destory

int maxIdleSave = getMaxIdle();         if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {             try {                 destroy(p);             } catch (Exception e) {                 swallowException(e);             }         } else {             if (getLifo()) {                 idleObjects.addFirst(p); // 这里。             } else {                 idleObjects.addLast(p);             }             if (isClosed()) {                 // Pool closed while object was being added to idle objects.                 // Make sure the returned object is destroyed rather than left                 // in the idle object pool (which would effectively be a leak)                 clear();             }         }

总结下:以上是Tomcat内部实现的DataSource部分关键代码。数据源我们有时候也称为 连接池 ,所谓 的概念,就是一组可以不断重用的资源,在使用完毕后,重新恢复状态,以备再次使用。

为了达到重用的效果,对于客户端的关闭操作,就不能做真实意义上的物理关闭,而是根据池的状态,以执行具体的入队重用,还是执行物理关闭

。无论连接池,还是线程池,池的原理大致都是这样的。

相关阅读: 线程池的原理 Tomcat的Connector组件 和Tomcat学设计模式 | 发布-订阅模式 猜你喜欢:

  • 深度揭秘乱码问题背后的原因及解决方式

  • WEB应用是怎么被部署的?

  • 怎样调试Tomcat源码

  • IDE里的Tomcat是这样工作的!

  • 重定向与转发的本质区别

  • 怎样阅读源代码

本公众号由曾从事应用服务器核心研发的工程师维护。文章深入Tomcat源码,分析应用服务器的实现细节,工作原理及与之相关的技术,使用技巧,工作实战等。起于Tomcat但不止于此。同时会分享并发、JVM等,内容多为原创,欢迎关注。

扫描或长按下方二维码,即可关注!

数据源连接池的原理及 Tomcat 中的应用

原文  http://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==&mid=2650859181&idx=1&sn=1805c29e51cd85e3241664a78cc2d068
正文到此结束
Loading...