鏈接數從異常到 300 到 5(RDS MySQL 的一個大坑•後記)

《記 RDS MySQL 的一個大坑》 中,我提到遇到 User juxxxxxxxxxx already has more than 'max_user_connections' active connections…… 這樣的錯誤,最終經過在循環中使用 Thread.Sleep,下降 CRUD 操做的頻率,讓鏈接數降低至不到原來的一半,從而解決了這個棘手的問題,有興趣的朋友能夠點擊連接回顧一下html

今天又看了一下添加 Thread.Sleep 後,程序運行時的 IOPS 和 鏈接數:mysql

iops-connections-3

運行結果:鏈接數:300,運行時間:68 分鐘,IOPS:7sql

昨天在博客園中發出上篇文章後,熱心的朋友(@沈贇@不知道風往哪兒吹)對此問題提出了寶貴的意見和想法,激發了我對此問題繼續深究的決定。數據庫

下午通過幾個小時的分析和測試,終於找到了該問題的真正緣由和更好的解決方法,在此作個補充。緩存

真正的緣由在於:使用 MySQL 官方提供的 MySql.Data 做爲驅動程序鏈接 MySQL 數據庫的時候,默認使用了鏈接池,才引起了這個問題。錯怪了阿里雲(上篇中提到懷疑阿里雲改了 MySQL 底層作了限制),在此對本身的不嚴謹表示誠懇的道歉😥😥😥,凡事要本身多思考多研究多求證,不可輕易懷疑權威的力量,切記切記!!!服務器

下面聊一聊該問題出現的真正緣由和更優的解決方法。socket

MySQL 鏈接池

根據官方介紹:MySQL Connector/NET 中(即 MySql.Data 中)鏈接池的工做的機制是,當客戶端配置 MySqlConnection 時,鏈接池經過保持一組與服務器的本地鏈接使其處於活動狀態,隨後,若是打開一個新的 MySqlConnection 對象,它將從鏈接池中建立鏈接,而不是從新建立一個新的本地鏈接。這樣即可以重用數據庫鏈接,避免了頻繁建立、釋放鏈接引發的大量性能開銷,這有助於縮短響應時間、統一管理、提升運行性能等等。ide

在軟件開發中,大多數狀況下,數據庫鏈接都有重用的可能,即使永不重用,鏈接池也有本身的回收機制在適當的時候釋放資源,這有點像帶有過時時間的緩存數據,也像 .NET 的 GC 回收機制。正由於在大多數狀況下,它能夠提升運行的性能,也有完善且可配置的回收機制。因此在沒有提供任何鏈接池選項的狀況下,MySQL Connector/NET 默認啓用鏈接池,也就是說,建立 MySqlConnection 時使用下面的鏈接字符串:性能

server=xxx;port=3306;userid=myuserid;password=pwd123;database=db125;charset=utf8;

等同於使用:測試

server=xxx;port=3306;userid=myuserid;password=pwd123;database=db125;charset=utf8;Pooling=true;

而我原來的代碼中剛好用的就是前者,也就是說默認啓用了鏈接池。

爲何使用鏈接池反而出問題了

上面說到鏈接池有那麼多的好處,爲何我用了鏈接池反倒出問題了呢?咱們來看一下

鏈接對池資源的利用狀況:

官方文檔:
Connector/NET runs a background job every three minutes and removes connections from pool that have been idle (unused) for more than three minutes. The pool cleanup frees resources on both client and server side. This is because on the client side every connection uses a socket, and on the server side every connection uses a socket and a thread.

譯文:
Connector/NET 每三分鐘運行一次後臺做業,從鏈接池中刪除閒置(未使用)超過三分鐘的鏈接。鏈接池清理會釋放客戶端和服務器端的資源。這是由於在客戶端,每一個鏈接使用一個套接字,而在服務器端,每一個鏈接都使用一個套接字和一個線程。

上一篇中有介紹過個人程序的基本狀況,這裏有必要再補充一下關鍵的使用場景:

咱們的 MySql 服務實例有不少臺,每臺實例上有不少個數據庫,只有其中一臺 MySql 服務實例出現了超出 max_user_connections 的異常,這臺實例最大的鏈接數限制在 600,可是這臺實例上的數據庫就有 700 多個。

聰明的朋友看到這裏,估計已經明白爲何使用了鏈接池會出現問題了。爲何呢?就由於上面提到的鏈接池每三分鐘運行一次清理操做唄。循環語句執行的速度是很快的,有的小庫瞬間就執行完了,可是在鏈接池中卻保持了一個鏈接,尚未到每隔三分鐘的資源回收時間(這也是我在上篇中添加了 Thread.Sleep 後鏈接數減小的緣由)。當這臺實例的 600 個鏈接被所有佔滿時,再鏈接同一實例上另外一個鏈接池中沒有緩存的數據庫時,就報了超出 max_user_connections 的異常。

解決方法

怎麼解決呢?最簡單的解決方法就是,判斷請求的是這臺 MySql 服務實例時,不使用鏈接池,這樣就會在調用 MySqlConnection 的 Close 方法時,當即釋放客戶端和服務端所佔用的資源。所以,在數據庫鏈接字符串中加上 Pooling=false,改爲下面這樣:

server=xxx;port=3306;userid=myuserid;password=pwd123;database=db125;charset=utf8;Pooling=false;

而後發佈到服務器上進行測試,查看一下程序運行時的 IOPS 和 鏈接數:

pooling-false-result

驚不驚喜,意不意外,嚯嚯嚯~~~🥰🥰🥰

運行結果:鏈接數只有 5 個,運行時間縮短到了 8 分鐘,IOPS 爲 36,與以前添加 Thread.Sleep 的測試結果相比,天壤之別呀……

最後,用一張圖來描述一下兩種解決方法的運行效果比較:

activity-diagram

結論

阿里雲 RDS MySQL 沒有問題,問題出在,在不恰當的場景使用了 MySQL 鏈接池,鏈接池雖好,但不可亂用喲,切記切記!

做者 : 技術譯民
出品 : 技術譯站

相關文章
相關標籤/搜索