Pgpool數據庫鏈接調優和參數解讀

原文地址: https://www.tony-yin.site/201...

pgpool

本文分享一次數據庫鏈接調優的經歷,並對pgpool一系列生命週期配置參數進行解讀。html

背景

測試發現數據庫讀寫有時候會短期卡頓,通過定位,發現數據庫VIP未發生漂移,服務端口正常,數據庫(pgpool)鏈接數已到達上限,因此判斷是數據庫鏈接數過多致使以後的短期訪問阻塞。python

Django數據庫鏈接處理

因爲創建數據庫鏈接最多的客戶端用的是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)還顯示有不少沒有關閉的鏈接呢?後端

那無非兩種場景:緩存

  1. 程序沒有走Response
  2. 程序走了Response,可是沒能關閉掉鏈接;

場景1

  • 正常的restful接口最終是返回Response,可是中間代碼出錯或者拋異常會致使最後沒能運行到Response
  • 項目中有不少程序並非對外暴露的接口,好比後端的一些定時任務,這類程序不會涉及到請求的開始和結束,若是這些任務中涉及DB的操做,Django是不會幫助程序關閉鏈接的。

場景2

再談正常走Response的程序,什麼緣由會致使鏈接沒能被正常關閉?restful

經過上面Django數據庫鏈接處理流程分析,能夠知道Django裏的數據庫鏈接是放在線程的local() 實例中,當本線程須要一個數據庫鏈接,判斷local中是否存在已有鏈接,有則複用;可是若是採用多線程的方式訪問數據庫,Django則會建立一條屬於當前線程的數據庫鏈接,即每一個線程都會建立屬於本身的數據庫鏈接,每一個線程也只能關閉當前線程的數據庫鏈接。多線程

因此若是一段程序中,採用了多線程的方式訪問DB,最後經過主線程返回Response,主線程是不會關閉其餘線程中的數據庫鏈接。併發

解決方案

客戶端

  1. 針對代碼異常沒有運行到Response的狀況,須要添加try-catch,在最終的finally加上返回Response的代碼;
  2. 針對非web接口,即最後不走Response的狀況,須要在程序最後額外添加關閉數據庫鏈接的代碼;
  3. 針對多線程使用數據庫的場景,解決方案就是除了主線程的每次工做線程完成一個任務後,就把它相關的數據庫鏈接關掉。
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 子進程狀態解讀

查看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掉並從新生成新的子進程。

Pgpool 生命週期參數解讀

child_life_time

該參數表示最後一次客戶端斷開鏈接後,子進程空閒持續的最大時間,避免長時間不退出致使內存泄露。客戶端斷開數據庫鏈接後,子進程切換成wait request狀態,若是child_life_time配置的時間範圍內,沒有客戶端與該子進程創建鏈接,該子進程便會被kill掉並從新生成新的子進程。0表示永遠不會退出子進程。

client_idle_limit

該參數表示最後一次客戶在鏈接中的數據庫操做後,該鏈接持續的最大時間。當一個客戶端在執行最後一條查詢後若是空閒到了client_idle_limit 秒數,這個客戶端的鏈接將被pgpool斷開。該參數能夠防止客戶端長時間的空閒鏈接佔用鏈接數,本文也是經過該參數進行數據庫優化。0表示永遠不會斷開鏈接。

child_max_connections

該參數表示pgpool容許的最大鏈接數,當pgpool創建的鏈接數到達child_max_connections配置的數目後,則會優先退出最先的子進程。該參數主要用於併發量大的場景,短期內觸發不到child_life_timeclient_idle_limit參數,0表示永遠不會退出子進程。

connection_life_time

該參數表示pgpool與數據庫後端創建鏈接的最大時間,主要用於緩存,提升數據庫性能。0表示永遠不會關閉鏈接。

總結

Django在大部分狀況下會幫助關閉數據庫鏈接,但部分場景,如無Response、程序拋異常、多線程等場景會致使數據庫鏈接沒法自動關閉。客戶端須要在以上場景下采起對應的解決方案,防止數據庫鏈接堆積。此外,數據庫端也須要配置客戶端空閒鏈接最大時間,保證在客戶端不關閉鏈接的狀況下可以關閉長時間空閒的數據庫鏈接。

Refer

  1. 經過CONN_MAX_AGE優化Django的數據庫鏈接
  2. 多線程Django程序耗盡數據庫鏈接的問題
  3. django數據庫鏈接
  4. A good PgPool II configuration
  5. pgpool配置文件詳解
  6. 關於PgPool客戶端阻塞
相關文章
相關標籤/搜索