
數據庫是所有應用中最不可或缺的一個(gè)組件,所有有狀態(tài)的數據都需要依托數據庫提供的格式,規范進(jìn)行存儲、讀取、改寫(xiě)等。那么既然是一個(gè)炙手可熱的角色,如何讓系統能夠快速有效的和數據庫進(jìn)行溝通,就是一個(gè)比較有意思的話(huà)題了。如果把這個(gè)內容全部展開(kāi),可能會(huì )涉及到數據庫的冗余性,擴展性設計等一系列的問(wèn)題,這次我們就先把目標鎖定在如何快速管理連接這個(gè)點(diǎn)上。
如果把各個(gè)應用比作是需要看病就醫的病患,那么醫院可以說(shuō)是一個(gè)大的數據庫。那如何看病就醫,井然有序,就需要一個(gè)規章法度,比如病人是否有此地就醫的資格,是否已經(jīng)掛號,有否既往病史,還需要什么化驗等。那么同樣,應用如果要得到數據庫的增刪改查的權限也一定要有一個(gè)流程。簡(jiǎn)要來(lái)說(shuō),流程就是通過(guò)一些網(wǎng)絡(luò )協(xié)議,握手連接到數據庫,提供一個(gè)有效的連接字符串,隨后數據庫會(huì )對比此應用是否有資格來(lái)訪(fǎng)問(wèn)這個(gè)數據庫,最后就能愉快的增刪改查了。

接下來(lái)我們將以Azure上面的MySQL PaaS為例,給大家講解一些簡(jiǎn)單的數據庫連接的使用場(chǎng)景,以及最佳實(shí)踐。
01、第一種場(chǎng)景:
單個(gè)進(jìn)程連接,非頻繁訪(fǎng)問(wèn)
我們假設一個(gè)病人只是有點(diǎn)小的頭疼腦熱,那么第一次就診結束,配了點(diǎn)藥,然后可能需要第二天去復診。這個(gè)時(shí)候也許他還得要走一次掛號,查醫保信息,配藥看醫生等一系列流程。所以并不能直接敲開(kāi)醫生診室的門(mén),說(shuō)我想直接看病配藥。那么同樣的道理,我們到數據庫的連接中,會(huì )有一個(gè)超時(shí)(timeout)的概念,也就是說(shuō)我第一次增刪改查完,也許我等待了60秒,再一次查詢(xún),發(fā)現數據庫說(shuō)對不起,請重新連接“掛號”因為時(shí)間太長(cháng)了,我不認識你了,或者說(shuō)你的狀態(tài)我不知道了,請重新握手協(xié)議,連接字符串,身份驗證走一遍。那么我們看圖示,里面哪些是我們需要一個(gè)數據庫連接要提供的信息呢。
1.數據庫的連接字符串,這個(gè)可以在A(yíng)zure上得到你的程序需要的連接字符串。需要注意的是Azure上的數據庫用戶(hù)名比較特殊,在@后面會(huì )帶上服務(wù)器名,這個(gè)和本地自建的不太一樣。

2.隨后我們用Python的程序新建一個(gè)表格,并且插入一些數據,模擬一個(gè)單進(jìn)程的非頻繁訪(fǎng)問(wèn)。

3.那么現在我們加入一個(gè)延時(shí)60秒,看看會(huì )發(fā)生什么?


