本文分享自華為雲社區《GaussDB(DWS) 集群通信系列一:pooler連接池》,作者:半島里有個小鐵盒。 1.前言 適用版本:【8.1.0(及以上)】 GaussDB(DWS) 為MPP型分散式資料庫,使用Share Nothing架構,數據分散存儲在各個DN節點,而CN不存儲數據,作為接收查 ...
本文分享自華為雲社區《GaussDB(DWS) 集群通信系列一:pooler連接池》,作者:半島里有個小鐵盒。
1.前言
適用版本:【8.1.0(及以上)】
GaussDB(DWS) 為MPP型分散式資料庫,使用Share Nothing架構,數據分散存儲在各個DN節點,而CN不存儲數據,作為接收查詢的入口,生成的計劃會儘量下推到DN並行執行以提升性能,此過程中會產生大量的建連操作,使得通信開銷變得很大。因此在大數據時代,集群規模越來越大,業務併發越來越高,資料庫集群各節點間的通信壓力也越來越大。GaussDB(DWS)集群通信技術,在大規模集群中可以承載高併發業務,能夠實現高性能分散式通信系統。
2.背景
GaussDB(DWS) 中客戶端執行查詢流程如上圖所示,其中具體的如下:
- 客戶端向CN的監聽埠發起連接;
- CN postmaster主線程accept連接,創建 postgres線程並將連接交給此線程處理;
- 客戶端下發query到CN;
- CN的postgres線程將查詢計划下發給其他 CN/DN,查詢結果沿原路徑返回到客戶端;
- 客戶端查詢結束,關閉連接;
- CN上對應的postgres線程銷毀退出;
CN與DN建連立流程,和客戶端與CN建連立流 程基本相同。因此為了減少CN與DN建立連接,以及DN進程中postgres線程創建、銷毀的開銷,CN端實現了Pooler連接池。
3.Pooler連接池
如上圖所示,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連接池具體的復用流程如下:
- session需要連接時,通過DB+USER為key找到正確的pooler連接池,優先從中取走現有連接,如果連接池中沒有連接的話,則新建連接;
- query結束後,CN的postgres線程並不會歸還連接,連接可以用於當前session的下一個查詢;
- 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通信運維也具備一定的參考意義。