版權聲明:本文爲博客園博主「水木桶」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處連接及本聲明。
原文連接:https://www.cnblogs.com/shuimutong/p/11408219.htmlhtml
手寫DAO框架系列先後更新了5篇文章,外加1篇使用示例,GDAO框架至此已經初具雛形,簡單的使用不成問題。接下來就是對框架進行不斷的優化。java
手寫DAO框架(六)-後續之框架使用示例 此篇文章對GDAO的性能較少說起,這裏就簡單的記載一下,後續有機會再更新。mysql
數據示例:id:14005 name:BatchName-4009 age:52,其中id是自動生成。git
數據庫是mariadb,和測試程序位於同一臺機器github
測試數據條數5000條,一次調用sql
userDao.add(uds[i]);數據庫
方法插入一條數據。apache
系統 | 配置 | 結果 |
macOS | 2c4t,8g,固態硬盤 | 總耗時:8629.0ms,平均耗時:1.726ms |
Win10 | 6c6t,24g,固態硬盤 | 總耗時:5153.0ms,平均耗時:1.031ms |
對於一個提供鏈接池功能的DAO框架,若是保存的鏈接失效了沒法自動移除池,若是鏈接數據庫的網絡出現閃斷鏈接沒法繼續使用,只能經過重啓服務來達到初始化鏈接的目的,這樣的作法顯然是不夠優雅的。網絡
爲了提升框架的穩定性,因此決定對框架的鏈接部分作一次優化。app
經過網上查資料,擬定了幾個點。
一、自動重連
autoReconnect=true
JDBC經過配置可實現
二、鏈接有效性檢測
a、配置鏈接檢測語句。備註:有的數據庫Driver支持ping(),可使用。
三、鏈接泄露檢查
當鏈接從鏈接池借出後,長時間(配置時間)不歸還,將強制回收。
四、瞭解到的其餘問題
若是鏈接閒置時間過長,可能被mysql主動關閉。
正常的使用-歸還(鏈接放到隊列,隊列是先進先出,下次再取,造成一個循環)流程,可避免鏈接閒置時間過長,暫緩優化。
二、鏈接有效性檢測
三、鏈接泄露檢查
博主開始寫代碼了,須要一段時間
鏈接有效性檢測,根據配置來檢測,代碼參考MysqlValidConnectionChecker。
鏈接泄露檢查,經過啓一條線程,根據配置時間進行檢查。
----------------------通過了好幾天的編寫,代碼完成了---------------------------------------------
直接上代碼。
#########v03##########
#鏈接檢測語句 checkConnectionValidationQuery=select 1
#歸還鏈接時檢測鏈接,true false
checkConnectionWhenReturn=true #定時檢測鏈接間隔時長(分鐘) periodCheckConnectionTimeMin=10 #鏈接泄露檢測 connectionLeakCheck=true #鏈接泄露檢測間隔時長(分鐘) connectionLeakCheckPeriodTimeMin=10 #強制歸還鏈接時長(小時) forceReturnConnectionTimeHour=6
說明:
1)配置能夠分爲1個基礎,3個部分。
1個基礎是指鏈接檢測語句,3個部分分別對應歸還鏈接時檢測鏈接、定時檢測鏈接間隔時長、鏈接泄露檢測
2)定時檢測主要是防止鏈接中斷了不能自動生成新的鏈接,鏈接間隔時長若是設爲0,則不會定時檢測。
1 package me.lovegao.gdao.connection; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 import java.util.ArrayList; 6 import java.util.HashSet; 7 import java.util.Iterator; 8 import java.util.List; 9 import java.util.Map; 10 import java.util.Map.Entry; 11 import java.util.Properties; 12 import java.util.Queue; 13 import java.util.Set; 14 import java.util.concurrent.ConcurrentHashMap; 15 import java.util.concurrent.ConcurrentLinkedQueue; 16 import java.util.concurrent.ExecutorService; 17 import java.util.concurrent.Executors; 18 19 import org.apache.commons.lang3.StringUtils; 20 import org.apache.commons.lang3.math.NumberUtils; 21 import org.slf4j.Logger; 22 import org.slf4j.LoggerFactory; 23 24 import me.lovegao.gdao.bean.SystemConstant; 25 import me.lovegao.gdao.util.ConnectionUtil; 26 27 /** 28 * 第二版簡易鏈接池實現<br/> 29 * 主要增長鏈接有效性檢測,包括歸還鏈接時檢測,定時檢測鏈接 30 * 31 * @author simple 32 * 33 */ 34 public class SimpleV2ConnectionPool extends SimpleConnectionPool { 35 private final static Logger log = LoggerFactory.getLogger(SimpleV2ConnectionPool.class); 36 private ExecutorService ES; 37 // 歸還鏈接時檢測鏈接,這步最好作成異步的,避免影響歸還速度 38 private boolean checkConnectionWhenReturn = false; 39 // 鏈接檢測語句 40 private String checkConnectionValidationQuery; 41 // 定時檢測鏈接的時間(分鐘) 42 private int periodCheckConnectionTimeMin; 43 /** 待檢測鏈接 **/ 44 private volatile Queue<Connection> TO_CHECK_CONNECTION_POOL; 45 //查詢超時時間 46 private final int QUERY_TIMEOUT_SECONDS; 47 //鏈接泄露檢測 48 private boolean checkConnectionLeak = false; 49 //鏈接泄露檢測間隔時長-分鐘 50 private int checkConnectionLeakPeriodTimeMin = 30; 51 //強制歸還鏈接時長(小時) 52 private double forceReturnConnectionTimeHour; 53 //鏈接最大空閒時長(小時) 54 // private double connectionMaxIdleTimeHour; 55 /**鏈接最後借出時間**/ 56 private Map<Integer, Long> CONNECTION_OUT_TIME_MAP_POOL = null; 57 58 public SimpleV2ConnectionPool(Properties properties) throws Exception { 59 super(properties); 60 QUERY_TIMEOUT_SECONDS = super.getQueryTimeoutSecond(); 61 initProp(properties); 62 initCheck(); 63 } 64 65 private void initProp(Properties properties) { 66 //鏈接有效性檢測配置 67 if (properties.containsKey(SystemConstant.STR_CHECK_CONNECTION_VALIDATION_QUERY)) { 68 checkConnectionValidationQuery = properties 69 .getProperty(SystemConstant.STR_CHECK_CONNECTION_VALIDATION_QUERY); 70 String checkWhenReturn = properties.getProperty(SystemConstant.STR_CHECK_CONNECTION_WHEN_RETURN); 71 if (checkWhenReturn.toLowerCase().equals("true")) { 72 checkConnectionWhenReturn = true; 73 } 74 if (properties.containsKey(SystemConstant.STR_PERIOD_CHECK_CONNECTION_TIME_MIN)) { 75 String periodTimeStr = properties.getProperty(SystemConstant.STR_PERIOD_CHECK_CONNECTION_TIME_MIN); 76 if (StringUtils.isNumeric(periodTimeStr)) { 77 periodCheckConnectionTimeMin = Integer.parseInt(periodTimeStr); 78 } 79 } 80 } 81 //鏈接泄露檢測配置 82 if (properties.containsKey(SystemConstant.STR_CONNECTION_LEAK_CHECK)) { 83 String leakCheckStr = properties.getProperty(SystemConstant.STR_CONNECTION_LEAK_CHECK); 84 if (leakCheckStr.toLowerCase().equals("true")) { 85 String leakCheckPeriodTimeStr = properties.getProperty(SystemConstant.STR_CONNECTION_LEAK_CHECK_PERIOD_TIME_MIN); 86 if (StringUtils.isNumeric(leakCheckPeriodTimeStr)) { 87 checkConnectionLeakPeriodTimeMin = Integer.parseInt(leakCheckPeriodTimeStr); 88 } 89 String forceReturnTimeStr = properties.getProperty(SystemConstant.STR_FORCE_RETURN_CONNECTION_TIME_HOUR); 90 if (NumberUtils.isNumber(forceReturnTimeStr)) { 91 forceReturnConnectionTimeHour = Double.parseDouble(forceReturnTimeStr); 92 if(forceReturnConnectionTimeHour > 0) { 93 checkConnectionLeak = true; 94 } 95 } 96 //最大空閒檢測,功能上和定時檢測鏈接重複,暫時不開發。 97 98 //須要同時配置(強制歸還時間)才能檢測鏈接泄露 99 if(forceReturnConnectionTimeHour > 0) { 100 checkConnectionLeak = true; 101 } 102 } 103 } 104 StringBuilder infoSb = new StringBuilder(); 105 infoSb.append("SimpleV2ConnectionPoolInitDone------") 106 .append(",checkConnectionValidationQuery:").append(checkConnectionValidationQuery) 107 .append(",checkConnectionWhenReturn:").append(checkConnectionWhenReturn) 108 .append(",periodCheckConnectionTimeMin:").append(periodCheckConnectionTimeMin) 109 .append(",checkConnectionLeak:").append(checkConnectionLeak) 110 .append(",checkConnectionLeakPeriodTimeMin:").append(checkConnectionLeakPeriodTimeMin) 111 .append(",forceReturnConnectionTimeHour:").append(forceReturnConnectionTimeHour); 112 System.out.println(infoSb.toString()); 113 } 114 115 @Override 116 public Connection getConnection() throws Exception { 117 Connection conn = super.getConnection(); 118 if(checkConnectionLeak) { 119 int connHashCode = conn.hashCode(); 120 CONNECTION_OUT_TIME_MAP_POOL.put(connHashCode, System.currentTimeMillis()); 121 } 122 return conn; 123 } 124 125 @Override 126 public void returnConnection(Connection conn) { 127 //檢測鏈接泄露 128 if(checkConnectionLeak) { 129 int connHashCode = conn.hashCode(); 130 //鏈接超時過長,已經被主動移除 131 if(!CONNECTION_OUT_TIME_MAP_POOL.containsKey(connHashCode)) { 132 return; 133 } else { 134 CONNECTION_OUT_TIME_MAP_POOL.remove(connHashCode); 135 } 136 } 137 //檢測歸還鏈接 138 if (checkConnectionWhenReturn) { 139 if(TO_CHECK_CONNECTION_POOL.isEmpty()) { 140 synchronized(TO_CHECK_CONNECTION_POOL) { 141 if(TO_CHECK_CONNECTION_POOL.isEmpty()) { 142 TO_CHECK_CONNECTION_POOL.add(conn); 143 TO_CHECK_CONNECTION_POOL.notifyAll(); 144 } else { 145 TO_CHECK_CONNECTION_POOL.add(conn); 146 } 147 } 148 } else { 149 TO_CHECK_CONNECTION_POOL.add(conn); 150 } 151 } else { 152 superReturnConnection(conn); 153 } 154 } 155 156 157 @Override 158 public void closeConnectionPool() { 159 if(ES != null) { 160 ES.shutdownNow(); 161 } 162 super.closeConnectionPool(); 163 } 164 165 private void superReturnConnection(Connection conn) { 166 super.returnConnection(conn); 167 } 168 169 private Connection superGetByConnectionHashCode(int hashCode) { 170 return super.getByConnectionHashCode(hashCode); 171 } 172 173 // 初始化檢查 174 private void initCheck() { 175 int threadPoolSize = 0; 176 if(checkConnectionWhenReturn) { 177 threadPoolSize += 2; 178 TO_CHECK_CONNECTION_POOL = new ConcurrentLinkedQueue(); 179 } 180 if(periodCheckConnectionTimeMin > 0) { 181 threadPoolSize++; 182 } 183 //須要同時配置(強制歸還時間、最大空閒時間)才能檢測鏈接泄露 184 if(checkConnectionLeak) { 185 threadPoolSize++; 186 CONNECTION_OUT_TIME_MAP_POOL = new ConcurrentHashMap(); 187 } 188 if(threadPoolSize > 0) { 189 ES = Executors.newFixedThreadPool(threadPoolSize); 190 // 檢查歸還鏈接 191 if(checkConnectionWhenReturn) { 192 //啓兩個線程同時檢測 193 for(int i=0; i<2; i++) { 194 ES.execute(new ReturnConnectionCheck()); 195 } 196 } 197 //定時檢測鏈接 198 if (periodCheckConnectionTimeMin > 0) { 199 ES.execute(new ConnectionPeriodCheck()); 200 } 201 //鏈接泄露檢測 202 if(checkConnectionLeak) { 203 ES.execute(new ConnectionLeakCheck()); 204 } 205 } 206 } 207 208 /** 209 * 鏈接泄露定時檢測 210 * @author simple 211 * 212 */ 213 class ConnectionLeakCheck implements Runnable { 214 int sleepTimeMs = checkConnectionLeakPeriodTimeMin * 60 * 1000; 215 @Override 216 public void run() { 217 Set<Integer> preConnHashCodeSet = new HashSet(); 218 while (true) { 219 if(ES.isShutdown()) { 220 break; 221 } 222 try { 223 Thread.sleep(sleepTimeMs); 224 } catch (Exception e) { 225 log.error("ConnectionLeakCheckSleepException", e); 226 } 227 try { 228 checkConnectionLeak(preConnHashCodeSet); 229 } catch (Exception e) { 230 log.error("ConnectionLeakCheckException", e); 231 } 232 } 233 } 234 } 235 236 //檢測鏈接泄露 237 private void checkConnectionLeak(Set<Integer> preConnHashCodeSet) throws Exception { 238 if(CONNECTION_OUT_TIME_MAP_POOL.size() < 1) { 239 preConnHashCodeSet = new HashSet(); 240 } else { 241 Iterator<Entry<Integer, Long>> connHashCodeIt = CONNECTION_OUT_TIME_MAP_POOL.entrySet().iterator(); 242 //先對比先後兩次的鏈接,若是有相同的,再檢測相同的鏈接 243 if(preConnHashCodeSet.size() == 0) { 244 while(connHashCodeIt.hasNext()) { 245 preConnHashCodeSet.add(connHashCodeIt.next().getKey()); 246 } 247 } else { 248 StringBuilder logSb = new StringBuilder(); 249 long timeFlag = (long) (System.currentTimeMillis() - forceReturnConnectionTimeHour * 3600 * 1000); 250 logSb.append("ConnectionLeakCheck---") 251 .append(",timeFlag:").append(timeFlag) 252 .append(",forceReturnConnectionTimeHour:").append(forceReturnConnectionTimeHour); 253 List<Integer> toCloseConnectionHashCodeList = new ArrayList(); 254 logSb.append(",toCloseConn,{"); 255 //過濾出兩次集合重合,且已經超時的元素 256 while(connHashCodeIt.hasNext()) { 257 Entry<Integer, Long> connEntry = connHashCodeIt.next(); 258 int connHashCode = connEntry.getKey(); 259 if(preConnHashCodeSet.contains(connHashCode) && connEntry.getValue() < timeFlag) { 260 toCloseConnectionHashCodeList.add(connHashCode); 261 logSb.append(connHashCode).append(":").append(connEntry.getValue()).append(","); 262 } 263 } 264 logSb.append("}"); 265 if(toCloseConnectionHashCodeList.size() > 0) { 266 for(Integer connHashCode : toCloseConnectionHashCodeList) { 267 Connection conn = superGetByConnectionHashCode(connHashCode); 268 if(conn != null) { 269 try { 270 conn.close(); 271 } catch (SQLException e) { 272 log.error("closeConnectionException", e); 273 } 274 CONNECTION_OUT_TIME_MAP_POOL.remove(connHashCode); 275 superReturnConnection(conn); 276 } 277 } 278 } 279 log.info(logSb.toString()); 280 //進行過一次檢測以後,對以前存儲的進行初始化 281 preConnHashCodeSet = new HashSet(); 282 } 283 } 284 } 285 286 /** 287 * 歸還鏈接檢測 288 * @author simple 289 * 290 */ 291 class ReturnConnectionCheck implements Runnable { 292 @Override 293 public void run() { 294 while (true) { 295 if(ES.isShutdown()) { 296 break; 297 } 298 Connection toCheckConn = TO_CHECK_CONNECTION_POOL.poll(); 299 if (toCheckConn == null) { 300 try { 301 synchronized(TO_CHECK_CONNECTION_POOL) { 302 TO_CHECK_CONNECTION_POOL.wait(); 303 } 304 } catch (InterruptedException e) { 305 log.error("checkReturnConnectionWaitException", e); 306 } 307 } else { 308 boolean canUse = ConnectionUtil.isValidConnection(toCheckConn, checkConnectionValidationQuery, 309 QUERY_TIMEOUT_SECONDS); 310 if (!canUse) { 311 try { 312 toCheckConn.close(); 313 } catch (SQLException e) { 314 log.error("checkReturnConnectionCloseConnException", e); 315 } 316 } 317 superReturnConnection(toCheckConn); 318 } 319 } 320 } 321 } 322 323 /** 324 * 鏈接定時檢測 325 * @author simple 326 * 327 */ 328 class ConnectionPeriodCheck implements Runnable { 329 int sleepTimeMs = periodCheckConnectionTimeMin * 60 * 1000; 330 @Override 331 public void run() { 332 while (true) { 333 if(ES.isShutdown()) { 334 break; 335 } 336 try { 337 Thread.sleep(sleepTimeMs); 338 } catch (Exception e) { 339 log.error("checkReturnConnectionSleepException", e); 340 } 341 while(true) { 342 //是否繼續檢測 343 boolean continueCheck = false; 344 Connection toCheckConn = null; 345 try { 346 toCheckConn = getConnection(); 347 if (toCheckConn != null) { 348 boolean canUse = ConnectionUtil.isValidConnection(toCheckConn, 349 checkConnectionValidationQuery, QUERY_TIMEOUT_SECONDS); 350 if (!canUse) { 351 toCheckConn.close(); 352 //鏈接不可用,繼續檢測其餘鏈接是否正常 353 continueCheck = true; 354 log.info("oneConnectionCannotUse,closeIt....."); 355 } 356 } 357 } catch (Exception e) { 358 log.error("checkReturnConnectionException", e); 359 continueCheck = true; 360 } finally { 361 if (toCheckConn != null) { 362 superReturnConnection(toCheckConn); 363 } 364 } 365 if(continueCheck) { 366 log.info("checkOneConnectionCannotUse,beginToCheckOtherConnection...."); 367 try { 368 Thread.sleep(500); 369 } catch (InterruptedException e) { 370 log.error("checkReturnConnectionWaitException", e); 371 } 372 } else { 373 break; 374 } 375 } 376 } 377 } 378 } 379 }
歸還鏈接的時候,若是不採用異步,那麼歸還鏈接的線程必須等待鏈接確認完畢以後才能繼續執行,這樣作感受性能不是最優的。
因此引入了異步,歸還鏈接時,鏈接直接放到一個待檢測的容器裏,不須要等待檢測完以後再返回。
待檢測鏈接由檢測線程異步進行檢測。檢測現場從待檢測容器裏取鏈接進行檢測,必然會出現空的狀況。
出現了空的狀況怎麼作好呢,是在那裏自旋等待?是休眠一段時間再檢測?仍是等待呢?
自旋等待,浪費計算資源;休眠的話,休眠時長很差肯定,誰知道下一毫秒會發生什麼?萬一由於鏈接未及時檢測出現了鏈接用盡,豈不是很尷尬?
因此,我選擇了等待,當線程歸還時,主動喚醒等待線程。
代碼實現以後,測試的時候,我發現運行報錯。wait()、notifyAll()方法不是那樣用的,須要獲取鎖。
添加了鎖以後,爲了儘可能減小同步帶來的性能損失,我採起了寫單例時常常提到的雙重檢查:我不須要每次都要拿鎖、通知,只須要在待檢測鏈接池是空的時候才須要進行拿鎖、通知。、
鏈接泄露檢測,主要是爲了防止鏈接被借出去以後,好久都沒有歸還的情景。
好久不歸還,這個鏈接還佔着鏈接池的坑,卻無法被複用,因此須要進行檢測。
檢測須要肯定的就是,取出多久算久?而後就是,多久檢測一次?
具體實現的時候,還有一個問題,就是強制歸還的鏈接應該怎麼歸還?直接放回鏈接池嗎?萬一鏈接真的還在被別的線程使用怎麼辦?
因此這裏,我採起先把鏈接關閉了,而後再歸還。
爲了保持鏈接池的鏈接都是最終可用的,因此須要對鏈接池的鏈接進行定時的檢測。
若是鏈接不可用,就把鏈接關閉,而後從鏈接池去除。
1)配置
##驅動名稱 driverName=com.mysql.jdbc.Driver ##鏈接url connectionUrl=jdbc:mysql://localhost:3306/simple?autoReconnect=true&useServerPrepStmts=false&rewriteBatchedStatements=true&connectTimeout=1000&useUnicode=true&characterEncoding=utf-8 ##用戶名 userName=simple ##用戶密碼 userPassword=123456 ##初始化鏈接數 initConnectionNum=10 ##最大鏈接數 maxConnectionNum=50 ##最大查詢等待時間 maxQueryTime=3 #########v03########## #歸還鏈接時檢測鏈接,true false checkConnectionWhenReturn=true #鏈接檢測語句 checkConnectionValidationQuery=select 1 #定時檢測鏈接間隔時長(分鐘) periodCheckConnectionTimeMin=1 #鏈接泄露檢測 connectionLeakCheck=false #鏈接泄露檢測間隔時長(分鐘) connectionLeakCheckPeriodTimeMin=1 #強制歸還鏈接時長(小時) forceReturnConnectionTimeHour=0.01
2)測試代碼
package me.lovegao.gdao.connpool; import java.sql.Connection; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import me.lovegao.gdao.bean.SystemConstant; import me.lovegao.gdao.connection.IConnectionPool; import me.lovegao.gdao.connection.SimpleV2ConnectionPool; public class V2ConnectionPoolTest { private final static Logger log = LoggerFactory.getLogger(V2ConnectionPoolTest.class); public static void main(String[] args) throws Exception { String dbPath = "mysql2.properties"; log.info("hello-----------------"); log.warn("hello-----------------"); periodCheckConnection(dbPath); } /** * 鏈接泄露檢測 * @param dbPath * @throws Exception */ public static void checkConnectionLeak(String dbPath) throws Exception { Properties dbProp = CommonUtil.loadProp(dbPath); dbProp.setProperty(SystemConstant.STR_PERIOD_CHECK_CONNECTION_TIME_MIN, "10"); dbProp.setProperty(SystemConstant.STR_CONNECTION_LEAK_CHECK, "true"); IConnectionPool connPool = new SimpleV2ConnectionPool(dbProp); Connection conn = connPool.getConnection(); Thread.sleep(240000); connPool.returnConnection(conn); Thread.sleep(61000); connPool.closeConnectionPool(); } /** * 鏈接定時檢測 * @param dbPath * @throws Exception */ public static void periodCheckConnection(String dbPath) throws Exception { Properties dbProp = CommonUtil.loadProp(dbPath); dbProp.setProperty(SystemConstant.STR_PERIOD_CHECK_CONNECTION_TIME_MIN, "1"); IConnectionPool connPool = new SimpleV2ConnectionPool(dbProp); Thread.sleep(120000); Connection conn = connPool.getConnection(); connPool.returnConnection(conn); Thread.sleep(2061000); // connPool.closeConnectionPool(); } /** * 歸還鏈接檢測 * @param dbPath * @throws Exception */ public static void checkWhenReturn(String dbPath) throws Exception { Properties dbProp = CommonUtil.loadProp(dbPath); IConnectionPool connPool = new SimpleV2ConnectionPool(dbProp); Connection conn = connPool.getConnection(); conn.close(); connPool.returnConnection(conn); Thread.sleep(20000); connPool.closeConnectionPool(); } }
我是分佈執行的測試,經過debug來校驗的流程,面對這種項目,不知道單測該如何寫。
我是經過中間把數據庫關了,又打開,來鏈接定時檢測的。結果證實沒有問題。
框架優化版本已提交到git:https://github.com/shuimutong/gdao.git,歡迎指點
相關測試代碼:https://github.com/shuimutong/useDemo.git ./gdao-demo下。
-----------------------本文完------------------------------