原文地址: https://www.tony-yin.site/201...
本文分享一次數據庫鏈接調優的經歷,並對pgpool
一系列生命週期配置參數進行解讀。html
測試發現數據庫讀寫有時候會短期卡頓,通過定位,發現數據庫VIP
未發生漂移,服務端口正常,數據庫(pgpool
)鏈接數已到達上限,因此判斷是數據庫鏈接數過多致使以後的短期訪問阻塞。python
因爲創建數據庫鏈接最多的客戶端用的是Django
框架,因此先分析一下Django
對數據庫鏈接的處理流程。web
Django
程序接受到請求以後,在第一次訪問數據庫的時候會建立一個鏈接,在請求結束時,關閉鏈接。Django1.6
後的版本,提供了數據庫持久鏈接的配置,經過DATABASE
配置上添加CONN_MAX_AGE
來控制每一個鏈接的最大存活時間。參數的原理就是在每次建立完數據庫鏈接以後,把鏈接放到一個Theard.local
的實例中。在request
請求結束時會判斷是否超過CONN_MAX_AGE
設置這個有效期。每次進行數據庫請求的時候其實只是判斷local
中有沒有已存在的鏈接,有則複用。數據庫
class BaseDatabaseWrapper(object): def connect(self): """Connects to the database. Assumes that the connection is closed.""" # Reset parameters defining when to close the connection max_age = self.settings_dict['CONN_MAX_AGE'] self.close_at = None if max_age is None else time.time() + max_age self.connection = self.get_new_connection(conn_params) ...... ...... self.run_on_commit = [] def close_if_unusable_or_obsolete(self): """ Closes the current connection if unrecoverable errors have occurred, or if it outlived its maximum age. """ if self.connection is not None: # If the application didn't restore the original autocommit setting, # don't take chances, drop the connection. if self.get_autocommit() != self.settings_dict['AUTOCOMMIT']: self.close() return # If an exception other than DataError or IntegrityError occurred # since the last commit / rollback, check if the connection works. if self.errors_occurred: if self.is_usable(): self.errors_occurred = False else: self.close() return if self.close_at is not None and time.time() >= self.close_at: self.close() return
Django
是經過HttpResponse
來做爲請求結束的標準,關閉鏈接的代碼也是在HttpResponseBase
這個class
中。django
class HttpResponseBase(six.Iterator): def close(self): for closable in self._closable_objects: try: closable.close() except Exception: pass self.closed = True # 請求結束時觸發request_finished這個觸發器 signals.request_finished.send(sender=self._handler_class)
經過上面能夠大體瞭解Django
對於數據庫鏈接的處理流程,客戶端中並無設置長鏈接時間,因此理論上來講每一個請求都會在結束時關閉數據庫鏈接,那麼爲何數據庫端(pgpool
)還顯示有不少沒有關閉的鏈接呢?後端
那無非兩種場景:緩存
Response
;Response
,可是沒能關閉掉鏈接;restful
接口最終是返回Response
,可是中間代碼出錯或者拋異常會致使最後沒能運行到Response
。DB
的操做,Django
是不會幫助程序關閉鏈接的。再談正常走Response
的程序,什麼緣由會致使鏈接沒能被正常關閉?restful
經過上面Django
數據庫鏈接處理流程分析,能夠知道Django
裏的數據庫鏈接是放在線程的local()
實例中,當本線程須要一個數據庫鏈接,判斷local
中是否存在已有鏈接,有則複用;可是若是採用多線程的方式訪問數據庫,Django
則會建立一條屬於當前線程的數據庫鏈接,即每一個線程都會建立屬於本身的數據庫鏈接,每一個線程也只能關閉當前線程的數據庫鏈接。多線程
因此若是一段程序中,採用了多線程的方式訪問DB
,最後經過主線程返回Response
,主線程是不會關閉其餘線程中的數據庫鏈接。併發
Response
的狀況,須要添加try-catch
,在最終的finally
加上返回Response
的代碼;web
接口,即最後不走Response
的狀況,須要在程序最後額外添加關閉數據庫鏈接的代碼;from django.db import connections # 每個線程都有專屬的connections,把本線程名下的全部鏈接關閉。 connections.close_all()
Pgpool
配置文件中配置客戶端鏈接空閒最大時間爲300
秒:
client_idle_limit = 300 # Client is disconnected after being idle for that many seconds # (even inside an explicit transactions!) # 0 means no disconnection
該參數表示當一個客戶端在執行最後一條查詢後若是空閒到了client_idle_limit
秒數, 到這個客戶端的鏈接將被斷開。這裏配置爲300
秒,防止客戶端存在長時間的空閒鏈接佔用鏈接數。
查看pgpool
鏈接數和狀態:
[root@host1 ~]# systemctl status pgpool ● pgpool.service - Pgpool-II Loaded: loaded (/usr/lib/systemd/system/pgpool.service; enabled; vendor preset: disabled) Active: active (running) since Wed 2019-10-23 16:14:42 CST; 5 days ago Main PID: 3195664 (pgpool) Memory: 163.8M CGroup: /system.slice/pgpool.service ├─ 2500232 pgpool: test_user test_db 192.168.1.1(39436) idle ├─ 2561154 pgpool: wait for connection request ├─ 794851 pgpool: test_user test_db 192.168.225.1(17831) idle in transaction ......
鏈接通常分爲如下幾種狀態:
客戶端位於192.168.1.1
上,經過test_user
用戶鏈接的數據庫test_db
,而且該鏈接當前處於idle
狀態。
2500232 pgpool: test_user test_db 192.168.1.1(39436) idle
客戶端位於192.168.1.1
上,經過test_user
用戶鏈接的數據庫test_db
,而且該鏈接當前處於transaction
狀態。
794851 pgpool: test_user test_db 192.168.1.1(17831) idle in transaction
當客戶端斷開鏈接後,原子進程便會切換成wait
狀態,等待下一次客戶端鏈接,若是child_life_time
配置的時間範圍內,沒有客戶端向這個子進程創建鏈接,該子進程便會kill
掉並從新生成新的子進程。
該參數表示最後一次客戶端斷開鏈接後,子進程空閒持續的最大時間,避免長時間不退出致使內存泄露。客戶端斷開數據庫鏈接後,子進程切換成wait request
狀態,若是child_life_time
配置的時間範圍內,沒有客戶端與該子進程創建鏈接,該子進程便會被kill
掉並從新生成新的子進程。0
表示永遠不會退出子進程。
該參數表示最後一次客戶在鏈接中的數據庫操做後,該鏈接持續的最大時間。當一個客戶端在執行最後一條查詢後若是空閒到了client_idle_limit
秒數,這個客戶端的鏈接將被pgpool
斷開。該參數能夠防止客戶端長時間的空閒鏈接佔用鏈接數,本文也是經過該參數進行數據庫優化。0
表示永遠不會斷開鏈接。
該參數表示pgpool
容許的最大鏈接數,當pgpool
創建的鏈接數到達child_max_connections
配置的數目後,則會優先退出最先的子進程。該參數主要用於併發量大的場景,短期內觸發不到child_life_time
和client_idle_limit
參數,0
表示永遠不會退出子進程。
該參數表示pgpool
與數據庫後端創建鏈接的最大時間,主要用於緩存,提升數據庫性能。0
表示永遠不會關閉鏈接。
Django
在大部分狀況下會幫助關閉數據庫鏈接,但部分場景,如無Response
、程序拋異常、多線程等場景會致使數據庫鏈接沒法自動關閉。客戶端須要在以上場景下采起對應的解決方案,防止數據庫鏈接堆積。此外,數據庫端也須要配置客戶端空閒鏈接最大時間,保證在客戶端不關閉鏈接的狀況下可以關閉長時間空閒的數據庫鏈接。