手写数据库连接池实战

简介: 手写数据库连接池实战

有经典,有干货,微信搜索【李子捌】关注这个傻瓜式坚持的程序员。


1、简单的数据库连接池

简介:

使用等待超时模式来构造一个简单的数据库连接池。

数据库连接池支持如下功能


普通获取连接无超时时间

超时获取连接,超时时间之后返回null

使用连接

释放连接

记录获取连接和为获取连接的次数,统计连接池的性能

超时等待模式

在实现数据库连接池功能之前,我们先来回顾一下上一章的等待/通知经典范式。即加锁、条件循环和处理逻辑三个步骤,但是常规的等待/通知无法做到超时等待,因此我们做一些小改动来实现一个超时等待的等待/通知范式。

改动方式:


定义等待时间T

计算超时返回的时间now() + T

当结果不满足或者超时时间未到,wait()

伪代码:

// 当前对象加锁
public synchronized Object get(long mills) throws InterruptedException {
  long future = System.currentTimeMills + mills;
    long remaining = mills;
    // 当结果不满足并且超时时间大于0时继续等待(即使被唤醒)
    while((result == null) && remainig > 0) {
      wait(remaining);
        remaining = future - System.currentTimeMills;
    }
    return result;
}

连接池代码及功能介绍

连接池初始大小为10,也可以指定大小。使用LinkedList队列来做连接池管理,队列尾部插入连接,队列头部获取连接。connection()/connection(long)支持普通获取连接无超时时间和超时等待获取连接,超时获取在指定时间内没有获取到连接将会返回null;releaseConnection(connection)释放连接后,连接归队并通知等待在该连接池上的线程获取连接。

package com.lizba.p3;
import java.sql.Connection;
import java.util.LinkedList;
/**
 * <p>
 *  数据库连接池
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/17 21:13
 */
public class ConnectionPool {
    /** 默认连接池大小 */
    private static final int DEFAULT_SIZE = 10;
    /** 连接池存储容器 */
    private LinkedList<Connection> pool = new LinkedList<>();
    public ConnectionPool(int initialSize) {
        // 参数不正确默认初始化10
        initialSize = initialSize <= 0 ? DEFAULT_SIZE : initialSize;
        for (int i = 0; i < initialSize; i++) {
            pool.addLast(ConnectionDriver.createConnection());
        }
    }
    /**
     * 释放连接,重回连接池,并且通知等待中的消费者
     * @param connection
     */
    public void releaseConnection(Connection connection) {
        if (connection != null) {
            synchronized (pool) {
                pool.addLast(connection);
                // 通知消费者
                pool.notifyAll();
            }
        }
    }
    /**
     * 直接获取连接,不知道超时时间,则会一直等待
     *
     * @return
     * @throws InterruptedException
     */
    public Connection connection() throws InterruptedException {
       return connection(0);
    }
    /**
     * 指定获取连接时间
     *
     * @param mills
     * @return
     * @throws InterruptedException
     */
    public Connection connection(long mills) throws InterruptedException {
        synchronized (pool) {
            if (mills <= 0) {
                while (pool.isEmpty()) {
                    pool.wait();
                }
                return pool.removeFirst();
            } else {
                long future = System.currentTimeMillis() + mills;
                long remaining = mills;
                while (pool.isEmpty() && remaining > 0) {
                    pool.wait(mills);
                    remaining = future - System.currentTimeMillis();
                }
                return pool.isEmpty() ? null : pool.removeFirst();
            }
        }
    }
}

连接驱动模拟:

Connection是一个接口,我们通过动态代理来创建Connection,当执行Connection的commit方法时,通过 TimeUnit.MILLISECONDS.sleep(200);休眠线程来模拟执行事务提交。

package com.lizba.p3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.concurrent.TimeUnit;
/**
 * <p>
 *    数据库连接驱动动态代理模拟类
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/17 16:57
 */
public class ConnectionDriver {
    private static final String COMMIT_OP = "commit";
    static class ConnectionHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // commit 时睡眠200ms
            if (method.getName().equals(COMMIT_OP)) {
                TimeUnit.MILLISECONDS.sleep(200);
            }
            return null;
        }
    }
    public static final Connection createConnection() {
        return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),
                new Class<?>[] {Connection.class}, new ConnectionHandler());
    }
}

客户端测试:

客户端使用多线程模拟对数据库发起多个连接,并通过统计获取和未获取的次数来计算在不同线程池大小和客户端连接数的情况下,客户端从线程池获取线程的成功和失败的次数。

package com.lizba.p3;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * <p>
 *    连接池测试,使用CountdownLatch确保connectionThread能同时执行,并发获取连接
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/17 17:51
 */
