什么是數據庫連接池
寫過后台的都知道,在操作數據庫之前,先要獲得數據庫連接,然后用數據庫連接去進行數據庫的增刪改查等操作。建立連接和銷毀連接都是一筆很大的性能開銷,因此產生了連接池的技術。連接池與線程池一樣,都是一種資源池,專門用於處理共享資源的。它可以維護一定數量的連接不銷毀,當有連接申請時,從池中取出供客戶使用;使用完畢則釋放歸還池中;當池中連接的數量不足時,還可以創建一部分連接。
使用連接池的優點
- 減少建立連接與銷毀連接的性能開銷,避免頻繁進行連接的建立與銷毀,提升系統性能。
- 限制客戶端向數據庫發起的數據庫訪問,避免系統宕機,也避免過多的連接被直接拒絕。
數據庫連接池的配置
目前有很多連接池框架,使用這些框架非常簡單,最主要就是對一些參數進行配置。這些參數包括最大連接數,最小連接數等,參數取值不同,對應用的性能與穩定性會造成不同的影響。下面我就以Tomcat自帶的DHCP數據庫連接池為例,通過對一個微博Web系統的同一條微博進行並發的瘋狂評論操作作壓力測試,去看看數據庫連接池的參數配置對系統會產生什么樣的影響。相信通過下面的壓力測試分析,讀者們可以體會到上述使用連接池的優點,同時也可以結合上一講對壓力測試的介紹,加深對壓力測試的理解,了解到壓力測試在系統優化中起到什么樣的作用。
測試前的配置與代碼准備
Tomcat的DHCP配置文件
如果Web應用是部署在Tomcat上的,其參數配置全在context.xml文件里。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
<?xml version='1.0' encoding='utf-8'?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- The contents of this file will be loaded for each web application -->
<Context>
<Resource
name="jdbc/drp"
type="javax.sql.DataSource"
driverClassName="org.postgresql.Driver"
maxIdle="100"
minIdle="100"
maxWait="-1"
username="postgres"
password="gdzqzxwjs95"
url="jdbc:postgresql://localhost:5432/weibo"
maxActive="100"/>
<!-- Default set of monitored resources -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->
<!-- Uncomment this to enable Comet connection tacking (provides events
on session expiration as well as webapp lifecycle) -->
<!--
<Valve className="org.apache.catalina.valves.CometConnectionManagerValve" />
-->
</Context>
|
- name是連接池的名稱,后台調用數據庫連接是需要引用這個名稱。
- type數據源類型,因為后台用java搭建,這里用java的數據源類。
- driverClassName是數據庫的驅動,這里用的是postgresql數據庫。
- maxIdle代表最大空閑連接數,0代表沒有限制。
- minIdle代表最小空閑連接數。
- maxWait代表最大等待時間,-1代表沒有限制。
- username是數據庫的用戶名。
- password是數據庫的密碼。
- url用於定位數據庫。
- maxActive代表最大連接數,0代表沒有限制。
下面的測試,我們主要圍繞maxIdle,minIdle,maxWait和maxAcitve展開。
minIdle是數據庫能維持的最小的空閑連接,當Web應用啟動時,會自動創建連接到minIdle這個數目,此后minIdle便沒有意義。
maxIdle代表數據庫能維持的最大空閑連接,當minIdle數目的連接不夠用時,連接池會繼續創建連接。當總連接沒有超過maxIdle時,新創建的連接如果被釋放不會被銷毀,仍然歸還池中;當總連接數超過maxIdle時,如果有連接被釋放那么這些連接會被銷毀,直到使連接池的連接數量達到maxIdle。此后,數據庫的總連接數不會少於maxIdle。
maxActive代表數據庫能維持的最大連接數。當maxIdle數目的數據庫連接仍然不夠用時,連接池會繼續創建連接,直到連接數達到maxActive。這些新創建的連接被釋放時直接銷毀,不歸還池中。如果數據庫連接池的總連接數達到maxAcitve,但仍然有連接申請,則這些連接會等待。一般maxIdle與maxAcitve的值應盡量接近,可以取maxIdle=maxActive。這樣可以避免高負荷系統頻繁創建與銷毀連接。
maxWait是最大等待時間。前面說過,當數據庫連接數達到maxActive,其余的連接申請會等待,而等待的最大時間就是maxWait。當等待時間超過maxWait,但仍然沒有連接被釋放時,則這些等待中的連接會被拒絕。
后台獲取和釋放數據庫連接代碼
1
2
3
|
Context ctx=
new InitialContext();
DataSource ds=(DataSource)ctx.lookup(
"java:comp/env/jdbc/drp");
Connection conn = ds.getConnection();
|
1
2
3
4
5
6
7
|
try {
if (conn != null) {
conn.close();
}
}
catch (Exception err1){
err1.printStackTrace();
}
|
代碼很簡單,連接釋放還是直接用close方法,底層會由連接池實現連接歸還而不銷毀。
壓力測試
測試任務
利用JMeter作測試工具,對同一條微博作高並發的瘋狂評論,對數據庫而言就是對評論表作高並發的插入操作,同時並發更新微博表同一條記錄的評論數屬性。
不用連接池
1秒內啟100個線程
1秒內啟200個線程
1秒內啟250個線程
1秒內啟300個線程
錯誤及分析
以上測試從1秒啟100個線程開始,不斷增加測試的線程數,直到1秒內啟動250個線程的時候開始出錯。且啟動線程的密集程度越高,錯誤率越高。此時后台拋出連接拒絕異常。
產生此異常,是因為數據庫允許的連接數往往是有限的。以postgresql數據庫為例,默認的數據庫最大允許連接數為100,當同時占用的連接數超過100個時,便會把超過的連接直接拒絕掉,同時拋出連接拒絕異常。之所以1秒產生250個線程才出錯,是因為如果機器運行足夠快,前面啟動的一部分線程已經運行完了可以被釋放,以供后續產生的線程使用。總之,只要同時占用的連接數不超過100個,就不會拒絕連接;反之多余的連接會被拒絕。
使用連接池,且maxActive超過100
配置maxActive = maxIdle = 200,maxWait = -1,1秒內啟200個線程。
錯誤及分析
postgresql里默認的最大連接數是100,如果連接池數目超過數據庫的連接數限制,則會拋出上述異常。因此maxActive參數不能超過數據庫的連接數限制。特別注意的是,即便maxActive設為100,但如果除了此程序還有其他程序訪問同一數據庫,那么其他程序也會占用數據庫連接數,使數據庫可用連接小於100,從而使此程序拋出上述異常!因此,准確來說,連接池的maxActive參數應該是不能大於數據庫最大的可用連接數!
使用連接池,maxActive=100,maxWait設為5秒
設置maxIdle = maxActive = 100,maxWait = 5000,1秒內啟250個線程
設置maxIdle = maxActive = 100,maxWait = 5000,1秒內啟300個線程
設置maxIdle = maxActive = 100,maxWait = 5000,1秒內啟400個線程
設置maxIdle = maxActive = 100,maxWait = 5000,1秒內啟450個線程
錯誤及分析
當1秒啟400個線程以上時,后台會拋出上述異常。
因為上面的測試maxWait設為5秒,當請求的連接數大於maxActive時,連接池不會再創建新的連接,而是等待已有的連接變為空閑。當等待的時間超過maxWait設定的時間,則會拋出上述異常。
maxWait無限制
設置maxIdle = maxActive = 100,maxWait = -1,1秒內啟400個線程
設置maxIdle = maxActive = 100,maxWait = -1,1秒內啟500個線程
設置maxIdle = maxActive = 100,maxWait = -1,1秒內啟700個線程
設置maxIdle = maxActive = 100,maxWait = -1,1秒內啟1000個線程
分析
只要maxWait不加限制,且連接池設置的最大連接數不超過數據庫允許的最大連接數,無論有多少的連接請求,都不會拋出異常,不會有錯誤。但是由於內存的限制,過多的請求必然導致響應時間變長,用戶體驗變差。為了測試方便,上面的壓力測試我是沒有用Duration Assertion的,只通過Response Assertion對http response的返回內容作斷言判斷是否正確返回。下面,我就對壓力測試加上Duration Assertion,對登錄響應限制為5秒,對評論微博的響應限制為10秒,繼續進行測試。
設置Duration Assertion
設置maxIdle = maxActive = 100,maxWait = -1,1秒內啟150個線程
設置maxIdle = maxActive = 100,maxWait = -1,1秒內啟200個線程
設置maxIdle = maxActive = 100,maxWait = -1,1秒內啟250個線程
錯誤及分析
綜上,只要將連接池的最大等待時間不加限制,無論有多大的並發訪問都不會因為異常而使數據庫操作失敗,從而導致錯誤。主要的性能瓶頸來自於機器內存,過多的請求會使響應時間急劇惡化,嚴重降低用戶體驗。這也是根據壓力測試的結果能作出的軟件上的最大優化。
總結
建議所有后台的數據庫連接都采用連接池技術。上面的壓力測試暴露的問題全是出自數據庫連接上,其實像瘋狂評論這樣的並發操作,特別容易產生數據庫死鎖或者序列化並發更新異常等更難解決的錯誤。后面我還會再對數據庫的鎖機制做一些總結,分析一下為什么上面的壓力測試不會出現死鎖等更嚴重的錯誤,以及這些錯誤是怎么產生的。