java各种数据库连接池对比

Posted by BY annie dong on April 2, 2018

这个世道肯定是使用数据库连接池的啦…不过java里面连接池比较多…


为什么需要使用数据库连接池

对于一个简单的数据库应用,可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它。但是对于一个复杂的数据库应用,频繁的建立、关闭连接,会极大的减低系统的性能,因为对于连接的使用成了系统性能的瓶颈,数据库连接池就是为了解决这个问题。
数据库连接池的基本原理是在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。如:
外部使用者可通过getConnection 方法获取连接,使用完毕后再通过releaseConnection方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。
数据库连接池的好处:

  1. 资源重用
    2.更快的系统响应速度
    3.新的资源分配手段
    4.统一的连接管理,避免数据库连接泄漏

JDBC & Driver & 数据库连接池 的关系

JDBC

JDBC全称Java Database Connectivity,是Java官方定义的一套连接数据库的规范,里面包括各种API,其中最重要的就是关于如何获取数据库连接Connection的方法:

  1. 直接调用DriverManager类的getConnection方法
  2. 实现DataSource接口,调用实例的getConnection方法 - 数据库连接池都是使用这种方式
    通过JDBC DriverManager获取数据库连接:
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(DB_URL,USER,PASS);
Statement stmt = conn.createStatement();
String sql;
sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = stmt.executeQuery(sql);
...

Driver

通过Driver连接数据库的方式是物理层连接,必不可少
JDBC规范规定Driver类在加载时必须向DriverManager类注册自己的实例,例如MySQL实现的Driver类在类加载的时候执行static代码块,将Driver实例注册在DriverManager类中,DriverManager类会维护一个CopyOnWriteArrayList来保存所有被注册的Driver:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

数据库连接池

JDBC规范提供了一个更加灵活的获取Connection的方式,即DataSource接口,DataSource接口规定了必须实现getConnection等方法。由于这种方式比DriverManager更加抽象,用户可以自己实现获取Connection的方式,在应用层也无需知道连接究竟是如何获取的,所以在实际应用中,人们往往基于DataSource来实现连接池、分库分表中间件的功能。
虽然有了DataSource,但是数据库厂商依然要提供上述的Driver,所有的DataSource最底层建立物理连接的方式还是通过Driver类来操作,比如MysqlDataSource底层的连接、知名数据库连接池Druid的DruidAbstractDataSource的createPhysicalConnection方法:

//MysqlDataSource.java
mysqlDriver.connect(jdbcUrlToUse, props);
// DruidAbstractDataSource.java
public Connection createPhysicalConnection(String url, Properties info) throws SQLException {
    Connection conn;
    if (getProxyFilters().size() == 0) {
        conn = getDriver().connect(url, info);
    } else {
        conn = new FilterChainImpl(this).connection_connect(info);
    }

    createCount.incrementAndGet();

    return conn;
}

java数据库连接池

第一代数据库连接池

img c3p0、DBCP、Proxool和BoneCP都已经很久没更新了,TJP(Tomcat JDBC Pool),druid,HikariCP则仍处于活跃的更新中。
第一代数据库连接池性能:
img

站在巨人肩膀上的第二代连接池

img

功能全面的Druid
  1. 提供性能卓越的连接池功能
  2. 还集成了sql监控,黑名单拦截等功能,用它自己的话说,druid是“为监控而生”
  3. druid另一个比较大的优势,就是中文文档比较全面(毕竟是国人的项目么)在github的wiki页面
性能无敌的HikariCP

img

  1. 字节码精简:优化代码,直到编译后的字节码最少,这样,CPU缓存可以加载更多的程序代码
  2. 优化代理和拦截器:减少代码,例如HikariCP的Statement proxy只有100行代码,只有BoneCP的十分之一
  3. 自定义数组类型(FastStatementList)代替ArrayList:避免每次get()调用都要进行range check,避免调用remove()时的从头到尾的扫描
  4. 自定义集合类型(ConcurrentBag):提高并发读写的效率
  5. 其他针对BoneCP缺陷的优化,比如对于耗时超过一个CPU时间片的方法调用的研究

总结

对整个系统应用来说,第二代连接池在使用过程中体会到的差别是微乎其微的,基本上不存在因为连接池的自身的配饰和使用导致系统性能下降的情况,除非是在单点应用的数据库负载足够高的时候(压力测试的时候),但即便是如此,通用的优化的方式也是单点改集群,而不是在单点的连接池上死扣。
github有关于spring-boot + druid的example: https://github.com/dongjx/cattery-spring-boot-jpa-druid

参考资料

JDBC中获取Connection的两种方式
大话数据库连接池