4.為什么之前連接執行語(yǔ)句都好好的,怎們就加了60秒的等待就歇菜了呢?別著(zhù)急,我們在A(yíng)zure MySQL的參數調整里看一下wait_timeout的值,你會(huì )發(fā)現,咦,怎么有一個(gè)值是30秒呢?原來(lái)超過(guò)了30秒這個(gè)時(shí)間,原來(lái)的連接就超時(shí)了,我們程序里寫(xiě)得是休眠60秒。就需要重新連接,重新掛號了。
那么你會(huì )發(fā)現一旦程序有了空閑時(shí)間段,數據庫就會(huì )不繼續等待了,直接用timeout這個(gè)參數把你的連接給掐斷了,需要你從頭再來(lái)。這個(gè)很好理解,比如你在昨天看了醫生,醫生也不可能一直記得你,也不可能一直等你,只有到了你再去的時(shí)候,重新掛號然后再和白衣天使一起討論病情。那么你有可能會(huì )問(wèn),那么如果是比較復雜的病情,一會(huì )要拍個(gè)片,一會(huì )要驗血,其中都需要等待時(shí)間,那么拿了報告,醫生又讓我掛號,驗證身份,豈不是很煩?那有沒(méi)有一個(gè)比較人性化的制度可以讓這些頻繁的檢查能夠管理起來(lái),那么我們就來(lái)看第二個(gè)場(chǎng)景。
02、第二個(gè)場(chǎng)景:
單個(gè)進(jìn)程,需要頻繁交互的數據庫
之前我們討論那種需要等化驗結果,多次詢(xún)問(wèn)醫生的場(chǎng)景。那么映射到數據庫,就是說(shuō)單個(gè)進(jìn)程,需要頻繁的訪(fǎng)問(wèn)數據庫。這個(gè)其實(shí)有兩個(gè)思路可以解決,第一種是把timeout的參數調的大一點(diǎn)其實(shí)就好了。這個(gè)沒(méi)錯,但是也存在一定安全和資源浪費,比如這個(gè)長(cháng)連接會(huì )不會(huì )被非法占用,寶貴的數據庫連接數是不是會(huì )被長(cháng)時(shí)間占用,這些都是問(wèn)題。那么第二個(gè)思路是,由一個(gè)中間人來(lái)協(xié)調所有的連接,同時(shí)讓這些連接不會(huì )超時(shí),同時(shí)也可以免去某一個(gè)進(jìn)程需要頻繁交互,頻繁驗證握手等,給程序帶了很多等待時(shí)間。那么這種中間人的技術(shù)就叫做連接池,它的作用就相當于一共有10個(gè)醫生坐班,連接池就是一個(gè)醫導(可以是人,也可以是醫院的信息化系統),事先都和這些醫生很熟,建立了信任的連接,那么病患只要和這個(gè)醫導建立連接,掛號驗證身份,就能做完各種化驗,繼續找醫生看病,省卻了很多流程上的時(shí)間。那么再數據庫的技術(shù)上有很多這些連接池的技術(shù),比如DBUtils,PySQLpool等再python上使用比較頻繁的模塊。這次我們就以DBUtils來(lái)給大家講解一下。
1.先看一下大概的概念,對比一下醫導和連接池的類(lèi)似概念。如圖上顯示的連接池在第一次驗證之后,把所有的連接都放在它的大池子里,應用端不用每次都走一遍繁瑣的流程,如果要在一個(gè)交易或者業(yè)務(wù)邏輯里頻繁增刪改查幾十次,那么完全可以省下來(lái)之前這么多步驟,應用速度大大提升。

2.那么接下來(lái), 用DBUtils來(lái)建立一個(gè)9個(gè)連接的連接池,數據庫timeout參數還是30s不變,然后我們同樣休眠60s,發(fā)現繼續使用conn1還是可以連接,說(shuō)明數據庫連接池并沒(méi)有釋放掉原來(lái)的連接,而是緩存在了連接池之中。連接池一直保持這和數據庫的硬連接,這個(gè)“硬”連接是真正走過(guò)所有的握手協(xié)議身份驗證的,而應用和連接池的連接則是軟連接是放在一個(gè)緩存池中里的。當然你還能定義有多少硬連接是在連接池建立完之后一直保留著(zhù)的,這樣會(huì )對突發(fā)的軟連接有一定的幫助。


