GaussDB(DWS)叢集通訊:詳解pooler連線池

华为云开发者联盟發表於2024-03-11

本文分享自華為雲社群《GaussDB(DWS) 叢集通訊系列一:pooler連線池》,作者:半島裡有個小鐵盒。

1.前言

適用版本:【8.1.0(及以上)】

GaussDB(DWS) 為MPP型分散式資料庫,使用Share Nothing架構,資料分散儲存在各個DN節點,而CN不儲存資料,作為接收查詢的入口,生成的計劃會盡量下推到DN並行執行以提升效能,此過程中會產生大量的建連操作,使得通訊開銷變得很大。因此在大資料時代,叢集規模越來越大,業務併發越來越高,資料庫叢集各節點間的通訊壓力也越來越大。GaussDB(DWS)叢集通訊技術,在大規模叢集中可以承載高併發業務,能夠實現高效能分散式通訊系統。

2.背景

GaussDB(DWS) 中客戶端執行查詢流程如上圖所示,其中具體的如下:

  1. 客戶端向CN的監聽埠發起連線;
  2. CN postmaster主執行緒accept連線,建立 postgres執行緒並將連線交給此執行緒處理;
  3. 客戶端下發query到CN;
  4. CN的postgres執行緒將查詢計劃下發給其他 CN/DN,查詢結果沿原路徑返回到客戶端;
  5. 客戶端查詢結束,關閉連線;
  6. CN上對應的postgres執行緒銷燬退出;

CN與DN建連立流程,和客戶端與CN建連立流 程基本相同。因此為了減少CN與DN建立連線,以及DN程序中postgres執行緒建立、銷燬的開銷,CN端實現了Pooler連線池。

3.Pooler連線池

img

如上圖所示,CN的pooler連線池中會儲存與其他CN/DN的連線,每一個連線在對端會對應一個postgres工作執行緒。

postgres工作執行緒是帶狀態屬性的,如database,所以可以認為pooler連線池中的連線也是帶屬性的。不同屬性間的連線是不能複用的,如上圖所示,按不同屬性切分為pool A/B/C等連線池。每個連線池中會存有連線往不同節點的空閒連線的陣列,提供介面給外部使用或放入連線。

CN上的postgres工作執行緒在需要連線其他節點時,會建立一個本地agent,嘗試從pooler連線池取跟本執行緒相同屬性的空閒連線,pooler如果沒有空閒連線,就會新建一個連線。連線交給agent後,可以視為執行緒私有。線上程退出時,agent才會將連線還給pooler。

接下來,我們從資料結構上來看看Pooler連線池實現原理:

空閒連線池

DatabasePool

/* All pools for specified database */
typedef struct databasepool
{
    char    *database;
    char    *user_name;
    char    *pgoptions; /* Connection options */
    HTAB    *nodePools; /* Hashtable of PGXCNodePool, one entry for each node */
    MemoryContext mcxt;
    struct databasepool *next; /* Reference to next to organize linked list */
} DatabasePool;

DatabasePool *databasePools = NULL;

程序級別資料結構

DatabasePool用於儲存空閒連線,根據database,user_name,pgoptions三者來確定
如果透過三者查詢到了,那麼就取其中對應的nodePools,找不到則新加

nodePools中對應的資料結構為PGXCNodePool,用於儲存本節點與其他每個cn/dn的連線,具體的儲存如下

cn1[0,1,2,3,4,…]

dn1[0,1,2,3,4,…]

NodePool

typedef struct NodePool
{
    Oid nodeoid; /* Hash key (must be first!) */
    bool valid;
    ArrayLockFreeQueue pool;
} PGXCNodePool;

程序級別資料結構

連線到某一節點的空閒連線的集合,透過無鎖佇列(陣列)實現
從這裡拿到對應的連線,直接複用

正在使用連線池

AgentPool

typedef struct
{
    ArrayLockFreeQueue pool;
    AgentSlot* slots;
}AgentPool;

程序級別的資料結構

存放一個程序中的所有的連線

其中slots為一個陣列,表明一個槽位,與pool為一一對應的狀態
用於儲存所有執行緒持有的連線,即poolAgent資料結構

AgentSlot

typedef struct
{
    int index;
    volatile AgentStatus status;
    PoolAgent* agent;
}AgentSlot;

程序級別資料結構

存放程序中的某一個連線

index與AgentPool->pool相關聯

status狀態為,標識該連線是否正在使用/空閒/持有

agent中為某個執行緒的資訊(也就是每個session),都是在全域性陣列poolAgents中儲存的

PoolAgent

