手写MyBatis数据库连接池

2023年 10月 11日 37.3k 0

1、资源池(Pool)技术

资源池(Resource Pool)是一种设计模式,预先构建好N个资源,需要的时候直接从池子里面拿,用完再放回去。
预先构建好资源,节省了构建资源的时间,可以提升应用程序的响应速度。
资源使用完毕后放回池子里,让其他线程可以复用资源,避免了资源反复创建和销毁的开销。

基于这种设计模式,于是就有了:线程池,连接池,内存池,对象池等池技术。

线程的创建和销毁开销是很大的,如果每执行一个异步任务都开启一个线程,那么很可能线程开启和销毁的开销比任务执行本身的开销都大,这样就显得得不偿失。

和线程池类似,如果每次执行SQL都去开启一个MySQL连接,执行完SQL再关闭连接,那么很可能连接的时间比SQL执行本身的开销都大。
因此,在实际项目中,几乎都会使用数据库连接池,而不是每次操作DB都开启新连接。

在Spring Boot项目中,默认的DataSource为com.zaxxer.hikari.HikariDataSource,如果不进行任何配置,那么Spring Boot将采用HikariDataSource连接池。

我一般用阿里的DruidDataSource,性能比较高,而且功能丰富,支持SQL语句执行监控,可以快速定位到项目中的慢查询,然后针对慢查询SQL进行优化。

2、Mybatis获取连接的流程

Debug跟了一下MyBatis获取MySQL连接的流程,大致如下:

  • org.apache.ibatis.executor.BaseExecutor#getConnection()获取连接。
  • org.mybatis.spring.transaction.SpringManagedTransaction#getConnection()获取连接。
  • SpringManagedTransaction内部的DataSource就是yml里配置的DataSource。
  • org.springframework.jdbc.datasource.DataSourceUtils#getConnection(DataSource dataSource)。
  • org.springframework.jdbc.datasource.DataSourceUtils#fetchConnection(DataSource dataSource)。
  • 最终在DataSourceUtils类中通过fetchConnection()获取连接:dataSource.getConnection()。
  • 3、手写连接池

    为了便于更好的理解数据库连接池的思想,于是决定自己手写实现一下。

    MyBatis获取连接,就是通过DataSourceUtils来获取的,内部就是调用了dataSource.getConnection()
    因此,我们只需要DataSource接口,重写getConnection()方法即可。

    需要注意的是:仅实现DataSource接口是不够的,还需要重写Connection的close()逻辑,默认逻辑是关闭连接,但是连接池的close()逻辑应该是将连接归还到池子。

    重写Connection的close()逻辑方式有很多种,可以创建一个Connection的包装类,但是需要重写的方法太多了,比较麻烦,于是我这里使用JDK的动态代理来实现。

    3.1、实现DataSource

    public class MyDataSourcePool implements DataSource {
    	//连接池容量
    	private static final int POOL_SIZE = 10;
    	//连接队列
    	private final LinkedBlockingQueue connectionQueue;
    
    	public MyDataSourcePool() throws SQLException {
    		//创建连接,备用
    		connectionQueue = new LinkedBlockingQueue(POOL_SIZE);
    		for (int i = 0; i < POOL_SIZE; i++) {
    			Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
    			connectionQueue.add(new ConnectionProxy(connection, this).getProxy());
    		}
    	}
    
    	@Override
    	public Connection getConnection() throws SQLException {
    		try {
    			//10s拿不到连接,就超时
    			return connectionQueue.poll(10, TimeUnit.SECONDS);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    			throw new RuntimeException(e);
    		}
    	}
    
    	//释放连接到连接池
    	public void release(Connection connection){
    		this.connectionQueue.add(connection);
    	}
    }
    

    3.2、动态代理Connection

    public class ConnectionProxy implements InvocationHandler {
    	private Connection connection;
    	private MyDataSourcePool myDataSourcePool;
    	private Connection proxy;
    
    	public ConnectionProxy(Connection connection,MyDataSourcePool myDataSourcePool) {
    		this.connection = connection;
    		this.myDataSourcePool = myDataSourcePool;
    	}
    
    	public Connection getProxy() {
    		proxy = (Connection) Proxy.newProxyInstance(connection.getClass().getClassLoader(), connection.getClass().getInterfaces(), this);
    		return proxy;
    	}
    
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		Object result = null;
    		if ("close".equals(method.getName())) {
    			//修改close()的逻辑
    			myDataSourcePool.release(this.proxy);
    		}else {
    			//其他方法不动
    			result = method.invoke(connection, args);
    		}
    		return result;
    	}
    }
    

    3.3、配置连接池

    server:
      port: 8001
    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/test
        username: root
        password: root
        type: com.xw.pool.MyDataSourcePool
        driver-class-name: com.mysql.cj.jdbc.Driver
    

    每次要执行SQL前,都会通过DataSourceUtils.fetchConnection()获取连接,SQL执行完毕后,会调用DataSourceUtils.doCloseConnection()来关闭连接。

    连接池所要做的,就是实现DataSource.getConnection()Connection.close()逻辑。

    4、性能测试

    手写的数据库连接池功能相对简单,所以基本不会有额外的开销影响性能。
    笔者和阿里的DruidDataSource进行了简单的性能比较。

    连接池默认10个连接,JMeter开启50个线程进行压测30秒,结果如下:

    • DruidDataSource
      在这里插入图片描述
    • 自己手写的
      在这里插入图片描述

    性能差别不大,笔记本环境比较复杂,不是专门的服务器测试,小波动可以忽略不计。
    DruidDataSource的峰值在10000,自己写的在11000左右,因为DruidDataSource还做了一些例如:防止SQL攻击,SQL耗时统计等其他工作,最终结果意料之中。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论