public class PoolTest {
    /** 初始化线程池 */
    private static ConnectionPool pool = new ConnectionPool(10);
    /** 保证所有线程都一起执行 */
    private static CountDownLatch start = new CountDownLatch(1);
    /** 保证main线程执行在所有线程执行完之后再往后执行 */
    private static CountDownLatch end;
    public static void main(String[] args) throws InterruptedException {
        // 定义获取连接的线程数量
        int threadSize = 10;
        // 定义每个线程获取连接的次数
        int count = 50;
        end = new CountDownLatch(threadSize);
        AtomicInteger getConnectionCount = new AtomicInteger();
        AtomicInteger notGetConnectionCount = new AtomicInteger();
        for (int i = 0; i < threadSize; i++) {
            Thread t = new Thread(new Runner(count, getConnectionCount, notGetConnectionCount), "connectionThread");
            t.start();
        }
        start.countDown();
        end.await();
        System.out.println("执行次数" + (threadSize * count));
        System.out.println("获取连接次数" + getConnectionCount);
        System.out.println("未获取连接次数" + notGetConnectionCount);
    }
    static class Runner implements Runnable {
        private int count;
        private AtomicInteger getConnectionCount;
        private AtomicInteger notGetConnectionCount;
        public Runner(int count, AtomicInteger getConnectionCount, AtomicInteger notGetConnectionCount) {
            this.count = count;
            this.getConnectionCount = getConnectionCount;
            this.notGetConnectionCount = notGetConnectionCount;
        }
        @Override
        public void run() {
            try {
                // 等待知道主线程执行完start.countDown();
                start.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 循环获取连接
            while (count > 0) {
                try {
                    Connection connection = pool.connection(1000);
                    if (connection != null) {
                        try {
                            connection.createStatement();
                            connection.commit();
                        } finally {
                            // 释放连接
                            pool.releaseConnection(connection);
                            // 记录获取的数量
                            getConnectionCount.incrementAndGet();
                        }
                    } else {
                        // 记录未获取到的数量
                        notGetConnectionCount.incrementAndGet();
                    }
                } catch (InterruptedException | SQLException e) {
                    e.printStackTrace();
                } finally {
                    count--;
                }
            }
            end.countDown();
        }
    }
}

image.png总结:

在数据量资源固定的情况下,随着并发连接数的上升,获取连接失败的比率会升高。此处超时连接的设计,在实际开发运用过程中,有利于减少程序阻塞时间,避免在连接一直不可用的情况下导致服务不可用,通过返回null,程序员在设计系统时可以做响应的容错措施或者服务降级等处理。

目录
相关文章
|
29天前
|
SQL 关系型数据库 数据库
国产数据实战之docker部署MyWebSQL数据库管理工具
【10月更文挑战第23天】国产数据实战之docker部署MyWebSQL数据库管理工具
98 4
国产数据实战之docker部署MyWebSQL数据库管理工具
|
20天前
|
存储 SQL 数据库
深入浅出后端开发之数据库优化实战
【10月更文挑战第35天】在软件开发的世界里,数据库性能直接关系到应用的响应速度和用户体验。本文将带你了解如何通过合理的索引设计、查询优化以及恰当的数据存储策略来提升数据库性能。我们将一起探索这些技巧背后的原理,并通过实际案例感受优化带来的显著效果。
36 4
|
2月前
|
SQL NoSQL 数据库
Cassandra数据库与Cql实战笔记
Cassandra数据库与Cql实战笔记
19 1
Cassandra数据库与Cql实战笔记
|
28天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
155 1
|
29天前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第26天】数据库作为现代应用系统的核心组件,其性能优化至关重要。本文主要探讨MySQL的索引策略与查询性能调优。通过合理创建索引(如B-Tree、复合索引)和优化查询语句(如使用EXPLAIN、优化分页查询),可以显著提升数据库的响应速度和稳定性。实践中还需定期审查慢查询日志,持续优化性能。
65 0
|
2月前
|
SQL 关系型数据库 MySQL
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
|
4月前
|
SQL 存储 NoSQL
Redis6入门到实战------ 一、NoSQL数据库简介
这篇文章是关于NoSQL数据库的简介,讨论了技术发展、NoSQL数据库的概念、适用场景、不适用场景,以及常见的非关系型数据库。文章还提到了Web1.0到Web2.0时代的技术演进,以及解决CPU、内存和IO压力的方法,并对比了行式存储和列式存储数据库的特点。
Redis6入门到实战------ 一、NoSQL数据库简介
|
2月前
|
SQL 数据处理 数据库
SQL语句优化与查询结果优化:提升数据库性能的实战技巧
在数据库管理和应用中,SQL语句的编写和查询结果的优化是提升数据库性能的关键环节
|
2月前
|
SQL 存储 Java
sql注入原理与实战(二)数据库原理
sql注入原理与实战(二)数据库原理
|
4月前
|
SQL 数据库
Spring5入门到实战------13、使用JdbcTemplate操作数据库(批量增删改)。具体代码+讲解 【下篇】
这篇文章是Spring5框架的实战教程,深入讲解了如何使用JdbcTemplate进行数据库的批量操作,包括批量添加、批量修改和批量删除的具体代码实现和测试过程,并通过完整的项目案例展示了如何在实际开发中应用这些技术。
Spring5入门到实战------13、使用JdbcTemplate操作数据库(批量增删改)。具体代码+讲解 【下篇】