深度剖析池化技术内幕---高性能底层组件探秘

    JDK中有常量池,线程池,数据库操作有连接池,那么这些池到底是什么?有什么作用?底层原理是啥?下面以连接池示例。

测试环境:JDK1.8 + MySQL8.0.16 + Spring Boot 2.1.3.RELEASE + Gradle 4.4.1

一. 从一个最原始的JDBC查询开始

  • build.gralde 加入相关依赖
// 依赖列表
dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'mysql:mysql-connector-java:8.0.16'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    testCompile group: 'junit', name: 'junit', version: '4.12'
    compileOnly 'org.projectlombok:lombok:1.18.4'
}
  • application.yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/zzxypm?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=UTC
    username: root
    password: root
  • 数据源配置参数
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author wanghong
 * @date 2019/08/18 14:58
 **/
@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceConfig {
    private String driverClassName;
    private String url;
    private String username;
    private String password;
}
  • 原生JDBC操作工具类
package com.hong.util;

import com.hong.config.DataSourceConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author wanghong
 * @date 2019/08/18 14:11
 **/
@Component
public class JDBCUtil {

    @Autowired
    private DataSourceConfig dataSourceConfig;

    /**
     * 获取数据库连接
     *
     * @return
     * @throws SQLException
     */
    public Connection getConnection() throws SQLException {
        try {
            Class.forName(dataSourceConfig.getDriverClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return DriverManager.getConnection(dataSourceConfig.getUrl(), dataSourceConfig.getUsername(), dataSourceConfig.getPassword());
    }

    /**
     * * 关闭资源
     * * @param resultSet 查询返回的结果集,没有为空
     * * @param statement
     * * @param connection
     */
    public void close(ResultSet resultSet, Statement statement, Connection connection) {

        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 增加,删除,修改
     */
    public void insertOrDeleteOrUpdate(String sql) {
        try {
            Connection connection = getConnection();
            PreparedStatement pst = connection.prepareStatement(sql);
            int execute = pst.executeUpdate();
            System.out.println("执行语句:" + sql + "," + execute + "行数据受影响");
            close(null, pst, connection);
        } catch (SQLException e) {
            System.out.println("异常提醒:" + e);
        }
    }

    /**
     * * 查询,返回结果集
     */
    public List<Map<String, Object>> select(String sql) {
        List<Map<String, Object>> returnResultToList = null;
        try {
            Connection connection = getConnection();
            PreparedStatement pst = connection.prepareStatement(sql);
            ResultSet resultSet = pst.executeQuery();
            returnResultToList = returnResultToList(resultSet);
            close(resultSet, pst, connection);
        } catch (SQLException e) {
            System.out.println("异常提醒:" + e);
        }
        return returnResultToList;
    }

    /**
     * 数据返回集合
     *
     * @param resultSet
     * @return
     * @throws SQLException
     */
    private List<Map<String, Object>> returnResultToList(ResultSet resultSet) {
        List<Map<String, Object>> values = null;
        try {
            // 键: 存放列的别名, 值: 存放列的值.
            values = new ArrayList<>();
            // 存放字段名
            List<String> columnName = new ArrayList<>();
            ResultSetMetaData rsmd = resultSet.getMetaData();
            for (int i = 0; i < rsmd.getColumnCount(); i++) {
                // 字段名
                columnName.add(rsmd.getColumnLabel(i + 1));
            }
            System.out.println("表字段为:");
            System.out.println(columnName);
            System.out.println("表数据为:");
            Map<String, Object> map = null;
            // 处理 ResultSet, 使用 while 循环
            while (resultSet.next()) {
                map = new HashMap<>();
                for (String column : columnName) {
                    Object value = resultSet.getObject(column);
                    map.put(column, value);
                    System.out.print(value + "\t");
                }
                // 把一条记录的 Map 对象放入准备的 List 中
                values.add(map);
                System.out.println();
            }
        } catch (SQLException e) {
            System.out.println("异常提醒:" + e);
        }
        return values;
    }

}
@Service
public class JdbcService {
    @Autowired
    private JDBCUtil jdbcUtil;

    /**
     * 数据库操作
     * @param sql
     */
    public List<Map<String, Object>> execute(String sql){
        List<Map<String, Object>> list = jdbcUtil.select(sql);
        return list;
    }
}
@Controller
public class HelloController {

    @Autowired
    private JdbcService jdbcService;

    @GetMapping("/")
    @ResponseBody
    public Object Home() {
        String sql = "SELECT * FROM t_user WHERE account = 'wanghong'";
        return jdbcService.execute(sql);
    }

}

在这里插入图片描述
好了,可以正常访问。下面模拟下并发查询。

二. 多线程并发操作
改造下service代码如下:

package com.hong.service;

import com.hong.util.JDBCUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

/**
 * @author wanghong
 * @date 2019/08/18 12:09
 **/
@Service
public class JdbcService {

    private static final int CONCURRENCY_LEVEL = 200; // 模拟并发请求

    @Autowired
    private JDBCUtil jdbcUtil;

    /**
     * 数据源连接压测
     */
    public void benchmarkTest() {
        String sql = "SELECT * FROM t_user WHERE account = 'wanghong'";
        CountDownLatch cdl = new CountDownLatch(CONCURRENCY_LEVEL);
        for (int i = 0; i < CONCURRENCY_LEVEL; i++) {
            // 多线程模拟客户端并发查询请求
            Thread thread = new Thread(() -> {
                try {
                    // 代码在这里等待CountDownLatch计数为0,代表所有线程可以start,再同时抢占运行
                    cdl.await();
                    execute(sql);
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + "线程执行出现异常:" + e.getMessage());
                }
            });
            thread.setName("jdbc-thread-" + i);
            thread.start();
            //线程启动后,倒记数器-1,表示又有一个线程就绪了
            cdl.countDown();
        }
    }

    /**
     * 数据库操作
     * @param sql
     */
    public List<Map<String, Object>> execute(String sql){
        List<Map<String, Object>> list = jdbcUtil.select(sql);
        return list;
    }
}
@Controller
public class HelloController {

    @Autowired
    private JdbcService jdbcService;

    @GetMapping("/")
    @ResponseBody
    public Object Home() {
       // String sql = "SELECT * FROM t_user WHERE account = 'wanghong'";
       // return jdbcService.execute(sql);
        jdbcService.benchmarkTest();
        return null;
    }
}

发送请求,然后控制台出现了如下异常信息:

java.sql.SQLNonTransientConnectionException: Data source rejected establishment of connection,  message from server: "Too many connections"

下面看看如何使用池化技术解决数据库连接的问题。

三. 池?

  • 池是什么?
    在这里插入图片描述
  • 池化技术有哪些共性?
    在这里插入图片描述
  • 什么样的资源需要池化?
    在这里插入图片描述

四. 如何设计一个池?

  • 设计实现
    在这里插入图片描述
    在这里插入图片描述

  • 技术内幕 — AtomicInteger CAS
    在这里插入图片描述

  • 技术内幕 — Condition
    在这里插入图片描述

  • 代码落地

package com.hong.config;

import java.sql.Connection;

/**
 * @author wanghong
 * @date 2019/08/18 16:21
 *  数据库连接池定义
 **/
public interface ConnectionPool {

    /**
     *  初始化
     * @param initSize 初始化连接数
     * @param maxActive 最大连接数
     * @param idleCount 空闲连接数
     * @param waitTime 获取连接超时时间
     */
    void init(int initSize ,int maxActive ,int idleCount,long waitTime);

    /**
     * 获取连接
     * @return
     */
    Connection get();

    /**
     * 回收资源
     * @param conn
     */
    void recycle(Connection conn);
}
package com.hong.config;

import com.hong.util.JDBCUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author wanghong
 * @date 2019/08/18 16:29
 * 连接池组件实现
 **/
@Component
public class ConnectionPoolImpl implements ConnectionPool {

    @Autowired
    private JDBCUtil jdbcUtil;

    private int initSize;
    private int maxActive;
    private int idleCount;
    private long waitTime;

    // 当前活动的线程数
    private AtomicInteger activeSize = new AtomicInteger();

    BlockingQueue<Connection> idle;
    BlockingQueue<Connection> busy;

    @Override
    public void init(int initSize, int maxActive, int idleCount, long waitTime) {
        // 省略参数检查
        this.initSize = initSize;
        this.maxActive = maxActive;
        this.idleCount = idleCount;
        this.waitTime = waitTime;

        idle = new LinkedBlockingDeque<>();
        busy = new LinkedBlockingDeque<>();

        initConnection(this.initSize);
    }

    /**
     * 初始化数据库连接
     *
     * @param initSize
     */
    private void initConnection(int initSize) {
        for (int i = 0; i < initSize; i++) {
            if (activeSize.get() < maxActive){
                // 双重检查
                if (activeSize.incrementAndGet() <= maxActive){
                    try {
                        Connection conn = jdbcUtil.getConnection();
                        idle.offer(conn);
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }else {
                    activeSize.decrementAndGet();
                }
            }
        }
    }

    @Override
    public Connection get() {
        long start = System.currentTimeMillis();
        // 1. 从空闲组获取
        Connection conn = idle.poll();
        if (conn != null){
            System.out.println("从空闲组获得连接");
            busy.offer(conn);
            return conn;
        }

        // 2. 空闲组没有,没有限制
        if (activeSize.get() < maxActive){
            if (activeSize.incrementAndGet() <= maxActive){
                System.out.println("创建一个新的连接");
                try{
                    conn = jdbcUtil.getConnection();
                    busy.offer(conn);
                    return conn;
                }catch (Exception e){
                    System.out.println("获取连接异常:" + e);
                }
            }else {
                activeSize.decrementAndGet();
            }
        }

        // 3.连接全忙,等待连接归还
        long timeout = waitTime - (System.currentTimeMillis() - start);
        try {
            conn = idle.poll(timeout, TimeUnit.MILLISECONDS);
            if (conn == null){
                throw new TimeoutException("等待获取连接超时,等待时间为:" + timeout + "ms");
            }
        } catch (InterruptedException | TimeoutException e) {
            e.printStackTrace();
            Thread.interrupted();
        }
        return conn;
    }

    @Override
    public void recycle(Connection conn) {
        if (conn == null){
            return;
        }

        // 从busy挪到idle
        boolean removed = busy.remove(conn);
        if (removed){
            // 空闲连接太多,释放掉
            if (idleCount < idle.size()){
                jdbcUtil.close(null,null,conn);
                activeSize.decrementAndGet();
                return;
            }

            boolean offerd = idle.offer(conn);
            // 没有放入成功
            if (!offerd){
                jdbcUtil.close(null,null,conn);
                activeSize.decrementAndGet();
            }
        }else {
            // 不在组中,关闭释放连接,防止连接泄露
            jdbcUtil.close(null,null,conn);
            activeSize.decrementAndGet();
        }
    }
}
package com.hong.util;

import com.hong.config.ConnectionPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

/**
 * @author wanghong
 * @date 2019/08/18 17:20
 **/
@Component
public class JDBCDao {

    @Autowired
    private ConnectionPool connectionPool;

    @Autowired
    private JDBCUtil jdbcUtil;

    /**
     * 注解@PostConstruct与@PreDestroy
     *
     * PostConstruct 用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。
     */
    @PostConstruct
    private void init(){
        connectionPool.init(10,100,10,5000);
    }

    public List<Map<String, Object>> select(String sql) {
        List<Map<String, Object>> returnResultToList = null;
        try {
            // 从连接池中获取连接
            Connection connection = connectionPool.get();
            PreparedStatement pst = connection.prepareStatement(sql);
            ResultSet resultSet = pst.executeQuery();
            returnResultToList = jdbcUtil.returnResultToList(resultSet);
            // 使用完,将连接放回连接池
            connectionPool.recycle(connection);
        } catch (SQLException e) {
            System.out.println("异常提醒:" + e);
        }
        return returnResultToList;
    }
}

JdbcService.java

    /**
     * 数据库操作
     * @param sql
     */
    public List<Map<String, Object>> execute(String sql){
       // List<Map<String, Object>> list = jdbcUtil.select(sql);
        List<Map<String, Object>> list = jdbcDao.select(sql);
        return list;
    }

请求测试,没有再报之前的异常了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值