1. 程式人生 > >數據庫連接池實現原理

數據庫連接池實現原理

出現 ins 初始化 基本原理 實現 plain comment 數據庫 ber

一、為什麽在連接數據庫時要使用連接池

數據庫連接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現得尤為突出。 一個數據庫連接對象均對應一個物理數據庫連接,每次操作都打開一個物理連接,使用完都關閉連接,這樣造成系統的 性能低下。 數據庫連接池的解決方案是在應用程序啟動時建立足夠的數據庫連接,並講這些連接組成一個連接池(簡單說:在一個“池”裏放了好多半成品的數據庫聯接對象),由應用程序動態地對池中的連接進行申請、使用和釋放。對於多於連接池中連接數的並發請求,應該在請求隊列中排隊等待。並且應用程序可以根據池中連接的使用率,動態增加或減少池中的連接數。 連接池技術盡可能多地重用了消耗內存地資源,大大節省了內存,提高了服務器地服務效率,能夠支持更多的客戶服務。通過使用連接池,將大大提高程序運行效率,同時,我們可以通過其自身的管理機制來監視數據庫連接的數量、使用情況等。

二、數據庫連接池的基本原理

數據庫連接池的基本思想就是為數據庫連接 建立一個“緩沖池”。預先在緩沖池中放入一定數量的連接,當需要建立數據庫連接時,只需從“緩沖池”中取出一個,使用完畢之後再放回去。我們可以通過設定 連接池最大連接數來防止系統無盡的與數據庫連接。更為重要的是我們可以通過連接池的管理機制監視數據庫的連接的數量?使用情況,為系統開發?測試及性能調 整提供依據。

三、數據庫連接池的工作原理

連接池的工作原理主要由三部分組成,分別為連接池的建立、連接池中連接的使用管理、連接池的關閉。

第一、連接池的建立。一般在系統初始化時,連接池會根據系統配置建立,並在池中創建了幾個連接對象,以便使用時能從連接池中獲取。連接池中的連接不能隨意創建和關閉,這樣避免了連接隨意建立和關閉造成的系統開銷。Java中提供了很多容器類可以方便的構建連接池,例如Vector、Stack等。

第二、連接池的管理。連接池管理策略是連接池機制的核心,連接池內連接的分配和釋放對系統的性能有很大的影響。其管理策略是:

當客戶請求數據庫連接時,首先查看連接池中是否有空閑連接,如果存在空閑連接,則將連接分配給客戶使用;如果沒有空閑連接,則查看當前所開的連接數是否已經達到最大連接數,如果沒達到就重新創建一個連接給請求的客戶;如果達到就按設定的最大等待時間進行等待,如果超出最大等待時間,則拋出異常給客戶。

當客戶釋放數據庫連接時,先判斷該連接的引用次數是否超過了規定值,如果超過就從連接池中刪除該連接,否則保留為其他客戶服務。

該策略保證了數據庫連接的有效復用,避免頻繁的建立、釋放連接所帶來的系統資源開銷。

第三、連接池的關閉。當應用程序退出時,關閉連接池中所有的連接,釋放連接池相關的資源,該過程正好與創建相反。