typedef struct
{
    /* Agent members */
    ThreadId        pid;
    DatabasePool*   pool;
    int             index;

    /* param members */ 
    char*           user_name;
    char*           pgoptions;
    char*           paramsStr;
    char*           localParams;   /* params temporarily saved before commit */
    char*           tempNamespace; /* temp namespace name of session */
    List*           paramsList;    /* save session params, for build paramsStr */
    int             paramsReady;    /* param is set, need rebuild paramsStr */

    /* Connection members */
    int             dnNum;
    int             cnNum;
    PoolSlot**      dnConn;
    PoolSlot**      cnConn;
    NodeConnDef*    cnDef;
    NodeConnDef*    dnDef;

    /* handles members */
    NodeHandle** dnHandles;
    NodeHandle** cnHandles;
    int dnUsed;
    int cnUsed;
} PoolAgent;

對於每起一個執行緒session(即連線),都會有一個PoolAgent與之對應,即從DatabasePool->NodePool->pool取出來的連線

  • cnDef、dnDef:初始化時從pgxc_node中拿到,即cn定義、埠、ip
  • (session級別)dnConn、cnConn:從databasePools->nodePools-> pool拿真正對應的連線
  • (query級別)dnHandles、cnHandles:從dnConn、cnConn裡邊dop出來使用,確保兩者是一一對應的狀態,當query結束時,只需要close handles就行,cnConn不需要close

Pooler連線池具體的複用流程如下:

  1. session需要連線時,透過DB+USER為key找到正確的pooler連線池,優先從中取走現有連線,如果連線池中沒有連線的話,則新建連線;
  2. query結束後,CN的postgres執行緒並不會歸還連線,連線可以用於當前session的下一個查詢;
  3. session結束後,CN的postgres執行緒會將連線還到對應的pooler,連線對應的DN上的postgres執行緒並不會退出,處於ReadCommand中,等待複用後CN新的postgres執行緒發起任務;

4.Pooler連線池相關的檢視

pg_pooler_status檢視

pg_pooler_status檢視記錄了pooler連線池中的所有連線資訊,每一行表示本CN發起的一個連線,對應對端程序的一個postgres執行緒

postgres=# select * from pg_pooler_status;
 database | user_name | tid |  node_oid  |  node_name   | in_use | node_port | fdsock |   remote_pid    | session_params
----------+-----------+-----+------------+--------------+--------+-----------+--------+-----------------+----------------
 postgres |   user1   |     | 2147483650 | datanode1    | f      |     37110 |     94 | 140259241618584 | none
 postgres |   user1   |     | 2147483650 | datanode1    | f      |     37110 |    101 | 140259241619432 | none
 postgres |   user1   |     | 2147483650 | datanode1    | f      |     37110 |     91 | 140259241618160 | none
 postgres |   user1   |     | 2147483650 | datanode1    | f      |     37110 |     95 | 140259241619008 | none
 postgres |   user1   |     | 2147483650 | datanode1    | f      |     37110 |     59 | 140259241562192 | none
 postgres |   user1   |     | 2147483650 | datanode1    | f      |     37110 |    106 | 140259241619856 | none
 postgres |   user1   |     | 2147483650 | datanode1    | f      |     37110 |    108 | 140259241620280 | none
 postgres |   user1   |     | 2147483650 | datanode1    | f      |     37110 |    117 | 140259241621128 | none
 postgres |   user1   |     | 2147483650 | datanode1    | f      |     37110 |    114 | 140259241620704 | none
  • in_use為‘t’表示這個連線正在某執行緒使用,為‘f’表示空閒連線等待複用
  • tid列為本CN的持有此連線的執行緒號
  • node_name列為對端程序號,remote_pid列為對端線執行緒號
  • 在query_id為0或CN/DN不一致時,透過pooler檢視查詢CN與DN連線關係

一般pooler連線池中的連線會非常多,可以按不同欄位group by檢視某個db pool池中的連線情況,或連往某個node的連線情況,如:

select database,user_name,node_name,in_use,count(*) from pg_pooler_status group by 1, 2, 3 ,4 order by 5 desc limit 50;

5.Pooler連線清理

清理Session持有的連線

  • cache_connection,是否使用pooler連線池快取連線,預設開
  • session_timeout,客戶端連線空閒超時後報錯退出歸還連線
  • enable_force_reuse_connections,事務結束後強制歸還連線
  • conn_recycle_timeout(8.2.1),CN空閒session超時後歸還連線

Pooler空閒連線池中的連線

  • pg_clean_free_conn檢視/函式,清理1/4的空閒連線池連線,CM定期呼叫
  • CLEAN CONNECTION語法,清理對應DB或user的所有空閒連線 clean connection to all for database postgres to USER user1;

6.總結

本文詳細介紹了Libcomm通訊庫及其原理,讓我們更好的理解GaussDB(DWS)叢集通訊中的具體邏輯,對於GaussDB通訊運維也具備一定的參考意義。

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章