tomcat-jdbc-pool 實現簡單分析
原文 http://kuang.io/tomcat-jdbc-pool-simple-source/
2015-05-22
什么是連接池?
池,不由自主的會想到水池。
小時候,我們都要去遠處的水井挑水,倒進家中的水池里面。這樣,每次要用水時,直接從水池中「取」就行了。不用大老遠跑去水井打水。
數據庫連接池就如此,我們預先準備好一些連接,放到池中。當需要時,就直接獲取。而不要每次跟數據庫建立一個新的連接。特別對數據庫連接這類耗時,耗資源的操作。當連接用完后,再放回池中,供后續使用。
連接池的作用?
避免多次去創建資源。例如,創建新的數據庫連接,500ms輕輕松松就消耗了。建立TCP連接,數據庫賬號驗證等等。這性能消耗起來,可是非常大的。
在稍大的系統內,連接池是必備的。同時,對技術人員要求,對連接池的掌握也是必須的。
tomcat-jdbc-pool的特色
基于jdk1.5后的并發實現。代碼簡潔,精練。核心的類就2,3個。
對池的控制就在org.apache.tomcat.jdbc.pool.ConnectionPool中搞定。
</div>先前有簡單看過 dbcp1.x, c3p0等等,代碼量真不少,邏輯復雜。想熟悉池的設計,可以仔細讀讀tomcat-jdbc-pool,非常快速的入手。在dbcp2的實現時,跟tomcat-jdbc-pool思路一致(完全copy的版本)
對于連接池來說,最基本的特點就是:
- 有一定的容量,及已經創建好的對象
- 有「借」有「還」操作的接口 </ul>
池中「借出」連接是怎么個過程?
在jdbc-pool設計有2隊列,分別為busy和idle,存儲「正在使用」和「空閑」的連接。都采用ArrayBlockingQueue以保證線程安全。
當有請求「借」的動作過來時,從idle中poll一個連接,然后將該連接再offer至busy隊列中。這是最基本最純凈的思路。
當idle連接不夠時,內部會再去創建新的連接返回給客戶端。
但是,做為「池」必須的職責之一是控制總量,不會任你去增長。
那么,有意思來了,他是怎么控制總量的咧?
我們可以通俗點稱『占坑法』(tomcat中也有不少場景采用這方式)。
首先池中有維護連接數總量「計數器」(采用AtomicInteger保證線程安全,每次新增或銷毀都會變更)。
『占坑法』就在每次要新創建連接池,先總量計數器+1(占位),再比較是否達到配置的池的最大連接數。如果沒有達到,則創建新的;如果已達到了,則等待現有連接釋放,再取走。
有點類似,大學時先用本書去搶位置占著。
大致實現代碼如下:
public class ConnectionPool { //連接數的總量 private AtomicInteger size = new AtomicInteger(0); //所有正在使用中的連接 private BlockingQueue<PooledConnection> busy; //所有空閑的連接 private BlockingQueue<PooledConnection> idle; private PooledConnection borrowConnection(int wait, String username, String password) throws SQLException { PooledConnection con = idle.poll(); while (true) { if (con != null) { try { busy.offer(con);//我這簡化了,在源碼中這兒會對連接進行校驗、檢查或進行連接。 } catch(Exception e) { } return con; } if (size.get() < getPoolProperties().getMaxActive()) { //占坑神技 if (size.addAndGet(1) > getPoolProperties().getMaxActive()) { //既然沒了,那數量也減回去 //再去等待其他連接歸還回來 size.decrementAndGet(); } else { return createConnection(now, con, username, password); } } try { //檢查并等待新的空閑連接 con = idle.poll(timetowait, TimeUnit.MILLISECONDS); } catch (InterruptedException ex) { //.... } } } }
用完后「歸還」連接是怎么個過程?
大致思路跟「借」操作相反落。當然是無視那些「善后」的工作,只關注資源的管理。
但是, 做為連接池必須的職責之一,并不真實的斷開與數據庫的連接。 而只是放至idle隊列中,供客戶端下次再使用。如果有需要或必要肯定會釋放,技巧所在。
大致代碼如下:
protected void returnConnection(PooledConnection con) { if (con != null) { try { con.lock(); if (busy.remove(con)) { //跟允許的最大空閑數比較 if(idle.size() < poolProperties.getMaxIdle()) { idle.offer(con); //源碼中調用release //會根據配置項執行一些校驗,例如:testOnReturn為true,則在回收時檢查連接是否正常 //release(con); } } catch(Exception e) { //.... } finally { con.unlock(); } } //end if }
當長時間運行后,怎么回收無效的連接?
這是連接池必備的功能之一,類似檢查死鏈或者釋放自身過多的資源。比如,在高并發過后,對資源消耗量少時,就釋放些不再使用的數據庫連接(真實斷開),維護合理的空格數量。
看到這應用場景就自然想到,通過后臺線程定時掃描。
「對的,就是這樣子。」
同樣在ConnectionPool這個類文件中的PoolCleaner類。寫在同個類文件中,便于用this進行傳遞數據。不用再去構造個復雜的ConnectionPool對象。
直接上代碼,「好代碼」就是最好的描述。
public class ConnectionPool { /**
- Initialize the connection pool - called from the constructor
*/
protected void init(PoolConfiguration properties) throws SQLException {
initializePoolCleaner(properties);
}
public void initializePoolCleaner(PoolConfiguration properties) {
if (properties.isPoolSweeperEnabled()) { poolCleaner = new PoolCleaner(this, properties.getTimeBetweenEvictionRunsMillis()); poolCleaner.start(); //只注冊一個清理器,并未啟動線程。 } //end if } /**- 檢查所有的空閑連接
*/
public void checkIdle(boolean ignoreMinSize) {
try {
if (idle.size()==0) return;
Iterator<PooledConnection> unlocked = idle.iterator();
while (unlocked.hasNext()) {
PooledConnection con = unlocked.next();
try {
con.lock();
//如果這時已到busy中,則不檢查了
if (busy.contains(con)) {
} release(con); idle.remove(con); } finally { con.unlock(); } } //while } catch (Exception e) { //.... } } private static volatile Timer poolCleanTimer = null; private static HashSet<PoolCleaner> cleaners = new HashSet<>(); //注冊一個清理器 private static synchronized void registerCleaner(PoolCleaner cleaner) { unregisterCleaner(cleaner); cleaners.add(cleaner); //一堆構造方式。。。 if (poolCleanTimer == null) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(ConnectionPool.class.getClassLoader()); poolCleanTimer = new Timer("PoolCleaner["+ System.identityHashCode(ConnectionPool.class.getClassLoader()) + ":"+continue;
}finally { Thread.currentThread().setContextClassLoader(loader); } } //構造定時掃描器 //java有內庫非常強大,想用啥有啥呀 poolCleanTimer.scheduleAtFixedRate(cleaner, cleaner.sleepTime,cleaner.sleepTime); } //真實的處理線程在這兒。。。 protected static class PoolCleaner extends TimerTask { protected WeakReference<ConnectionPool> pool; PoolCleaner(ConnectionPool pool, long sleepTime) { //弱引用,不了解的可以google下 this.pool = new WeakReference<>(pool); } @Override public void run() { ConnectionPool pool = this.pool.get(); if (pool == null) { stopRunning(); } else if (!pool.isClosed()) {System.currentTimeMillis() + "]", true);
if (pool.getPoolProperties().getMinIdle() < pool.idle.size()) { pool.checkIdle(); //check. check now. } } } public void start() { registerCleaner(this); //并未啟動線程,只是注冊一個清理器 } public void stopRunning() { unregisterCleaner(this); } } }</pre>
- 檢查所有的空閑連接
*/
public void checkIdle(boolean ignoreMinSize) {
try {
if (idle.size()==0) return;
Iterator<PooledConnection> unlocked = idle.iterator();
while (unlocked.hasNext()) {
PooledConnection con = unlocked.next();
try {
con.lock();
//如果這時已到busy中,則不檢查了
if (busy.contains(con)) {