拓宽阅览

万字长文浅显易懂数据库衔接池 HikariCP/Commons DBCP/Tomcat/c3p0/druid 对比

从零开端手写 mybatis (三)jdbc pool 怎么从零手写完结数据库衔接池 dbcp?

万字长文浅显易懂数据库衔接池 HikariCP/Commons DBCP/Tomcat/c3p0/druid 对比

Database Connection Pool 数据库衔接池概览

c3p0 数据池入门运用教程

alibaba druid 入门介绍

数据库衔接池 HikariCP 功用为什么这么快?

Apache Tomcat DBCP(Database Connection Pool) 数据库衔接池-01-入门介绍

vibur-dbcp 并发、快速且功用齐备的 JDBC 衔接池,供给先进的功用监控功用-01-入门介绍

前语

数据库衔接池在日常开发中简直是必备的技能,可是很多常识大多比较零散。

这儿老马为大家简略做一个汇总,便于查阅学习。

衔接池的效果

资源重用

由于数据库衔接得到重用,防止了频频创立、开释衔接引起的很多功用开支。在削减体系耗费的基础上, 另一方面也增进了体系运转环境的平稳性(削减内存碎片以及数据库临时进程/线程的数量)。

更快的体系呼应速度

数据库衔接池在初始化进程中,往往现已创立了若干数据库衔接置于池中备用。此刻衔接的初始化作业均已完结。 关于业务恳求处理而言,直接运用现有可用衔接,防止了数据库衔接初始化和开释进程的时刻开支,然后缩减了体系全体呼应时刻。

新的资源分配手段

关于多运用同享同一数据库的体系而言,可在运用层通过数据库衔接的装备,运用数据库衔接池技能。 设置某一运用最大可用数据库衔接数,防止某一运用独占一切数据库资源。

一致的衔接办理,防止数据库衔接走漏

在较为齐备的数据库衔接池完结中,可依据预先设定的衔接占用超时时刻,强制回收被超时占用的衔接。 然后防止了常规数据库衔接操作中或许出现的资源走漏(当程序存在缺陷时,恳求的衔接忘掉封闭,这时候,就存在衔接走漏了)。

万字长文浅显易懂数据库衔接池 HikariCP/Commons DBCP/Tomcat/c3p0/druid 对比

常见的优异开源组件有哪些?

有关数据库衔接池的优异开源组件:

  1. HikariCP: HikariCP 是一个高功用的 JDBC 衔接池,被广泛认为是现在功用最好的 JDBC 衔接池之一。它具有快速启动、低资源耗费和高功用等特点,适用于各种规模的运用程序。

  2. Apache Commons DBCP: Apache Commons DBCP 是 Apache 软件基金会的一个子项目,供给了一个牢靠的 JDBC 衔接池完结。它支撑基本的衔接池功用,而且易于集成到各种 Java 运用程序中。

  3. Tomcat JDBC Pool: Tomcat JDBC Pool 是 Apache Tomcat 项目的一个组件,供给了一个牢靠的 JDBC 衔接池完结。它专为在 Tomcat 环境下运用而规划,但也能够作为独立的衔接池运用。

  4. H2 Database Connection Pool: H2 Database 是一个嵌入式数据库,它也供给了一个简略而有用的 JDBC 衔接池完结。虽然它首要用于嵌入式数据库的运用场景,但也能够作为独立的衔接池运用。

  5. c3p0: c3p0 是一个盛行的 JDBC 衔接池完结,具有丰厚的装备选项和牢靠的功用。它支撑衔接池的高度定制,而且在很多企业级运用中被广泛运用。

  6. Druid: Druid 是阿里巴巴开源的一个数据库衔接池完结,它不仅供给了衔接池功用,还供给了监控、计算、防火墙等高档功用。Druid 被广泛运用于大型互联网企业的出产环境中。

对比

HikariCP 2.6.0、commons-dbcp2 2.1.1、Tomcat 8.0.24、Vibur 16.1、c3p0 0.9.5.2

以下是对上述数据库衔接池组件的具体对比:

特性 HikariCP Apache Commons DBCP Tomcat JDBC Pool H2 Database Connection Pool c3p0 Druid
功用 十分高 一般 一般 一般 一般 十分高
装备简略性 中等 中等 中等 中等
可定制性 中等 中等
监控和计算功用
防火墙功用
社区活跃度 中等 中等 中等
适用场景 各种场景 一般场景 Tomcat 环境 嵌入式数据库场景 各种场景 大型互联网企业环境
是否支撑衔接池复用
支撑的数据库 一切干流数据库 一切干流数据库 一切干流数据库 H2 Database 一切干流数据库 一切干流数据库

看得出来,Druid 和 HikariCP 功用是最优异的。

不过别着急,咱们慢慢来,先看看其他的。

DBCP组件

介绍

