性能优化之数据库和数据源连接池配置
什么?!數據庫連接拿不到?
今天在公司對系統進行壓測,由于我的sit和dev和uat環境都是用的用的是一個數據庫服務器,我讓用戶在的sit進行壓測,分別是單線程測試,并發測試,但是用戶一開始測,我的三個環境就都掛掉了。 用戶一下子全部找上門來,我就跑去看容器是不是cpu內存或者io被耗盡了,結果一看40%!還差的遠呢!那就究竟是什么導致我的環境都訪問不了呢?我打開Navicat去看看數據庫,結果發現連接數據庫居然耗時特別久,我就明白了原來是數據庫連接拿不到。那么應該的因素有哪些呢?一個是數據庫的最大連接數;一個是配置文件中數據源連接池的配置。
1. 查看當前數據庫mysql最大連接數量和最大允許連接數量
?通常,mysql的最大連接數默認是100, 最大可以達到16384
show variables like 'max_connections';(查可以看當前的最大連接數) set global max_connections=1000;(設置最大連接數為1000,可以再次查看是否設置成功,這個只是暫時的,如果mysql服務重啟,就會失效,真正要改還是要在my.ini文件中修改)在修改最大連接數的時候會有這樣一個疑問—這個值是不是越大越好,或者設置為多大才合適?這個參數的大小要綜合很多因素來考慮,比如使用的平臺所支持的線程庫數量(windows只能支持到2048)、服務器的配置(特別是內存大小)、每個連接占用資源(內存和負載)的多少、系統需要的響應時間等。可以在global或session范圍內修改這個參數。連接數的增加會帶來很多連鎖反應,需要在實際中避免由此引發的負面影響。
首先看一下MySQL的狀態:
show status;
Open tables:560,即當前數據庫打開表的數量是560個,注意這個560并不是實際的560個表,因為MySQL是多線程的系統,幾個不同的并發連接可能打開同一個表,這就需要為不同的連接session分配獨立的內存空間來存儲這些信息以避免沖突。因此連接數的增加會導致MySQL需要的文件描述符數目的增加。另外對于MyISAM表,還會建立一個共享的索引文件描述符。
在MySQL數據庫層面,有幾個系統參數決定了可同時打開的表的數量和要使用的文件描述符,那就是table_open_cache、max_tmp_tables和open_files_limit
table_open_cache:2000,這就是說所有的MySQL線程一共能同時打開2000個表,我們可以搜集系統的打開表的數量的歷史記錄和這個參數來對比,決定是否要增加這個參數的大小。查看當前的打開表的數目(Open tables)可用上邊提到過的status命令,另外可以直接查詢這個系統變量的值:
?
Open_tables就是當前打開表的數目,通過flush tables命令可以關閉當前打開的表。 這個值如果過大,并且如果沒有經常的執行flush tables命令,可以考慮增加table_open_cache參數的大小。
接下來看max_tmp_tables:
?show variables like 'max_tmp%';
max_tmp_tables:32即單個客戶端連接能打開的臨時表數目。
查看當前已打開的臨時表的信息:
show global status like '%tmp%table%';
根據這兩個值可以判斷臨時表的創建位置,一般選取BLOB和TEXT列、Group by 和 Distinct語句的數據量超過512 bytes,或者union的時候select某列的數據超過512 bytes的時候,就直接在磁盤上創建臨時表了,另外內存中的臨時表變大的時候,也可能被MySQL自動轉移到磁盤上(由tmp_table_size和max_heap_table_size參數決定)。
增加table_open_cache或max_tmp_tables 參數的大小后,從操作系統的角度看,mysqld進程需要使用的文件描述符的個數就要相應的增加,這個是由open_files_limit參數控制的。
show variables like 'open_files%';
但是這個參數是OS限制的,所以我們設定的值并不一定總是生效。如果OS限制MySQL不能修改這個值,那么置為0。如果是專用的MySQL服務器上,這個值一般要設置的盡量大,就是設為沒有報Too many open files錯誤的最大值,這樣就能一勞永逸了。當操作系統無法分配足夠的文件描述符的時候,mysqld進程會在錯誤日志里記錄警告信息。
相應的,有兩個狀態變量記錄了當前和歷史的文件打開信息:
?
MySQL為每個連接分配線程來處理,可以通過threads_connected參數查看當前分配的線程數量:
show status like '%thread%';
?
比較threads_connected參數和前面提到的max_connections參數,也可以作為目前的系統負載的參照,決定是否需要修改連接數。
查看每個線程的詳細信息:mysql>show processlist;對影響系統運行的線程:kill connection|query threadid的命令殺死。
Druid數據源配置
Java程序很大一部分要操作數據庫,操作數據庫需要不斷建立連接釋放連接,為了高性能操作數據庫,數據庫連接池橫空出世。阿里開源在 github 上面的數據庫連接池是其中十分優秀的,國內大量的公式都在使用。
這里主要講高并發下的數據源連接池配置,druid的其它優秀的公共包括監控、安全機制等都不作拓展。
配置參數:
和其它連接池一樣DRUID的DataSource類為:com.alibaba.druid.pool.DruidDataSource,基本配置參數如下:
| 配置 | 缺省值 | 說明 |
| name | ? | 配置這個屬性的意義在于,如果存在多個數據源,監控的時候可以通過名字來區分開來。? 如果沒有配置,將會生成一個名字,格式是:"DataSource-" + System.identityHashCode(this) |
| jdbcUrl | ? | 連接數據庫的url,不同數據庫不一樣。例如:? mysql : jdbc:mysql://10.20.153.104:3306/druid2? oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto |
| username | ? | 連接數據庫的用戶名 |
| password | ? | 連接數據庫的密碼。如果你不希望密碼直接寫在配置文件中,可以使用ConfigFilter。詳細看這里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter |
| driverClassName | 根據url自動識別 | 這一項可配可不配,如果不配置druid會根據url自動識別dbType,然后選擇相應的driverClassName(建議配置下) |
| initialSize | 0 | 初始化時建立物理連接的個數。初始化發生在顯示調用init方法,或者第一次getConnection時 |
| maxActive | 8 | 最大連接池數量(不是越大越好) |
| maxIdle | 8 | 已經不再使用,配置了也沒效果 |
| minIdle | ? | 最小連接池數量 |
| maxWait | ? | 獲取連接時最大等待時間,單位毫秒。配置了maxWait之后,缺省啟用公平鎖,并發效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。 |
| poolPreparedStatements | false | 是否緩存preparedStatement,也就是PSCache。PSCache對支持游標的數據庫性能提升巨大,比如說oracle。在mysql下建議關閉。 |
| maxOpenPreparedStatements | -1 | 要啟用PSCache,必須配置大于0,當大于0時,poolPreparedStatements自動觸發修改為true。在Druid中,不會存在Oracle下PSCache占用內存過多的問題,可以把這個數值配置大一些,比如說100 |
| validationQuery | ? | 用來檢測連接是否有效的sql,要求是一個查詢語句。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會其作用。 |
| testOnBorrow | true | 申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。 |
| testOnReturn | false | 歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能 |
| testWhileIdle | false | 建議配置為true,不影響性能,并且保證安全性。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。 |
| timeBetweenEvictionRunsMillis | ? | 有兩個含義:? 1) Destroy線程會檢測連接的間隔時間2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明 |
| numTestsPerEvictionRun | ? | 不再使用,一個DruidDataSource只支持一個EvictionRun |
| minEvictableIdleTimeMillis | ? | ? |
| connectionInitSqls | ? | 物理連接初始化的時候執行的sql |
| exceptionSorter | 根據dbType自動識別 | 當數據庫拋出一些不可恢復的異常時,拋棄連接 |
| filters | ? | 屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有:? 監控統計用的filter:stat日志用的filter:log4j防御sql注入的filter:wall |
| proxyFilters | ? | 類型是List<com.alibaba.druid.filter.Filter>,如果同時配置了filters和proxyFilters,是組合關系,并非替換關系 |
高并發的配置
數據庫連接池的設置分析——測試數據
該測試數據是Oracle 性能小組測試&發布的,視頻內容在https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
連接池設置數量越小、總耗時越少?
聯想到為啥Nginx內部僅僅使用了4個線程,其性能就大大超越了100個進程的Apache HTTPD呢?好像更加堅定了這一觀點。?
這取決于你的資源:要知道,即使是單核 CPU 的計算機也能“同時”運行著數百個線程。但我們其實都知道,這只不過是操作系統快速切換時間片,跟我們玩的一個小把戲罷了。一核 CPU同一時刻只能執行一個線程,然后操作系統切換上下文,CPU 核心快速調度,執行另一個線程的代碼,不停反復,給我們造成了所有進程同時運行假象。其實,在一核 CPU 的機器上,順序執行A和B永遠比通過時間分片切換“同時”執行A和B要快,其中原因,學過操作系統這門課程的童鞋應該很清楚。一旦線程的數量超過了 CPU 核心的數量,再增加線程數系統就只會更慢,而不是更快,因為這里涉及到上下文切換耗費的額外的性能。
時間片的概念是什么?
時間片即CPU分配給各個程序的時間,每個線程被分配一個時間段, 稱作它的時間片,即該進程允許運行的時間,使各個程序從表面上看是同時進行的。
如果在時間片結束時進程還在運行,則CPU將被剝奪并分配給另一個進程。如果進程在時間片結束前阻塞或結束,則CPU當即進行切換。而不會造成CPU資源浪費。
在宏觀上:我們可以同時打開多個應用程序,每個程序并行不悖,同時運行。
但在微觀上:由于只有一個CPU,一次只能處理程序運行的一部分,如何處理公平,一種方法就是引入時間片,每個程序輪流執行。
結論:當線程數大于CPU的核心數,需要通過算法及時間分片切換該哪個線程獲得CPU的使用權,這個是需要耗時的。因為這里涉及到上下文切換耗費的額外的性能。
如果計算機是4核,機器值運行四個線程,處理速度相當快。但這只是理想狀態。
限制因素
實際上,限制線程發揮的,不止有CPU,還有磁盤IO和網絡IO。
下面分別來看CPU 磁盤IO 網絡IO:
只考慮CPU
結論1:如果只考慮CPU,不考慮磁盤I/O和網絡I/O,在一個8核的服務器上,數據庫連接數/線程數設置為8能夠提供最優的性能,如果再增加連接數,反而會因為上下文切換導致性能下降。(這是理想情況)
數據庫連接數/線程數= CPU的核心數
耗時因素:尋址的耗時+旋轉耗時;
當你的線程處理的是IO密集型業務時,便可以讓 線程/連接數 設置的比CPU核心數大一些,這樣就能夠在同樣的時間內,完成更多的工作,提升吞吐量。因為磁盤尋址的時候,CPU是空閑的,可以讓CPU做點其他的。
問題:設置成多大合適呢?
取決于磁盤:SSD固態硬盤、普通機械硬盤
SSD固態硬盤,它不需要尋址,也不需要旋轉碟片!是不是可以設置更大些?
結論正好相反! 無需尋址和沒有旋回耗時的確意味著更少的阻塞,所以更少的線程( 更接近于CPU核心數)會發揮出更高的性能。只有當阻塞密集時,更多的線程數才能發揮出更好的性能。
考慮網絡IO
網絡是我們應用程序最難把控了,因為網絡擁塞、超時 由不得我們控制;通常網絡瓶頸業務我們系統優化放在最后的。
如圖,隨著客戶端連接數的增加,每秒傳輸速度影響還是很大的。
該圖是 PostgreSQL 的基準性能測試數據,從圖中我們可以看到,TPS 在連接數達到 50 時開始變緩。回過頭來想下,在上面 Oracle 的性能測試視頻中,測試人員們將連接數從 2048 降到了 96,實際上 96 還是太高了,除非你的服務器 CPU 核心數有 16 或 32。
數據庫連接池計算公式
下面公式由PostgreSQL 提供,不過底層原理是不變的,它適用于市面上絕大部分數據庫產品。還有,你應該模擬預期的訪問量,并通過下面的公式先設置一個偏合理的值,然后在實際的測試中,通過微調,來尋找最合適的連接數大小。
連接數=((CPU核心數*2)+有效磁盤數)
注意:核心數不應包含超線程(hyper thread),即使打開了超線程也是如此,如果熱點數據全被緩存了,那么有效磁盤數實際是0,隨著緩存命中率的下降,有效磁盤數也逐漸趨近于實際的磁盤數。另外需要注意,這一公式作用于SSD的效果如何,尚未明了
舉個栗子
按照這個公式,如果說你的服務器CPU是4核i7的,連接池大小應該為((4*2)+1)=9。
取個整,我們就設置為10吧。你這個行不行啊? 10 也太小了吧!
測試結果表明:這個設置能輕松支撐3000用戶以6000 TPS的速率并發執行簡單查詢的場景。你還可以將連接池大小超過10,那時,你會看到響應時長開始增加,TPS開始下降。
結論:你需要的是一個小連接池,和一個等待連接的線程隊列!
補充:連接池中的連接數量大小應該設置成:數據庫能夠有效同時進行的查詢任務數(通常情況下來說不會高于2*CPU核心數)。
回顧數據庫連接池的實現原理
JDBC訪問數據庫的問題總結
(1)數據庫連接資源很稀缺,每次創建和關閉都非常耗時。
(2)每次數據庫連接使用完后要及時關閉回收,如果程序運行中間出現異常,導致連接沒有及時關閉,將有可能導致內存泄漏,服務器崩潰。
(3)這種方式,不能控制被創建的連接對象的數量,也就是說,你可以創建無數個連接對象。沒有誰去約束限制,對象太多,導致內存泄漏,服務器崩潰。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 由網絡資料收集整理而成
?
總結
以上是生活随笔為你收集整理的性能优化之数据库和数据源连接池配置的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据分析入门(第一课)
- 下一篇: 从建表到SQL优化