四、連接池關鍵問題分析

  1、並發問題

  為了使連接管理服務具有最大的通用性,必須考慮多線程環境,即並發問題。這個問題相對比較好解決,因為Java語言自身提供了對並發管理的支 持,使用synchronized關鍵字即可確保線程是同步的。使用方法為直接在類方法前面加上synchronized關鍵字,如:

  public synchronized Connection getConnection()

  2、多數據庫服務器和多用戶

  對於大型的企業級應用,常常需要同時連接不同的數據庫(如連接Oracle和Sybase)。如何連接不同的數據庫呢?我們采用的策略是:設計 一個符合單例模式的連接池管理類,在連接池管理類的唯一實例被創建時讀取一個資源文件,其中資源文件中存放著多個數據庫的url地址()?用戶名()?密 碼()等信息。如 tx.url=172.21.15.123:5000/tx_it,tx.user=yang,tx.password=yang321。根據資源文件提 供的信息,創建多個連接池類的實例,每一個實例都是一個特定數據庫的連接池。連接池管理類實例為每個連接池實例取一個名字,通過不同的名字來管理不同的連 接池。

  對於同一個數據庫有多個用戶使用不同的名稱和密碼訪問的情況,也可以通過資源文件處理,即在資源文件中設置多個具有相同url地址,但具有不同用戶名和密碼的數據庫連接信息。

  3、事務處理

  我們知道,事務具有原子性,此時要求對數據庫的操作符合“ALL-ALL-NOTHING”原則,即對於一組SQL語句要麽全做,要麽全不做。

  在Java語言中,Connection類本身提供了對事務的支持,可以通過設置Connection的AutoCommit屬性為 false,然後顯式的調用commit或rollback方法來實現。但要高效的進行Connection復用,就必須提供相應的事務支持機制。可采用 每一個事務獨占一個連接來實現,這種方法可以大大降低事務管理的復雜性。

  4、連接池的分配與釋放

  連接池的分配與釋放,對系統的性能有很大的影響。合理的分配與釋放,可以提高連接的復用度,從而降低建立新連接的開銷,同時還可以加快用戶的訪問速度。

  對於連接的管理可使用空閑池。即把已經創建但尚未分配出去的連接按創建時間存放到一個空閑池中。每當用戶請求一個連接時,系統首先檢查空閑池內 有沒有空閑連接。如果有就把建立時間最長(通過容器的順序存放實現)的那個連接分配給他(實際是先做連接是否有效的判斷,如果可用就分配給用戶,如不可用 就把這個連接從空閑池刪掉,重新檢測空閑池是否還有連接);如果沒有則檢查當前所開連接池是否達到連接池所允許的最大連接數(maxConn),如果沒有 達到,就新建一個連接,如果已經達到,就等待一定的時間(timeout)。如果在等待的時間內有連接被釋放出來就可以把這個連接分配給等待的用戶,如果 等待時間超過預定時間timeout,則返回空值(null)。系統對已經分配出去正在使用的連接只做計數,當使用完後再返還給空閑池。對於空閑連接的狀 態,可開辟專門的線程定時檢測,這樣會花費一定的系統開銷,但可以保證較快的響應速度。也可采取不開辟專門線程,只是在分配前檢測的方法。

  5、連接池的配置與維護

  連接池中到底應該放置多少連接,才能使系統的性能最佳?系統可采取設置最小連接數(minConn)和最大連接數(maxConn)來控制連接 池中的連接。最小連接數是系統啟動時連接池所創建的連接數。如果創建過多,則系統啟動就慢,但創建後系統的響應速度會很快;如果創建過少,則系統啟動的很 快,響應起來卻慢。這樣,可以在開發時,設置較小的最小連接數,開發起來會快,而在系統實際使用時設置較大的,因為這樣對訪問客戶來說速度會快些。最大連 接數是連接池中允許連接的最大數目,具體設置多少,要看系統的訪問量,可通過反復測試,找到最佳點。

  如何確保連接池中的最小連接數呢?有動態和靜態兩種策略。動態即每隔一定時間就對連接池進行檢測,如果發現連接數量小於最小連接數,則補充相應數量的新連接,以保證連接池的正常運轉。靜態是發現空閑連接不夠時再去檢查。

技術分享圖片

程序開發過程中,存在很多問題:

首先,每一次web請求都要建立一次數據庫連接。建立連接是一個費時的活動,每次都得花費0.05s~1s的時間,而且系統還要分配內存資源。這個時間對於一次或幾次數據庫操作,或許感覺不出系統有多大的開銷。

可是對於現在的web應用,尤其是大型電子商務網站,同時有幾百人甚至幾千人在線是很正常的事。在這種情況下,頻繁的進行數據庫連接操作勢必占用很多的系統資源,網站的響應速度必定下降,嚴重的甚至會造成服務器的崩潰。不是危言聳聽,這就是制約某些電子商務網站發展的技術瓶頸問題。其次,對於每一次數據庫連接,使用完後都得斷開。否則,如果程序出現異常而未能關閉,將會導致數據庫系統中的內存泄漏,最終將不得不重啟數據庫