许多Apache项目支撑与联系型数据库进行交互。为每个用户创立一个新衔接或许很耗时(一般需求多秒钟的时钟时刻),以履行或许需求毫秒级时刻的数据库业务。关于一个揭露保管在互联网上的运用程序,在一起在线用户数量或许十分大的状况下,为每个用户翻开一个衔接或许是不可行的。因而,开发人员一般期望在一切当时运用程序用户之间同享一组“池化”的翻开衔接。在任何给定时刻实际履行恳求的用户数量一般仅仅活跃用户总数的十分小的百分比,在恳求处理期间是唯一需求数据库衔接的时刻。运用程序自身登录到DBMS,并在内部处理任何用户账户问题。

现已有几个数据库衔接池可用,包含Apache产品内部和其他地方。这个Commons包供给了一个时机,来协调创立和保护一个高效、功用丰厚的包,以Apache许可证发布。

commons-dbcp2依赖于commons-pool2中的代码,以供给底层的目标池机制。

maven 引进

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-dbcp2</artifactId>
  <version>2.9.0</version>
</dependency>

代码

github.com/apache/comm…

PoolingDataSourceExample

这儿的 datasource 是池化的。

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolingDataSource;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
public class PoolingDataSourceExample {
    public static void main(String[] args) {
        //
        // First we load the underlying JDBC driver.
        // You need this if you don't use the jdbc.drivers
        // system property.
        //
        System.out.println("Loading underlying JDBC driver.");
        try {
            Class.forName("org.h2.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("Done.");
        //
        // Then, we set up the PoolingDataSource.
        // Normally this would be handled auto-magically by
        // an external configuration, but in this example we'll
        // do it manually.
        //
        System.out.println("Setting up data source.");
        DataSource dataSource = setupDataSource(args[0]);
        System.out.println("Done.");
        //
        // Now, we can use JDBC DataSource as we normally would.
        //
        Connection conn = null;
        Statement stmt = null;
        ResultSet rset = null;
        try {
            System.out.println("Creating connection.");
            conn = dataSource.getConnection();
            System.out.println("Creating statement.");
            stmt = conn.createStatement();
            System.out.println("Executing statement.");
            rset = stmt.executeQuery(args[1]);
            System.out.println("Results:");
            int numcols = rset.getMetaData().getColumnCount();
            while(rset.next()) {
                for(int i=1;i<=numcols;i++) {
                    System.out.print("t" + rset.getString(i));
                }
                System.out.println("");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (rset != null)
                    rset.close();
            } catch (Exception e) {
            }
            try {
                if (stmt != null)
                    stmt.close();
            } catch (Exception e) {
            }
            try {
                if (conn != null)
                    conn.close();
            } catch (Exception e) {
            }
        }
    }
    // 这儿的 datasource 是池化的。
    public static DataSource setupDataSource(String connectURI) {
        //
        // First, we'll create a ConnectionFactory that the
        // pool will use to create Connections.
        // We'll use the DriverManagerConnectionFactory,
        // using the connect string passed in the command line
        // arguments.
        //
        ConnectionFactory connectionFactory =
            new DriverManagerConnectionFactory(connectURI, null);
        //
        // Next we'll create the PoolableConnectionFactory, which wraps
        // the "real" Connections created by the ConnectionFactory with
        // the classes that implement the pooling functionality.
        //
        PoolableConnectionFactory poolableConnectionFactory =
            new PoolableConnectionFactory(connectionFactory, null);
        //
        // Now we'll need a ObjectPool that serves as the
        // actual pool of connections.
        //
        // We'll use a GenericObjectPool instance, although
        // any ObjectPool implementation will suffice.
        //
        ObjectPool<PoolableConnection> connectionPool =
                new GenericObjectPool<>(poolableConnectionFactory);
        // Set the factory's pool property to the owning pool
        poolableConnectionFactory.setPool(connectionPool);
        //
        // Finally, we create the PoolingDriver itself,
        // passing in the object pool we created.
        //
        PoolingDataSource<PoolableConnection> dataSource =
                new PoolingDataSource<>(connectionPool);
        return dataSource;
    }
}

更多内容,可参阅

apache commons dbcp2

c3p0

是什么?

c3p0是一个易于运用的库,通过运用jdbc3规范和jdbc2的可选扩展界说的功用来扩展传统JDBC驱动程序,然后使其“企业安排妥当”。

从0.9.5版开端,c3p0完全支撑jdbc4规范。

特别是c3p0供给了一些有用的服务:

一个类,它使传统的依据DriverManager的JDBC驱动程序习惯最新的javax.sql.DataSource计划,以获取数据库衔接。

DataSources后边的Connection和PreparedStatement的通明池能够“包装”传统驱动程序或任意非池化DataSources。

该库尽力使细节正确:

c3p0数据源既可引证也可序列化,因而合适绑定到各种依据JNDI的命名服务。

检入池中的Connections和Statements时,会仔细整理Statement和ResultSet,以防止客户端运用仅整理其Connections的惰性但常见的资源办理战略时资源耗尽。

该库采用JDBC 2和3规范界说的办法(即便这些与库作者的首选项冲突)。

数据源以JavaBean款式编写,供给了一切必需和大多数可选特点(以及一些非规范特点)以及无参数结构函数。

完结了一切JDBC界说的内部接口(ConnectionPoolDataSource,PooledConnection,生成ConnectionEvent的Connection等)。

您能够将c3p0类与兼容的第三方完结混合运用(尽管并非一切c3p0功用都能够与ConnectionPoolDataSource的外部完结一同运用)。

c3p0期望供给的数据源完结不合适大批量“ J2EE企业运用程序”运用。

maven 导入

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.29</version>
</dependency>

入门代码

通过代码显式指定装备:

ComboPooledDataSource source = new ComboPooledDataSource();
source.setDriverClass("com.mysql.jdbc.Driver");
source.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8");
source.setUser("root");
source.setPassword("123456");
//获取链接
Connection connection = source.getConnection();
System.out.println(connection.getCatalog());
  • 日志输出
七月 17, 2020 4:58:21 下午 com.mchange.v2.log.MLog
信息: MLog clients using java 1.4+ standard logging.
七月 17, 2020 4:58:22 下午 com.mchange.v2.c3p0.C3P0Registry 
信息: Initializing c3p0-0.9.5.5 [built 11-December-2019 22:18:33 -0800; debug? true; trace: 10]
七月 17, 2020 4:58:22 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1bqqx35abpix6b312lrdzj|7bfcd12c, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1bqqx35abpix6b312lrdzj|7bfcd12c, idleConnectionTestPeriod -> 0, initialPoolSize -> 2, jdbcUrl -> jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 30, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 10, maxStatements -> 50, maxStatementsPerConnection -> 0, minPoolSize -> 2, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
test

更多内容,可参阅

c3p0

tomcat jdbc pool

是什么?

Apache Tomcat DBCP(Database Connection Pool)是一个用于办理数据库衔接的组件,一般与Apache Tomcat服务器一同运用。

它供给了一种机制来有用地办理数据库衔接,以便在高负载下供给更好的功用和可伸缩性。

以下是Tomcat DBCP的一些要害特性和作业原理:

  1. 衔接池办理: Tomcat DBCP通过创立和保护一组预先装备的数据库衔接来办理衔接池。这些衔接在需求时能够被运用程序运用,并在不再需求时开释回池中。

  2. 衔接池参数装备: 能够通过Tomcat的装备文件(如context.xml)或许直接在运用程序中的代码中装备衔接池的各种参数,例如最大衔接数、最小衔接数、最大等待时刻等。

  3. 衔接池的作业流程: 当运用程序需求与数据库进行交互时,它从衔接池中恳求一个数据库衔接。假如衔接池中有空闲的衔接可用,衔接池会将一个衔接分配给运用程序。一旦运用程序完结了对数据库的操作,它将衔接返回给衔接池,以供其他运用程序运用。

  4. 衔接验证: Tomcat DBCP能够装备为在从衔接池中获取衔接时验证衔接的有用性。这能够通过履行简略的SQL查询或其他方式的衔接测验来完结。这有助于确保从池中获取的衔接是可用和有用的。

  5. 功用优化: 通过保护一组现已翻开的数据库衔接,Tomcat DBCP能够防止在每次数据库恳求时都从头创立和销毁衔接,然后进步了功用和效率。

  6. 异常处理: Tomcat DBCP能够处理数据库衔接的异常状况,例如数据库服务器断开衔接或许衔接超时。它会测验从头建立衔接或许返回错误信息,以便运用程序能够适当地处理这些异常状况。

  7. 监控和办理: Tomcat DBCP供给了监控和办理衔接池的功用,能够通过JMX(Java Management Extensions)接口来检查衔接池的状况、活动衔接数、空闲衔接数等信息,而且能够通过办理工具对衔接池进行操作。

为什么 tomcat 要自研,而不是用 apache dbcp 这些已有的?

Apache Tomcat 一开端的确运用了像 Commons DBCP 和 Commons Pool 这样的外部组件来办理数据库衔接池。

可是,后来 Apache Tomcat 团队决议开发自己的衔接池完结,即 Tomcat DBCP。

这是有几个原因的:

  1. 更好的集成: 将衔接池功用直接集成到 Tomcat 中能够供给更好的功用和更好的集成。这样做能够更好地与 Tomcat 内部的线程办理、类加载器和上下文生命周期等功用集成,以便供给更一致和更牢靠的衔接池办理。

  2. 功用优化: Apache Tomcat 团队能够更深化地了解 Tomcat 自身的内部作业原理,以优化衔接池的功用,使其更合适与 Tomcat 一同运用。自己完结的衔接池或许会针对 Tomcat 的特定需求进行优化,以供给更好的功用和牢靠性。

  3. 更好的操控: 通过开发自己的衔接池完结,Apache Tomcat 团队能够更好地操控衔接池的开发和保护进程。他们能够依据自己的需求进行定制和扩展,而不受外部库的限制。

  4. 解决特定问题: 有时候外部库或许存在一些限制或许问题,而开发自己的完结能够更灵活地解决这些问题。或许是由于在特定的运用状况下,已有的库无法满意 Tomcat 的需求,或许为了解决一些已知的问题而决议开发自己的完结。

入门比如

  1. 确保你现已在Tomcat的lib目录中包含 commons-dbcp.jarcommons-pool.jar

  2. 在你的Web运用程序的WEB-INF目录下创立一个名为context.xml的文件,并在其间装备数据库衔接池。

以下是一个示例context.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
               maxActive="100" maxIdle="30" maxWait="10000"
               username="#{username}" password="#{password}"
               driverClassName="com.mysql.jdbc.Driver"
               url="jdbc:mysql://localhost:3306/#{database}"/>
</Context>
  1. 在你的Web运用程序中,你能够通过JNDI查找来获取数据库衔接。以下是一个简略的Servlet示例,演示怎么获取数据库衔接并履行查询:
import java.io.*;
import java.sql.*;
import javax.naming.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.sql.*;
public class MyServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        Connection conn = null;
        try {
            // 查找上下文中的数据库衔接池
            Context ctx = new InitialContext();
            DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/TestDB");
            // 从衔接池获取衔接
            conn = ds.getConnection();
            // 履行查询
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT * FROM your_table");
            while (rs.next()) {
                out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name"));
                out.println("<br/>");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 封闭衔接
            try {
                if (conn != null)
                    conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

拓宽阅览:

更多细节,拜见 tomcat dbcp

vibur dbcp

是什么?

Vibur DBCP 是一个并发、快速且功用齐备的 JDBC 衔接池,供给先进的功用监控功用,包含慢 SQL 查询的检测和记载、运用线程的非饥饿确保、语句缓存以及与 Hibernate 集成等特性。

该项目主页包含了对一切 Vibur 特性和装备选项的具体描述,以及与 Hibernate 和 Spring 的各种装备示例等内容。

Vibur DBCP 依据 Vibur Object Pool 构建,后者是一个通用的并发 Java 目标池。

特性

首要特点一览

  • 确保没有线程会被排除在拜访 JDBC 衔接池衔接之外。拜见 poolFair 装备参数。

  • 检测和记载慢 SQL 查询、大于预期的 ResultSet 和持续时刻较长的 getConnection() 办法调用。检查相关的装备特点 这儿 和 这儿。

  • 支撑 Hibernate 3.6、4.x 和 5.x 的集成。

  • 对 JDBC Statement(Prepared 和 Callable)进行缓存支撑。

  • 运用规范 Java 并发工具和动态署理构建,不运用任何 synchronized 块或办法。

  • Vibur DBCP 需求 Java 1.6+,而且仅有以下外部依赖项:其专用目标池、slf4j/log4j 和 ConcurrentLinkedHashMap。CLHM 依赖项是可 选的,只要在启用/运用 JDBC Statement 缓存时,运用程序才需求供给它。

其他特点

  • 智能池巨细调整 – 依据最近运用的衔接数量的启发式办法,能够削减 JDBC 池中的空闲衔接数量。

  • 支撑验证距离;即,在每次运用之前,从 JDBC 池获取的衔接并不会被验证,只要在衔接上一次运用后通过一定时刻后才会进行验证。

  • 能够通过调用署理的 unwrap 办法从相应的署理目标中检索原始 JDBC 衔接或 Statement 目标。

  • 为当时获取的一切 JDBC 衔接供给记载(通过 JMX 或日志文件),包含它们被获取时的仓库盯梢;假如调试丢失/未封闭的衔接或许运用程序 想知道当时一切衔接的来源,这将十分有用。

  • JMX 支撑 – 池注册了一个 MBean,通过它能够调查和/或设置各种池参数。

maven 依赖

<dependency>
  <groupId>org.vibur</groupId>
  <artifactId>vibur-dbcp</artifactId>
  <version>25.0</version>
</dependency>

Spring with Hibernate 3.6/4.x/5.x Configuration Snippet

<!-- Vibur DBCP dataSource bean definition: -->
<bean id="dataSource" class="org.vibur.dbcp.ViburDBCPDataSource" init-method="start" destroy-method="terminate">
   <property name="jdbcUrl" value="jdbc:hsqldb:mem:sakila;shutdown=false"/>
   <property name="username" value="sa"/>
   <property name="password" value=""/>
   <property name="poolInitialSize">10</property>
   <property name="poolMaxSize">100</property>
   <property name="connectionIdleLimitInSeconds">30</property>
   <property name="testConnectionQuery">isValid</property>
   <property name="logQueryExecutionLongerThanMs" value="500"/>
   <property name="logStackTraceForLongQueryExecution" value="true"/>
   <property name="statementCacheMaxSize" value="200"/>
</bean>
<!-- For Hibernate5 set the sessionFactory class below to org.springframework.orm.hibernate5.LocalSessionFactoryBean -->
<!-- For Hibernate4 set the sessionFactory class below to org.springframework.orm.hibernate4.LocalSessionFactoryBean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
   <property name="dataSource" ref="dataSource"/>
   <property name="packagesToScan" value="the.project.packages"/>
   <property name="hibernateProperties">
   <props>
      <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
      <prop key="hibernate.cache.use_second_level_cache">false</prop>
      <prop key="hibernate.cache.use_query_cache">true</prop>
   </props>
   </property>
</bean>
<!-- For Hibernate5 set the transactionManager class below to org.springframework.orm.hibernate5.HibernateTransactionManager -->
<!-- For Hibernate4 set the transactionManager class below to org.springframework.orm.hibernate4.HibernateTransactionManager -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
   <property name="sessionFactory" ref="sessionFactory"/>
</bean>

Programming Configuration Snippet

public DataSource createDataSourceWithStatementsCache() {
    ViburDBCPDataSource ds = new ViburDBCPDataSource();
    ds.setJdbcUrl("jdbc:hsqldb:mem:sakila;shutdown=false");
    ds.setUsername("sa");
    ds.setPassword("");
    ds.setPoolInitialSize(10);
    ds.setPoolMaxSize(100);
    ds.setConnectionIdleLimitInSeconds(30);
    ds.setTestConnectionQuery("isValid");
    ds.setLogQueryExecutionLongerThanMs(500);
    ds.setLogStackTraceForLongQueryExecution(true);
    ds.setStatementCacheMaxSize(200);
    ds.start();
    return ds;
}       

vibur 的功用相比较其他的,算是比较优异的。可是 github star 比较少。

装备项特别多,感兴趣的话 拜见 vibur dbcp

alibaba druid

这个在国内应该算是众所周知了,介绍的文章十分多,这儿只做简略介绍。

是什么

Druid是Java言语中最好的数据库衔接池。Druid能够供给强大的监控和扩展功用。

PS:国内的话,仍是十分引荐运用这个的。

一些优异的能力

  1. 高功用:Druid 衔接池被规划为高功用的衔接池,具有优异的衔接获取、偿还速度以及低推迟的特点,能够满意高并发的数据库拜访需求。

  2. 实时监控:Druid 衔接池供给了丰厚的实时监控功用,能够实时地监控衔接池的状况、功用指标以及数据库拜访状况,协助用户及时发现和解决潜在的问题。

  3. 衔接池扩展:Druid 衔接池支撑衔接池的动态扩展和收缩,能够依据实际的数据库拜访负载主动调整衔接池的巨细,进步资源运用率。

  4. SQL防火墙:Druid 衔接池内置了 SQL 防火墙功用,能够对用户提交的 SQL 进行实时的安全检查和过滤,防止 SQL 注入等安全问题。

  5. 衔接走漏检测:Druid 衔接池能够检测衔接的走漏状况,及时发现并处理衔接未正确封闭的状况,防止因衔接走漏导致的数据库资源糟蹋和功用下降。

  6. 完善的计算功用:Druid 衔接池供给了丰厚的计算功用,能够计算衔接池的运用状况、功用指标以及数据库拜访状况,协助用户深化了解数据库拜访的状况。

  7. 多数据源支撑:Druid 衔接池支撑多种类型的数据库,包含 MySQL、Oracle、PostgreSQL 等,能够灵活习惯不同类型的数据库拜访需求。

maven

<dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid</artifactId>
     <version>1.2.15</version>
</dependency>

装备

DruidDataSource大部分特点都是参阅DBCP的,假如你原来便是运用DBCP,迁移是十分便利的。

 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
     <property name="url" value="${jdbc_url}" />
     <property name="username" value="${jdbc_user}" />
     <property name="password" value="${jdbc_password}" />
     <property name="filters" value="stat" />
     <property name="maxActive" value="20" />
     <property name="initialSize" value="1" />
     <property name="maxWait" value="6000" />
     <property name="minIdle" value="1" />
     <property name="timeBetweenEvictionRunsMillis" value="60000" />
     <property name="minEvictableIdleTimeMillis" value="300000" />
     <property name="testWhileIdle" value="true" />
     <property name="testOnBorrow" value="false" />
     <property name="testOnReturn" value="false" />
     <property name="poolPreparedStatements" value="true" />
     <property name="maxOpenPreparedStatements" value="20" />
     <property name="asyncInit" value="true" />
 </bean>

这个是 spring 的装备,其实装备上便是一个 POJO

感兴趣的话,能够拓宽一下:

alibaba druid-01-intro 入门介绍

alibaba druid-02-FAQ druid 常见问题

druid+mysql 个人实战比如

这儿以 durid 做一个实战比如,其他的也都大同小异。

mysql 数据预备

建表语句

use test;
CREATE TABLE "users" (
  "id" int(11) NOT NULL,
  "username" varchar(255) NOT NULL,
  "email" varchar(255) NOT NULL,
  PRIMARY KEY ("id")
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

插入数据

insert into users (id, username, email) values (1, 'u-1', '1@email.com');
insert into users (id, username, email) values (2, 'u-2', '2@email.com');
insert into users (id, username, email) values (3, 'u-3', '3@email.com');

数据承认:

mysql> select * from users;
+----+----------+-------------+
| id | username | email       |
+----+----------+-------------+
|  1 | u-1      | 1@email.com |
|  2 | u-2      | 2@email.com |
|  3 | u-3      | 3@email.com |
+----+----------+-------------+
3 rows in set (0.00 sec)

数据库预备

maven 引进

<!-- MySQL JDBC Driver -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version> <!-- 或许最新版别 -->
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
</dependency>

入门代码

package com.github.houbb.calcite.learn.mysql;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
 * druid 整合 mysql 运用
 * @author 老马啸西风
 */
public class DruidMySQLExample {
    public static void main(String[] args) {
        // 初始化 Druid 数据源
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");
        dataSource.setUsername("admin");
        dataSource.setPassword("123456");
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 从衔接池获取数据库衔接
            conn = dataSource.getConnection();
            // 创立 Statement 目标
            stmt = conn.createStatement();
            // 履行 SQL 查询
            rs = stmt.executeQuery("SELECT * FROM users");
            // 遍历成果集
            while (rs.next()) {
                // 处理每一行数据
                int id = rs.getInt("id");
                String username = rs.getString("username");
                String email = rs.getString("email");
                // 输出到操控台
                System.out.println("ID: " + id + ", username: " + username+ ", email: " + email);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 封闭资源
            try {
                if (rs != null) rs.close();
                if (stmt != null) stmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

输出如下:

ID: 1, username: u-1, email: 1@email.com
ID: 2, username: u-2, email: 2@email.com
ID: 3, username: u-3, email: 3@email.com

我原本认为 druid 现已天下无敌了,没想到 HikariCP 愈加骁勇。

HikariCP 是谁的部将?

HikariCP

是什么?

快速、简略、牢靠。HikariCP 是一个“零额定开支”的出产安排妥当的 JDBC 衔接池。

该库巨细约为130Kb,十分轻量级。

构件 Artifacts

Java 11+ Maven 构件

<dependency>
   <groupId>com.zaxxer</groupId>
   <artifactId>HikariCP</artifactId>
   <version>5.1.0</version>
</dependency>

Java 8 Maven 构件 (保护形式)

<dependency>
   <groupId>com.zaxxer</groupId>
   <artifactId>HikariCP</artifactId>
   <version>4.0.3</version>
</dependency>

装备运用

这个返回比较简略,都是一致的。

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons");
config.setUsername("bart");
config.setPassword("51mp50n");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
HikariDataSource ds = new HikariDataSource(config);

Hikari 为什么这么快?

为什么 Hikari 能够做到功用基本无损耗?到底是怎么完结的。

这也是本文的重点,也是老马为什么写这篇文章的原因。

咱们深化到你的字节码中

为了使 HikariCP 的速度达到现在的水平,咱们进行了字节码级别的工程处理,乃至更进一步。咱们采用了咱们所知的一切技巧来协助 JIT 协助您。

咱们研究了编译器的字节码输出,乃至 JIT 的汇编输出,以将要害的程序例程限制在 JIT 内联阈值以下。

咱们展平了继承层次结构,躲藏了成员变量,消除了类型转换。

微优化

HikariCP 包含许多微优化,单独看每个优化简直无法丈量,但总体上对功用有所提高。其间一些优化是以每百万次调用摊销的毫秒为单位进行衡量的。

ArrayList

一个十分重要(就功用而言)的优化是在用于盯梢翻开的 Statement 实例的 ConnectionProxy 中消除对 ArrayList<Statement> 实例的运用。

当封闭 Statement 时,有必要从此调集中删去它,当封闭 Connection 时,有必要迭代该调集并封闭任何翻开的 Statement 实例,终究有必要清空该调集。关于一般用途而言,Java 的 ArrayList 每次履行 get(int index) 调用时都会进行规模检查,这是明智的做法。可是,由于咱们能够对咱们的规模供给确保,所以这个检查仅仅额定开支。

此外,remove(Object) 完结履行自始至终的扫描,可是 JDBC 编程中常见的形式是在运用后当即封闭 Statement,或许按翻开次序的相反次序封闭。关于这些状况,从尾部开端的扫描将履行得更好。

因而,ArrayList<Statement> 被替换为一个自界说类 FastList,它消除了规模检查,并履行从尾部到头部的移除扫描。

PS:这一点在很多工具中能够简略,相对是一个能够想到的优化计划。

ConcurrentBag

HikariCP 包含一个名为 ConcurrentBag 的自界说无锁调集。这个主意是从 C# .NET 的 ConcurrentBag 类借来的,但内部完结是相当不同的。

ConcurrentBag 供给…

  • 无锁规划

  • 线程本地缓存

  • 行列盗取

  • 直接传递优化

…这导致了高度并发性、极低的推迟和最小化的伪同享现象的产生。

PS: 无锁乃是加锁的最高境地,值得以后一致深化学习一下。

调用:invokevirtual vs invokestatic

为了为 Connection、Statement 和 ResultSet 实例生成署理,HikariCP 最初运用一个单例工厂,ConnectionProxy 的状况下保存在静态字段(PROXY_FACTORY)中。

以下是十多个相似以下办法的办法:

public final PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
{
    return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
}

运用原始的单例工厂,生成的字节码如下所示:

    public final java.sql.PreparedStatement prepareStatement(java.lang.String, java.lang.String[]) throws java.sql.SQLException;
    flags: ACC_PRIVATE, ACC_FINAL
    Code:
      stack=5, locals=3, args_size=3
         0: getstatic     #59                 // Field PROXY_FACTORY:Lcom/zaxxer/hikari/proxy/ProxyFactory;
         3: aload_0
         4: aload_0
         5: getfield      #3                  // Field delegate:Ljava/sql/Connection;
         8: aload_1
         9: aload_2
        10: invokeinterface #74,  3           // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;
        15: invokevirtual #69                 // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;
        18: return

能够看到首先是对静态字段 PROXY_FACTORY 的 getstatic 调用,以及(终究)对 ProxyFactory 实例上的 getProxyPreparedStatement() 的 invokevirtual 调用。

咱们消除了单例工厂(由 Javassist 生成)并用具有静态办法的终究类替换了它(其办法体由 Javassist 生成)。

Java 代码变为:

    public final PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
    {
        return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
    }

其间 getProxyPreparedStatement() 是在 ProxyFactory 类中界说的静态办法。生成的字节码如下所示:

private final java.sql.PreparedStatement prepareStatement(java.lang.String, java.lang.String[]) throws java.sql.SQLException;
flags: ACC_PRIVATE, ACC_FINAL
Code:
  stack=4, locals=3, args_size=3
     0: aload_0
     1: aload_0
     2: getfield      #3                  // Field delegate:Ljava/sql/Connection;
     5: aload_1
     6: aload_2
     7: invokeinterface #72,  3           // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;
    12: invokestatic  #67                 // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;
    15: areturn

这儿有三件事值得注意:

getstatic 调用消失了。

invokevirtual 调用被替换为更简略由 JVM 优化的 invokestatic 调用。

终究,或许乍一看没有注意到的是,栈的巨细从 5 个元素削减到 4 个元素。这是由于在 invokevirtual 的状况下,隐式传递了 ProxyFactory 实例(即 this)到栈上,而且在调用 getProxyPreparedStatement() 时栈上的值还有一个额定的(看不见的)弹出操作。

总的来说,这个改变消除了一个静态字段拜访,一个推送和从栈中弹出的操作,而且由于调用点确保不会更改,使得调用更简略由 JIT 进行优化。

PS: 老实说,这个优化点实在是太高了,有 15 楼那么高,一般开发者根本不会有这个高度。

_(ツ)_/ 是的,但仍是…

在咱们的基准测验中,明显咱们正在运转针对一个存根 JDBC 驱动程序完结,因而 JIT 进行了很多内联。

可是,在基准测验中,其他衔接池也在存根级别进行相同的内联。所以,对咱们来说没有固有的优势。

可是,在运用真实驱动程序时,内联肯定是方程式的重要部分,这引出了另一个话题…

⏱ 调度器量子

一些轻松的阅览材料。

总结起来,明显,当你“一起”运转 400 个线程时,除非你有 400 个中心,否则你实际上并没有“一起”运转它们。操作体系,运用 N 个 CPU 中心,在你的线程之间切换,给每个线程一个小的“切片”时刻来运转,称为量子。

在许多运用程序中运转很多线程时,当你的时刻片用完时(作为一个线程),或许要“很长时刻”才能再次得到调度程序的运转时机。因而,在其时刻片内,线程尽或许多地完结作业,防止强制抛弃时刻片的锁,否则将会产生功用丢失。而且不是一点点。

这就引出了…

CPU 缓存行失效

当你无法在量子内完结作业时,另一个很大的影响便是 CPU 缓存行失效。

假如你的线程被调度程序抢占,当它再次有时机运转时,它经常拜访的一切数据很或许不再位于中心的 L1 或中心对的 L2 缓存中。更有或许是由于你无法操控下次将被调度到哪个中心。

这两点涉及到一些计算机自身的常识,感兴趣的话,能够看一下老马的翻译文章:

HikariCP 拓宽阅览之伪同享 (False sharing)

HikariCP 拓宽阅览 cpu 调度 / CPU Scheduling

伪同享这一点曾经李大狗的数据结构源码解析中也提到过,算得上是优化底层的老油条了。

数据源写到这儿基本结束了,可是呢。

纸上得来终觉浅,绝知此事要躬行

假如让咱们自己完结一个 dbcp 数据库衔接池呢?

简略版手动完结

自己完结一个简化版,便于了解原理。

简略完结

  • 衔接池接口
public interface IPool {
    /**
     * 获取新的数据库链接
     * @return 数据库链接
     */
    PoolConnection getPoolConnection();
}

其间 PoolConnection 如下:

public class PoolConnection {
    /**
     * 是否繁忙
     */
    private volatile boolean isBusy;
    /**
     * 数据库链接信息
     */
    private Connection connection;
}
  • 中心完结
public class PoolImpl implements IPool {
    /**
     * 数据库驱动
     */
    private final String jdbcDriver;
    /**
     * 数据库衔接
     */
    private final String jdbcUrl;
    /**
     * 数据库用户名
     */
    private final String username;
    /**
     * 数据库密码
     */
    private final String passowrd;
    /**
     * 衔接池巨细
     */
    private final int size;
    /**
     * 数据库衔接池列表
     */
    private List<PoolConnection> poolConnections = new ArrayList<>();
    public PoolImpl(String jdbcDriver, String jdbcUrl, String username, String passowrd, int size) {
        this.jdbcDriver = jdbcDriver;
        this.jdbcUrl = jdbcUrl;
        this.username = username;
        this.passowrd = passowrd;
        this.size = size;
        init();
    }
    private void init() {
        try {
            //1. 注册数据库衔接信息
            Driver sqlDriver = (Driver) Class.forName(jdbcDriver).newInstance();
            DriverManager.registerDriver(sqlDriver);
            //2. 初始化衔接池
            initConnectionPool();
        } catch (InstantiationException | IllegalAccessException | SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    /**
     * 初始化链接
     * @throws SQLException sql 异常
     */
    private void initConnectionPool() throws SQLException {
        for(int i = 0; i < size; i++) {
            Connection connection = DriverManager.getConnection(jdbcUrl, username, passowrd);
            PoolConnection poolConnection = new PoolConnection(false, connection);
            poolConnections.add(poolConnection);
        }
    }
    @Override
    public PoolConnection getPoolConnection() {
        if(poolConnections.size() <= 0) {
            return null;
        }
        PoolConnection poolConnection = getRealConnection();
        while (poolConnection == null) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            poolConnection = getRealConnection();
        }
        return poolConnection;
    }
    /**
     * 获取数据库链接目标
     * @return 数据库链接目标
     */
    private synchronized PoolConnection getRealConnection() {
        for(PoolConnection poolConnection : poolConnections) {
            // 寻觅不处于繁忙状况的衔接
            if(!poolConnection.isBusy()) {
                Connection connection = poolConnection.getConnection();
                // 测验当时衔接是否有用
                try {
                    if(!connection.isValid(5000)) {
                        Connection validConnection = DriverManager.getConnection(jdbcUrl, username, passowrd);
                        poolConnection.setConnection(validConnection);
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                // 设置为繁忙
                poolConnection.setBusy(true);
                return poolConnection;
            }
        }
        return null;
    }
}
  • 线程池办理类

运用单例

public class PoolManager {
    /**
     * 衔接池持有类
     */
    private static class PoolHolder {
        private static String url = "";
        private static String driver = "";
        private static String username = "";
        private static String password = "";
        private static int size = 10;
        private static IPool poolImpl = new PoolImpl(driver, url, username, password, size);
    }
    /**
     * 内部类单利形式产生运用目标
     * @return 单例
     */
    public static IPool getInstance() {
        return PoolHolder.poolImpl;
    }
}

当然,上面的比如过于浅尝辄止,想深化学习,能够参阅下下面的文章。

第一节 从零开端手写 mybatis(一)MVP 版别

第二节 从零开端手写 mybatis(二)mybatis interceptor 插件机制详解

第三节 从零开端手写 mybatis(三)jdbc pool 从零完结数据库衔接池

第四节 从零开端手写 mybatis(四)- mybatis 业务办理机制详解

小结

数据库衔接池在国内干流仍是 druid,可是 HikariCP 可谓在规划上精益求精,值得咱们深化学习其理念。

山高路远,行则将至。