3.一旦設置了連接池,就比較完美解決了單進(jìn)程的頻繁訪(fǎng)問(wèn)問(wèn)題了。那么你可能會(huì )問(wèn),如果是多進(jìn)程怎么處理呢?連接池可以完美解決這個(gè)問(wèn)題嗎?接下來(lái)我們來(lái)看看下一個(gè)場(chǎng)景。
03、第三種場(chǎng)景:
多進(jìn)程訪(fǎng)問(wèn)數據庫
一般在顯示問(wèn)題里,資源都是比較緊俏的。資源不緊俏說(shuō)的好聽(tīng)是冗余度高,防范黑天鵝,說(shuō)的不好聽(tīng)就是浪費,但是這個(gè)平衡點(diǎn)確實(shí)很難掌握。那么我們現在討論的場(chǎng)景就是資源相當緊俏的數據庫連接,要被很多進(jìn)程頻繁同時(shí)訪(fǎng)問(wèn)。比如現在是流行病高發(fā)時(shí)期,醫院看呼吸道疾病的醫生可能只有10個(gè)人,但是同時(shí)要看病的患者可能成百上千,那么怎么樣才能讓這些心急火燎的病患能夠看病治療呢?那么醫導這個(gè)系統可以記住所有來(lái)訪(fǎng)的病人,知道哪些可以先去做血液檢查,哪些在這個(gè)空隙,可以找醫生看片子,那么10個(gè)醫生的效率得到了最有話(huà)。只需要醫導系統與這10個(gè)醫生連接,所有的病人信息都緩存在醫導這里;而不需要病人直接和醫生建立連接,那么效率就是等10個(gè)病人全部檢查完畢,才能看接下來(lái)的病人,效率會(huì )十分的緩慢。
1.現在我們先看一下, 如果不做任何共享連接的方式。也就是說(shuō)到了連接上限就掐斷,所有應用被勸離。

2.先看一下目前Azure MySQL的設置的連接數上限是10(當然這個(gè)不是最大值,最大連接數隨著(zhù)你選擇的實(shí)例大小而變化)

3.初始化連接池的時(shí)候,不選共享連接, 參數上Blocking放成False。啟動(dòng)20個(gè)并發(fā)的線(xiàn)程

4.沒(méi)有共享等待連接機制的情況下,超出連接數的線(xiàn)程就報錯了。

5.那么現在我們把等待緩存機制換上,那么并發(fā)來(lái)的線(xiàn)程就能夠等待之前的線(xiàn)程釋放資源后,繼續使用有限的連接數。這樣就達到了一個(gè)緩存池的作用。同樣的道理,如果我們醫生醫院不夠大的時(shí)候,就可以建立很多簡(jiǎn)易或者野外的醫院,隔離病房等,讓看病的病患先得到妥善安置,然后在按需就醫。


6.現在我們把緩存機制打開(kāi),把Blocking參數置成True。發(fā)現即使有20個(gè)進(jìn)程同時(shí)并發(fā),也會(huì )通過(guò)連接池緩存技術(shù),妥善把所有進(jìn)程完成,達到共享10個(gè)連接數的可能性。

場(chǎng)景總結
最后我們來(lái)總結一下前面三個(gè)場(chǎng)景。第一個(gè)場(chǎng)景是單進(jìn)程非頻繁訪(fǎng)問(wèn),所以每一次訪(fǎng)問(wèn)都需要做一次數據庫連接,會(huì )消耗一定資源和數據庫連接數量。那么第二個(gè)場(chǎng)景是單進(jìn)程,但是頻繁訪(fǎng)問(wèn),引入了數據庫連接池的概念,可以事先維護好一定數量的連接池,不用每次訪(fǎng)問(wèn)都去建立一次連接,那么頻繁訪(fǎng)問(wèn)的速度得到了保證。第三種場(chǎng)景的問(wèn)題是如果是多進(jìn)程的訪(fǎng)問(wèn)情況,連接數量會(huì )不夠,這個(gè)時(shí)候引入了連接池緩存等待機制,就很好的解決了高并發(fā)多進(jìn)程的問(wèn)題。所以在什么場(chǎng)景下選擇什么樣的技術(shù)方案是至關(guān)重要的。
要解決數據庫連接或者看病難這種緊缺資源的問(wèn)題,需要用到一些中間調劑人或者系統,那么這個(gè)系統是用公平的輪詢(xún)機制,隨機數機制,還是帶有權重的機制,就涉及到資源分配的大問(wèn)題了。設計系統的時(shí)候一定要把這些因素考慮進(jìn)去,那么愿天下系統安穩,一切苦厄消散。
最后,提供本文技術(shù)參考指南及MySQL 連接池代碼供各位查閱:
DBUtils 用戶(hù)指南:
https://cito.github.io/DBUtils/UsersGuide.html#id2
代碼傳送門(mén):
https://github.com/jurejoy/MySQLconnectionpool