通過上面的分析,我們可以看出來,“數據庫連接”是一種稀缺的資源,為了保障網站的正常使用,應該對其進行妥善管理。其實我們查詢完數據庫後,如果不關閉連接,而是暫時存放起來,當別人使用時,把這個連接給他們使用。就避免了一次建立數據庫連接和斷開的操作時間消耗。

數據庫連接池的基本思想:就是為數據庫連接建立一個“緩沖池”。預先在緩沖池中放入一定數量的連接,當需要建立數據庫連接時,只需從“緩沖池”中取出一個,使用完畢之後再放回去。我們可以通過設定連接池最大連接數來防止系統無盡的與數據庫連接

創建數據庫連接池大概有3個步驟:

① 創建ConnectionPool實例,並初始化創建10個連接,保存在Vector中(線程安全)
② 實現getConnection()從連接庫中獲取一個可用的連接
③ returnConnection(conn) 提供將連接放回連接池中方法

五、連接池實現代碼(java)

[java] view plain copy
  1. package book.util;
  2. import java.sql.Connection;
  3. import java.sql.DatabaseMetaData;
  4. import java.sql.Date;
  5. import java.sql.Driver;
  6. import java.sql.DriverManager;
  7. import java.sql.PreparedStatement;
  8. import java.sql.ResultSet;
  9. import java.sql.SQLException;
  10. import java.sql.Statement;
  11. import java.util.Vector;
  12. public class Pool {
  13. public static void main(String[] args) {
  14. Pool pool = new Pool("com.microsoft.sqlserver.jdbc.SQLServerDriver","jdbc:sqlserver://localhost:1433;DataBaseName=Book","sa","aaaaaa");
  15. try {
  16. pool.createConnections(4);
  17. } catch (SQLException e) {
  18. e.printStackTrace();
  19. }
  20. Connection conn = pool.getConnection();
  21. try {
  22. String sql = "select * from allbook";
  23. PreparedStatement ps;
  24. ps = conn.prepareStatement(sql);
  25. ResultSet rs=ps.executeQuery();
  26. while(rs.next()){
  27. System.out.println(rs.getString("BOOKNAME"));
  28. }
  29. } catch (SQLException e) {
  30. // TODO Auto-generated catch block
  31. e.printStackTrace();
  32. }finally{
  33. pool.returnConnection(conn);
  34. }
  35. //long startTime=System.currentTimeMillis();
  36. //long endTime=System.currentTimeMillis();
  37. //System.out.println("程序運行時間: "+(endTime-startTime)+"ms");
  38. }
  39. private String jdbcDriver = "";//數據庫驅動
  40. private String dbUrl = "";//數據庫url
  41. private String dbUsername = "";//數據庫用戶名
  42. private String dbPassword = "";//數據庫密碼
  43. private String testTable = "";
  44. private int initialConnectionsNum = 10;//連接池初始連接數
  45. private int maxConnectionsNum = 50;//連接池最大連接數
  46. private int incrementalConnections = 5;//每次動態添加的連接數
  47. private Vector<PooledConnection> connections = null;//向量,存放連接池中的連接,初始為空
  48. /*無參構造函數*/
  49. public Pool()
  50. {}
  51. /*帶參數的構造函數
  52. * 初始化數據庫驅動、數據庫url、數據庫用戶名、數據庫密碼、測試表
  53. * */
  54. public Pool(String driver, String url, String name, String pass)
  55. {
  56. this.jdbcDriver = driver;
  57. this.dbUrl = url;
  58. this.dbUsername = name;
  59. this.dbPassword = pass;
  60. //this.testTable = table;
  61. try {
  62. this.createPool();
  63. } catch (InstantiationException e) {
  64. // TODO Auto-generated catch block
  65. e.printStackTrace();
  66. } catch (IllegalAccessException e) {
  67. // TODO Auto-generated catch block
  68. e.printStackTrace();
  69. } catch (ClassNotFoundException e) {
  70. // TODO Auto-generated catch block
  71. e.printStackTrace();
  72. } catch (SQLException e) {
  73. // TODO Auto-generated catch block
  74. e.printStackTrace();
  75. }
  76. }
  77. /*函數,創建連接池*/
  78. public synchronized void createPool()
  79. throws InstantiationException, IllegalAccessException,
  80. ClassNotFoundException, SQLException
  81. {
  82. /*確保連接池為創建,如果已經創建,則保存連接的向量不為空
  83. * */
  84. if (this.connections != null)
  85. {
  86. return ;
  87. }
  88. //驅動器實例化
  89. Driver driver = (Driver)(Class.forName(this.jdbcDriver).newInstance());
  90. //註冊驅動器
  91. DriverManager.registerDriver(driver);
  92. //創建保存連接的向量
  93. this.connections = new Vector<PooledConnection>();
  94. //創建數據庫連接
  95. this.createConnections(this.initialConnectionsNum);
  96. }
  97. /*函數,創建數據庫連接
  98. * */
  99. private void createConnections (int num) throws SQLException
  100. {
  101. /*循環創建連接
  102. * 需要首先檢查當前連接數是否已經超出連接池最大連接數
  103. * */
  104. for (int i = 0; i < num; ++i)
  105. {
  106. //檢查
  107. if (this.connections.size() >= this.maxConnectionsNum)
  108. {
  109. return;
  110. }
  111. //創建連接
  112. this.connections.addElement
  113. (new PooledConnection(newConnection()));
  114. }
  115. }
  116. /*函數,創建一個數據庫連接*/
  117. private Connection newConnection() throws SQLException
  118. {
  119. /*創建連接*/
  120. Connection con = DriverManager.getConnection(this.dbUrl,
  121. this.dbUsername, this.dbPassword);
  122. /*如果是第一次創建連接,則檢查所連接的數據庫的允許最大連接數是否小於
  123. * 我們所設定的最大連接數*/
  124. if (this.connections.size() == 0)
  125. {
  126. DatabaseMetaData metadata = con.getMetaData();
  127. //得到數據庫最大連接數
  128. int dbMaxConnectionsNum = metadata.getMaxConnections();
  129. //如果數據庫最大連接數更小,則更改我們所設定的連接池最大連接數
  130. if (dbMaxConnectionsNum > 0
  131. && this.maxConnectionsNum > dbMaxConnectionsNum)
  132. {
  133. this.maxConnectionsNum = dbMaxConnectionsNum;
  134. }
  135. }
  136. return con;
  137. }
  138. /*函數,得到一個可用連接
  139. * */
  140. public synchronized Connection getConnection ()
  141. {
  142. Connection con = null;
  143. /*檢查連接池是否已經建立*/
  144. if (this.connections == null)
  145. {
  146. return con;
  147. }
  148. //得到一個可用連接
  149. try {
  150. con = this.getFreeConnection();
  151. } catch (SQLException e) {
  152. // TODO Auto-generated catch block
  153. e.printStackTrace();
  154. }
  155. //如果未找到合適連接,循環等待、查找,知道找到合適連接
  156. while(con == null)
  157. {
  158. this.wait(30);
  159. try {
  160. con = this.getFreeConnection();
  161. } catch (SQLException e) {
  162. // TODO Auto-generated catch block
  163. e.printStackTrace();
  164. }
  165. }
  166. return con;
  167. }
  168. /*函數,得到一個可用連接*/
  169. private Connection getFreeConnection() throws SQLException
  170. {
  171. Connection con = null;
  172. //查找一個可用連接
  173. con = this.findFreeConnection();
  174. //如果未找到可用連接,就建立一些新的連接,再次查找
  175. if (con == null)
  176. {
  177. this.createConnections(this.incrementalConnections);
  178. //再次查找
  179. con = this.findFreeConnection();
  180. }
  181. return con;
  182. }
  183. /*函數,從現有連接中查找一個可用連接
  184. * 在現有的連接中(向量connections中)找到一個空閑連接,
  185. * 並測試這個鏈接是否可用,若不可用則重新建立連接,替換原來的連接*/
  186. private Connection findFreeConnection () throws SQLException
  187. {
  188. Connection con = null;
  189. for (int i = 0; i < this.connections.size(); ++i)
  190. {
  191. PooledConnection pol = (PooledConnection)this.connections.get(i);
  192. if (!pol.isBusy())
  193. {
  194. /*如果此鏈接未被使用,則返回這個連接並,設置正在使用標誌*/
  195. con = pol.getCon();
  196. pol.setBusy(true);
  197. /*測試連接是否可用*/
  198. if (!this.testCon(con))
  199. {
  200. con = this.newConnection();
  201. pol.setCon(con);
  202. }
  203. break;
  204. }
  205. }
  206. return con;
  207. }
  208. /*函數,測試連接是否可用
  209. * */
  210. private boolean testCon (Connection con)
  211. {
  212. boolean useable = true;
  213. try
  214. {
  215. Statement st = con.createStatement();
  216. ResultSet rs = st.executeQuery("select count(*) from " + this.testTable);
  217. rs.next();
  218. }
  219. catch(SQLException e)
  220. {
  221. /*上面拋出異常,連接不可用,關閉*/
  222. useable = false;
  223. this.closeConnection(con);
  224. }
  225. return useable;
  226. }
  227. /*函數,將使用完畢的連接放回連接池中
  228. * */
  229. public void returnConnection(Connection con)
  230. {
  231. /*確保連接池存在*/
  232. if (this.connections == null)
  233. {
  234. return ;
  235. }
  236. for (int i = 0; i < this.connections.size(); ++i)
  237. {
  238. PooledConnection pool = this.connections.get(i);
  239. //找到相應連接,設置正在使用標誌為false
  240. if (con == pool.getCon())
  241. {
  242. pool.setBusy(false);
  243. }
  244. }
  245. }
  246. /*函數,刷新連接池中的連接*/
  247. public synchronized void refreshConneciontPool () throws SQLException
  248. {
  249. /*確保連接池存在*/
  250. if (this.connections == null)
  251. {
  252. return ;
  253. }
  254. for (int i = 0; i < this.connections.size(); ++i)
  255. {
  256. PooledConnection pool = this.connections.get(i);
  257. if (pool.isBusy())
  258. {
  259. this.wait(5000);
  260. }
  261. this.closeConnection(pool.getCon());
  262. pool.setCon(this.newConnection());
  263. pool.setBusy(false);
  264. }
  265. }
  266. /*函數,關閉連接池*/
  267. public void closeConnectionPool()
  268. {
  269. /*確保連接池存在*/
  270. if (this.connections == null)
  271. {
  272. return ;
  273. }
  274. for (int i = 0; i < this.connections.size(); ++i)
  275. {
  276. PooledConnection pool = this.connections.get(i);
  277. if (pool.isBusy())
  278. {
  279. this.wait(5000);
  280. }
  281. this.closeConnection(pool.getCon());
  282. this.connections.remove(i);
  283. }
  284. this.connections = null;
  285. }
  286. /*函數,暫時無可用連接,進入等待隊列等待m秒,再試
  287. * */
  288. private void wait(int mSecond)
  289. {
  290. try {
  291. Thread.sleep(mSecond);
  292. } catch (InterruptedException e) {
  293. // TODO Auto-generated catch block
  294. e.printStackTrace();
  295. }
  296. }
  297. /**
  298. * @return the jdbcDriver
  299. */
  300. public String getJdbcDriver() {
  301. return jdbcDriver;
  302. }
  303. /**
  304. * @param jdbcDriver the jdbcDriver to set
  305. */
  306. public void setJdbcDriver(String jdbcDriver) {
  307. this.jdbcDriver = jdbcDriver;
  308. }
  309. /**
  310. * @return the dbUrl
  311. */
  312. public String getDbUrl() {
  313. return dbUrl;
  314. }
  315. /**
  316. * @param dbUrl the dbUrl to set
  317. */
  318. public void setDbUrl(String dbUrl) {
  319. this.dbUrl = dbUrl;
  320. }
  321. /**
  322. * @return the dbUsername
  323. */
  324. public String getDbUsername() {
  325. return dbUsername;
  326. }
  327. /**
  328. * @param dbUsername the dbUsername to set
  329. */
  330. public void setDbUsername(String dbUsername) {
  331. this.dbUsername = dbUsername;
  332. }
  333. /**
  334. * @return the dbPassword
  335. */
  336. public String getDbPassword() {
  337. return dbPassword;
  338. }
  339. /**
  340. * @param dbPassword the dbPassword to set
  341. */
  342. public void setDbPassword(String dbPassword) {
  343. this.dbPassword = dbPassword;
  344. }
  345. /**
  346. * @return the testTable
  347. */
  348. public String getTestTable() {
  349. return testTable;
  350. }
  351. /**
  352. * @param testTable the testTable to set
  353. */
  354. public void setTestTable(String testTable) {
  355. this.testTable = testTable;
  356. }
  357. /**
  358. * @return the initialConnectionsNum
  359. */
  360. public int getInitialConnectionsNum() {
  361. return initialConnectionsNum;
  362. }
  363. /**
  364. * @param initialConnectionsNum the initialConnectionsNum to set
  365. */
  366. public void setInitialConnectionsNum(int initialConnectionsNum) {
  367. this.initialConnectionsNum = initialConnectionsNum;
  368. }
  369. /**
  370. * @return the maxConnectionsNum
  371. */
  372. public int getMaxConnectionsNum() {
  373. return maxConnectionsNum;
  374. }
  375. /**
  376. * @param maxConnectionsNum the maxConnectionsNum to set
  377. */
  378. public void setMaxConnectionsNum(int maxConnectionsNum) {
  379. this.maxConnectionsNum = maxConnectionsNum;
  380. }
  381. /**
  382. * @return the incrementalConnections
  383. */
  384. public int getIncrementalConnections() {
  385. return incrementalConnections;
  386. }
  387. /**
  388. * @param incrementalConnections the incrementalConnections to set
  389. */
  390. public void setIncrementalConnections(int incrementalConnections) {
  391. this.incrementalConnections = incrementalConnections;
  392. }
  393. /**
  394. * @return the connections
  395. */
  396. public Vector<PooledConnection> getConnections() {
  397. return connections;
  398. }
  399. /**
  400. * @param connections the connections to set
  401. */
  402. public void setConnections(Vector<PooledConnection> connections) {
  403. this.connections = connections;
  404. }
  405. /*函數,連接使用完畢,關閉連接*/
  406. private void closeConnection (Connection con)
  407. {
  408. try
  409. {
  410. con.close();
  411. }
  412. catch(SQLException e)
  413. {
  414. e.printStackTrace();
  415. }
  416. }
  417. /*內部使用的保存數據庫連接的類
  418. * 兩個成員變量:連接、是否正在使用*/
  419. class PooledConnection
  420. {
  421. private Connection con = null;//連接
  422. private boolean busy = false;//是否正在使用,默認為非
  423. /*構造函數*/
  424. public PooledConnection(Connection con)
  425. {
  426. this.con = con;
  427. }
  428. /**
  429. * @return the con
  430. */
  431. public Connection getCon() {
  432. return con;
  433. }
  434. /**
  435. * @param con the con to set
  436. */
  437. public void setCon(Connection con) {
  438. this.con = con;
  439. }
  440. /**
  441. * @return the busy
  442. */
  443. public boolean isBusy() {
  444. return busy;
  445. }
  446. /**
  447. * @param busy the busy to set
  448. */
  449. public void setBusy(boolean busy) {
  450. this.busy = busy;
  451. }
  452. }
  453. }

數據庫連接池實現原理