設計容錯的資料庫連線池程式設計思路--架構優化之道【JAVA核心】
阿新 • • 發佈:2018-12-26
本專案的原始碼已經上傳,歡迎點選下載
1、資料庫連線池的產生背景?
資料庫在高併發情況下會面臨諸多問題——
- JDBC:管道的臨時建立——非常耗用資源,而且在高併發中,多個客戶端同時建立IO管道
- DB的承受能力如何考慮?
- 執行SQL語句完畢後又要馬上銷燬——之後難道不要再用?
- 最大連線管道上限也沒有——太多了無法承受
- 響應效能在高併發連線時如何保證?
急需第三方優化框架管理資源連線,提供以下功能——
- 管道快取 複用
- 設定連線上限,MAX值,保證DB正常響應
- 解決我們快取管道里面的高併發佔用問題——保證管道資料唯一性
2、什麼是資料庫連線池?
在Java中開源的資料庫連線池有以下幾種 :
(1)C3P0:是一個開放原始碼的JDBC連線池,它在lib目錄中與Hibernate一起釋出,包括了實現jdbc3和jdbc2擴充套件規範說明的Connection 和Statement 池的DataSources 物件。
(2)Druid:Druid不僅是一個數據庫連線池,還包含一個ProxyDriver、一系列內建的JDBC元件庫、一個SQL Parser。
支援所有JDBC相容的資料庫,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等。
Druid針對Oracle和MySql做了特別優化,比如:
- Oracle的PS Cache記憶體佔用優化
- MySql的ping檢測優化
資料庫連線池主要採用池化技術(資源池模式)——也就是快取
連線池架構:解決了非常寶貴的連線資源,不用臨時去new,事先準備一定數量的連線資源物件,且這個資源物件還可以被複用。且當執行緒正在佔用的時候其他執行緒是互斥狀態。為資料庫的連線資源進行調配,減少資源空閒的時間。
3、資料庫連線池的應用架構所處的位置和功能?
4、自己手寫設計一個數據庫連線池
本專案的原始碼已經上傳,歡迎點選下載
思路圖:
4.1 專案整體架構
4.2 druid部分編碼
4.2.1 ISimplePool介面
package com.heylink.druid; /* * introductions:面向介面程式設計 * 抽取連線池架構的介面 * created by Heylink on 2018/4/23 21:13 */ public interface ISimplePool { //獲取連線 PoolConnection getConnection(); //建立連線 void createConnection(int count); }
4.2.2 SimplePoolImpl介面實現
package com.heylink.druid;
import com.heylink.util.PropertiesUtil;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Objects;
import java.util.Vector;
/*
* introductions:執行緒池的實現類,實現管道的配置、初始化、建立和提供
* created by Heylink on 2018/4/27 18:31
*/
public class SimplePoolImpl implements ISimplePool {
//jdbc引數
private static String jdbcDriver = null;
private static String jdbcUrl = null;
private static String username = null;
private static String password = null;
//限制連線池中的管道數量引數
private static int initCount = 3;
private static int stepSize = 10;
private static int poolMaxSize = 150;
private static Vector<PoolConnection> poolConnections = new Vector<>();
public SimplePoolImpl() {
init();
}
private void init() {
//1.外部配置檔案的讀取
jdbcDriver = PropertiesUtil.getProperty("jdbcDriver");
jdbcUrl = PropertiesUtil.getProperty("jdbcUrl");
username = PropertiesUtil.getProperty("username");
password = PropertiesUtil.getProperty("password");
//2.限制管道數量引數初始化
if (Integer.valueOf(Objects.requireNonNull(PropertiesUtil.getProperty("initCount"))) > 0) {
initCount = Integer.valueOf(PropertiesUtil.getProperty("initCount"));
}
if (Integer.valueOf(Objects.requireNonNull(PropertiesUtil.getProperty("stepSize"))) > 0) {
stepSize = Integer.valueOf(PropertiesUtil.getProperty("stepSize"));
}
if (Integer.valueOf(Objects.requireNonNull(PropertiesUtil.getProperty("poolMaxSize"))) > 0) {
poolMaxSize = Integer.valueOf(PropertiesUtil.getProperty("poolMaxSize"));
}
//3.DriverManager 管家用來註冊,驅動例項
try {
//獲取資料庫的驅動物件
Driver dbDriver = (Driver) Class.forName(jdbcDriver).newInstance();
DriverManager.registerDriver(dbDriver);
} catch (Exception e) {
e.printStackTrace();
}
//4.初始化連線池中的管道數量
createConnection(initCount);
}
/**
* 對外提供管道
* @author heylinlook
* @date 2018/4/27 20:20
* @param
* @return
*/
@Override
public PoolConnection getConnection() {
if (poolConnections.size() == 0) {
System.out.println("連線池還未初始化,將執行初始化操作");
createConnection(initCount);
}
PoolConnection connection = getRealConnection();
//多執行緒環境中 執行緒拿到物件 引用堆當中例項 可能被其他執行緒搶走或者執行緒終止導致管道為空
while (connection == null) {
//管道被搶,擴容
createConnection(stepSize);
//再次獲取有效管道
connection = getRealConnection();
try {
//個人經驗:人為的減少競爭 醒來後仍然判斷是否為空
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//返回有效物件
return connection;
}
/**
* 獲取有效的管道 加鎖
* 1.管道物件長期在集合中,沒有超時回收
* 2.沒有被其他執行緒佔用
* @author heylinlook
* @date 2018/4/27 20:23
* @param
* @return
*/
private synchronized PoolConnection getRealConnection() {
for (PoolConnection conn : poolConnections) {
if (!conn.isBusy()) {
//沒有被佔用的情況
Connection connection = conn.getConnection();
//進一步判斷是否沒有長期存在集合中被回收
try {
//如果失效
if (!connection.isValid(2000)) {
//替換 保證最大效率(不走create方法)
connection = DriverManager.getConnection(jdbcUrl, username, password);
}
} catch (SQLException e) {
e.printStackTrace();
}
//當前執行緒佔用該管道
conn.setBusy(true);
return conn;
}
}
//如果遍歷完都沒有有效的管道,返回空
return null;
}
@Override
public void createConnection(int count) {
//判斷是否還可以再增加管道數量
if (poolMaxSize <= 0 || poolMaxSize < (poolConnections.size() + count)) {
return;
}
//新增count個管道
for (int i = 0; i < count; i++) {
try {
Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
//封裝管道
PoolConnection poolConnection = new PoolConnection(false, connection);
//新增到vector
poolConnections.add(poolConnection);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
4.2.3 PoolConnection類
package com.heylink.druid;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/*
* introductions:自定義連線資料管道Bean
* 原生的Connection上面沒有複用的標記
* 自己封裝設計isBusy標記位來解決執行緒被佔用的問題
* 總結:這個思想就是封裝思想,也就是擴充套件功能
* created by Heylink on 2018/4/27 18:32
*/
public class PoolConnection {
//表示繁忙的標誌,複用的標誌,執行緒安全
private boolean isBusy = false;
//真正的管道 用來操作資料的java.sql.Connection
private Connection connection;
public PoolConnection(boolean isBusy, Connection connection) {
this.isBusy = isBusy;
this.connection = connection;
}
public boolean isBusy() {
return isBusy;
}
public void setBusy(boolean busy) {
isBusy = busy;
}
public Connection getConnection() {
return connection;
}
public void setConnection(Connection connection) {
this.connection = connection;
}
//自定義釋放方法
public void close() {
this.isBusy = false;
}
//自定義查詢功能
public ResultSet queryBySql(String sql) {
Statement sm = null;
ResultSet rs = null;
try {
sm = connection.createStatement();
rs = sm.executeQuery(sql);
} catch (SQLException e) {
e.printStackTrace();
}
return rs;
}
}
4.2.4 PoolManager類
package com.heylink.druid;
/*
* introductions:內部類單例模式來對外提供連線池物件
* 嚴格保證執行緒安全機制
* 注意:
* 簡單的單例模式還不能完全防禦記憶體當中在執行時期有多個物件
* 只是在返回時合併為一個結果
* 會給系統帶來額外開銷
* created by Heylink on 2018/4/27 18:32
*/
public class PoolManager {
/**
* 內部類
* @author heylinlook
* @date 2018/4/27 20:44
* @param
* @return
*/
private static class createPool {
private static SimplePoolImpl simplePool = new SimplePoolImpl();
}
/**
* JVM中類載入器位元組碼是一個嚴格互斥理論模型
* 類載入器原理
* 完美的實現執行緒安全
* @author heylinlook
* @date 2018/4/27 20:45
* @param
* @return
*/
public static SimplePoolImpl getInstance() {
return createPool.simplePool;
}
}
4.3 util部分編碼
package com.heylink.util;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;
public class PropertiesUtil {
private static Properties props;
static {
String fileName = "jdbcPool.properties";
props = new Properties();
try {
props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static String getProperty(String key) {
String value = props.getProperty(key.trim());
if(value == null || value.trim().isEmpty()) {
return null;
}
return value.trim();
}
public static String getProperty(String key, String defaultValue) {
String value = props.getProperty(key.trim());
if(value == null || value.trim().isEmpty()) {
value = defaultValue;
}
return value.trim();
}
}
4.4 resource部分編碼
4.4.1 jdbcPool.properties屬性檔案
jdbcDriver=com.mysql.jdbc.Driver
#url
jdbcUrl=jdbc:mysql://localhost:3306/test
#資料賬戶名
username=root
password=admin123
#規範連線池的引數 規範初始化的時候產生多少執行緒的連結管道
initCount=10
#步進量 開發500 架構5000
stepSize=4
#資料庫管道數量限制
poolMaxSize=150
4.4.2 test.sql測試資料庫
-- MySQL dump 10.13 Distrib 5.7.12, for Win64 (x86_64)
--
-- Host: localhost Database: test
-- ------------------------------------------------------
-- Server version 5.6.34-log
--
-- Table structure for table `items`
--
DROP TABLE IF EXISTS `items`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `items` (
`ID` int(11) NOT NULL,
`NAME` varchar(45) DEFAULT NULL,
`PRICE` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `items`
--
LOCK TABLES `items` WRITE;
/*!40000 ALTER TABLE `items` DISABLE KEYS */;
INSERT INTO `items` VALUES (1,'heylink',100),(2,'chicken',200),(3,'dog',234);
/*!40000 ALTER TABLE `items` ENABLE KEYS */;
UNLOCK TABLES;
4.5 test部分編碼
import com.heylink.druid.PoolConnection;
import com.heylink.druid.PoolManager;
import com.heylink.druid.SimplePoolImpl;
import java.sql.ResultSet;
import java.sql.SQLException;
/*
* introductions:
* created by Heylink on 2018/4/27 18:31
*/
public class SimplePoolTest {
//拿到連線池物件
private static SimplePoolImpl simplePool = PoolManager.getInstance();
/**
* 單個執行緒使用連線池物件做查詢業務
* @author heylinlook
* @date 2018/4/27 21:01
* @param
* @return
*/
public synchronized static void selectData() {
PoolConnection connection = simplePool.getConnection();
//資料庫中存放了測試表
ResultSet rs = connection.queryBySql("SELECT * FROM items");
System.out.println("執行緒名稱: " + Thread.currentThread().getName());
try {
while (rs.next()) {
System.out.print(rs.getString("ID") + "\t\t");
System.out.print(rs.getString("NAME") + "\t\t");
System.out.println(rs.getString("PRICE") + "\t\t");
}
rs.close();
//業務做完後,我們就釋放 ——連線池中的管道複用機制
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//建立1500個執行緒,列印輸出資料庫內容
for (int i = 0; i < 1500; i++) {
new Thread(new Runnable() {
@Override
public void run() {
selectData();
}
}).start();
}
}
}
4.6 程式輸出
輸出結果正常
4.7 注意事項
本專案按照設計容錯的資料庫連線池程式設計思路進行編碼測試用的SQL語句在resources資料夾下,執行一遍即可
如果報錯No suitable driver found for jdbc:mysql://127.0.0.1:3306/
請先確認將mysql-connector-java-*.jar包放在
JDK安裝路徑下,如
C:\Program Files\Java\jdk\jre\lib\ext
4.8專案原始碼
本專案的原始碼已經上傳到我的GitHub主頁,有問題歡迎留言,歡迎點選下載