本文分享自華為雲社群《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通訊運維也具備一定的參考意義。
點選關注,第一時間瞭解華為雲新鮮技術~