前端/後端協議

wongchaofan發表於2024-05-26

PostgreSQL使用基於訊息的協議在前端和後端(客戶端和伺服器)之間進行通訊。該協議透過TCP/IP和 Unix 域套接字支援。埠號 5432 已在 IANA 註冊為支援此協議的伺服器的常規 TCP 埠號,但實際上可以使用任何非特權埠號。

概述

該協議具有啟動和正常執行的獨立階段。在啟動階段,前端開啟與伺服器的連線並進行身份驗證以滿足伺服器的要求。

訊息傳送概述

所有通訊都是透過訊息流進行的。訊息的第一個位元組標識訊息型別,接下來的四個位元組指定訊息其餘部分的長度(此長度計數包括其本身,但不包括訊息型別位元組)。訊息的其餘內容由訊息型別決定。由於歷史原因,客戶端傳送的第一條訊息(啟動訊息)沒有初始訊息型別位元組。

擴充套件查詢概述

在擴充套件查詢協議中,SQL 命令的執行分為多個步驟。步驟之間保留的狀態由兩種型別的物件表示:準備好的語句入口。準備好的語句表示對文字查詢字串進行解析和語義分析的結果。準備好的語句本身並不準備執行,因為它可能缺少引數的特定值。入口表示準備執行或已部分執行的語句,其中已填充任何缺少的引數值。(對於SELECT語句,入口相當於開啟的遊標,但我們選擇使用不同的術語,因為遊標不處理非SELECT語句。)

格式和格式程式碼

特定資料型別的資料可能以多種不同格式傳輸。從PostgreSQL 7.4 開始,唯一支援的格式是“文字”“二進位制”,但協議為將來的擴充套件做了準備。任何值的所需格式都由格式程式碼指定。客戶端可以為每個傳輸的引數值和查詢結果的每一列指定格式程式碼。文字的格式程式碼為零,二進位制的格式程式碼為一,所有其他格式程式碼都保留以供將來定義。

值的文字表示是特定資料型別的輸入/輸出轉換函式生成並接受的任何字串。在傳輸的表示中,沒有尾隨空字元;如果前端想要將接收的值作為 C 字串處理,則必須向其新增一個。(順便說一下,文字格式不允許嵌入空值。)

整數的二進位制表示使用網路位元組順序(最高有效位元組優先)。對於其他資料型別,請查閱文件或原始碼以瞭解二進位制表示。請記住,複雜資料型別的二進位制表示可能會因伺服器版本的不同而發生變化;文字格式通常是更便攜的選擇。

訊息流

要開始一個會話,前端會開啟一個到伺服器的連線併傳送一個啟動訊息。此訊息包括使用者的名稱和使用者想要連線的資料庫的名稱;它還標識要使用的特定協議版本。(啟動訊息還可以包括執行時引數的其他設定,這是可選的。)然後伺服器使用此資訊及其配置檔案(例如pg_hba.conf)的內容來確定連線是否暫時可以接受,以及需要什麼額外的身份驗證(如果有)。

然後,伺服器傳送適當的身份驗證請求訊息,前端必須用適當的身份驗證響應訊息(例如密碼)回覆該訊息。對於除 GSSAPI 和 SSPI 之外的所有身份驗證方法,最多隻有一個請求和一個響應。在某些方法中,前端根本不需要響應,因此不會發生身份驗證請求。對於 GSSAPI 和 SSPI,可能需要多次交換資料包才能完成身份驗證。

身份驗證週期以伺服器拒絕連線嘗試(ErrorResponse)或傳送 AuthenticationOk 而結束。

簡單查詢

前端向後端傳送查詢訊息,從而啟動一個簡單的查詢週期。該訊息包括以文字字串表示的 SQL 命令(或多個命令)。然後,後端根據查詢命令字串的內容傳送一個或多個響應訊息,最後傳送 ReadyForQuery 響應訊息。ReadyForQuery 通知前端它可以安全地傳送新命令。(前端實際上不必在發出另一個命令之前等待 ReadyForQuery,但前端必須負責確定如果先前的命令失敗而已發出的後續命令成功會發生什麼。)

擴充套件查詢

擴充套件查詢協議將上述簡單查詢協議分解為多個步驟。準備步驟的結果可以多次重複使用以提高效率。此外,還提供其他功能,例如可以將資料值作為單獨的引數提供,而不必將其直接插入查詢字串中。

在擴充套件協議中,前端首先傳送一個 Parse 訊息,其中包含一個文字查詢字串、可選的有關引數佔位符資料型別的一些資訊以及目標準備好的語句物件的名稱(空字串選擇未命名的準備好的語句)。響應是 ParseComplete 或 ErrorResponse。引數資料型別可以由 OID 指定;如果沒有給出,解析器將嘗試以與對無型別文字字串常量相同的方式推斷資料型別。

函式呼叫

函式呼叫子協議允許客戶端請求直接呼叫資料庫的pg_proc系統目錄中存在的任何函式。客戶端必須具有該函式的執行許可權。

函式呼叫迴圈由前端向後端傳送 FunctionCall 訊息來啟動。然後後端根據函式呼叫的結果傳送一個或多個響應訊息,最後傳送 ReadyForQuery 響應訊息。ReadyForQuery 通知前端它可以安全地傳送新的查詢或函式呼叫。

複製操作

COPY命令允許高速批次傳輸資料到伺服器或從伺服器傳輸資料。複製輸入和複製輸出操作都會將連線切換到不同的子協議,直到操作完成。

當後端執行COPY FROM STDIN SQL 語句時,將啟動複製入模式(將資料傳輸到伺服器)。後端向前端傳送 CopyInResponse 訊息。然後前端應傳送零個或多個 CopyData 訊息,形成輸入資料流。(訊息邊界不需要與行邊界有任何關係,儘管這通常是一個合理的選擇。)前端可以透過傳送 CopyDone 訊息(允許成功終止)或 CopyFail 訊息(這將導致COPY SQL 語句因錯誤而失敗)來終止複製入模式。然後後端恢復到COPY啟動之前的命令處理模式,該模式將是簡單或擴充套件查詢協議。它接下來將傳送 CommandComplete(如果成功)或 ErrorResponse(如果不成功)。

如果在複製模式下後端檢測到錯誤(包括收到 CopyFail 訊息),後端將發出 ErrorResponse 訊息。如果COPY命令是透過擴充套件查詢訊息發出的,後端現在將丟棄前端訊息,直到收到 Sync 訊息,然後它將發出 ReadyForQuery 並返回正常處理。如果COPY命令是在簡單查詢訊息中發出的,則該訊息的其餘部分將被丟棄併發出 ReadyForQuery。在任一情況下,前端發出的任何後續 CopyData、CopyDone 或 CopyFail 訊息都將被丟棄。

非同步操作

有幾種情況下,後端會傳送前端命令流未明確提示的訊息。前端必須隨時準備好處理這些訊息,即使沒有參與查詢。至少,在開始讀取查詢響應之前應該檢查這些情況。

由於外部活動,可能會生成 NoticeResponse 訊息;例如,如果資料庫管理員命令“快速”關閉資料庫,後端將在關閉連線之前傳送一個 NoticeResponse 來表明這一事實。因此,即使連線名義上處於空閒狀態,前端也應始終準備好接受和顯示 NoticeResponse 訊息。

每當後端認為前端應該知道的任何引數的有效值發生變化時,就會生成 ParameterStatus 訊息。最常見的情況是響應前端執行的SET SQL 命令時發生這種情況,這種情況實際上是同步的 — 但也可能因為管理員更改了配置檔案然後向伺服器傳送了SIGHUP訊號而發生引數狀態更改。此外,如果回滾了SET命令,則會生成適當的 ParameterStatus 訊息來報告當前有效值。

取消正在進行的請求

在查詢處理過程中,前端可能會請求取消查詢。出於實現效率的原因,取消請求不會直接透過開啟的連線傳送到後端:我們不希望後端在查詢處理過程中不斷檢查來自前端的新輸入。取消請求應該相對較少,因此我們讓它們稍微麻煩一些,以避免在正常情況下受到懲罰。

要發出取消請求,前端會開啟與伺服器的新連線併傳送 CancelRequest 訊息,而不是通常透過新連線傳送的 StartupMessage 訊息。伺服器將處理此請求,然後關閉連線。出於安全原因,不會對取消請求訊息做出直接答覆。

除非 CancelRequest 訊息包含與在連線啟動期間傳遞給前端的金鑰資料(PID 和金鑰)相同,否則它將被忽略。如果請求與當前正在執行的後端的 PID 和金鑰匹配,則當前查詢的處理將被中止。(在現有實現中,這是透過向處理查詢的後端程序傳送特殊訊號來完成的。)

取消訊號可能會產生影響,也可能不會產生影響 — 例如,如果它在後端處理完查詢後到達,則不會產生影響。如果取消有效,則會導致當前命令提前終止並顯示錯誤訊息。

所有這些的結果是,出於安全和效率的原因,前端沒有直接的方法來判斷取消請求是否成功。它必須繼續等待後端響應查詢。發出取消只會提高當前查詢很快完成的可能性,並提高查詢失敗並顯示錯誤訊息而不是成功的可能性。

由於取消請求是透過與伺服器的新連線傳送的,而不是透過常規的前端/後端通訊鏈路傳送的,因此任何程序都可能發出取消請求,而不僅僅是要取消查詢的前端。這在構建多程序應用程式時可能會提供額外的靈活性。但它也帶來了安全風險,因為未經授權的人可能會嘗試取消查詢。透過要求在取消請求中提供動態生成的金鑰來解決安全風險。

終止

正常、優雅的終止過程是前端傳送終止訊息並立即關閉連線。收到此訊息後,後端關閉連線並終止。

在極少數情況下(例如管理員命令關閉資料庫),後端可能會在前端未發出任何請求的情況下斷開連線。在這種情況下,後端將在關閉連線之前嘗試傳送錯誤或通知訊息,說明斷開連線的原因。

其他終止場景由各種故障情況引起,例如一端或另一端的核心轉儲、通訊鏈路丟失、訊息邊界同步丟失等。如果前端或後端發現連線意外關閉,則應清理並終止。如果前端不想終止自身,可以選擇透過重新聯絡伺服器來啟動新的後端。如果收到無法識別的訊息型別,也建議關閉連線,因為這可能表示訊息邊界同步丟失。

無論是正常終止還是異常終止,任何開啟的事務都會回滾,而不是提交。但是,應該注意,如果前端在處理非SELECT查詢時斷開連線,後端可能會在注意到斷開連線之前完成查詢。如果查詢在任何事務塊(BEGIN ... COMMIT序列)之外,則其結果可能會在識別出斷開連線之前提交。

SSL會話加密

如果PostgreSQL內建了SSL支援,則可以使用SSL加密前端/後端通訊。這在攻擊者可能能夠捕獲會話流量的環境中提供了通訊安全性。有關使用SSL加密PostgreSQL會話的更多資訊,請參閱第 18.9 節

要啟動SSL加密連線,前端首先傳送 SSLRequest 訊息而不是StartupMessage。然後伺服器響應一個包含SN的位元組,分別表示它願意或不願意執行SSL。如果前端對響應不滿意,它可能會在此關閉連線。要在S之後繼續,請與伺服器執行SSL啟動握手(這裡未描述,是SSL規範的一部分)。如果成功,請繼續傳送常規的 StartupMessage。在這種情況下,StartupMessage 和所有後續資料都將是SSL加密的。要在N之後繼續,請傳送常規的 StartupMessage 並在不加密的情況下繼續。

前端還應準備好處理來自伺服器的對 SSLRequest 的 ErrorMessage 響應。這僅在伺服器PostgreSQL中新增SSL支援之前才會發生。(這樣的伺服器現在非常古老,可能不再存在。)在這種情況下,必須關閉連線,但前端可能會選擇開啟一個新連線並繼續進行而不請求SSL

當可以執行SSL加密時,伺服器預計只傳送單個S位元組,然後等待前端發起SSL握手。如果此時有其他位元組可供讀取,則可能意味著中間人正在試圖執行緩衝區填充攻擊 ( CVE-2021-23222 )。前端應編碼為在將套接字移交給其 SSL 庫之前從套接字讀取一個位元組,或者如果發現它們讀取了額外的位元組,則將其視為協議違規。

初始 SSLRequest 還可用於正在開啟的連線中以傳送 CancelRequest 訊息。

雖然協議本身不提供伺服器強制SSL加密的方法,但管理員可以將伺服器配置為拒絕未加密的會話作為身份驗證檢查的副產品。

流複製協議

要啟動流複製,前端會在啟動訊息中傳送複製引數。布林值true指示後端進入 walsender 模式,在該模式下可以發出一小組複製命令而不是 SQL 語句。在 walsender 模式下只能使用簡單查詢協議。啟用log_replication_commands後,複製命令將記錄在伺服器日誌中。傳遞database作為值指示 walsender 連線到dbname引數中指定的資料庫,這將允許該連線用於從該資料庫進行邏輯複製。

為了測試複製命令,您可以透過psql或任何其他使用libpq的工具使用包含複製選項的連線字串建立複製連線,例如:

psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"

但是,使用pg_receivexlog(用於物理複製)或pg_recvlogical(用於邏輯複製)通常更有用。

walsender 模式中接受的命令有:

IDENTIFY_SYSTEM

 請求伺服器確認自身身份。伺服器回覆一行結果集,包含四個欄位

TIMELINE_HISTORY tli

  請求伺服器傳送時間線tli的時間線歷史檔案。

CREATE_REPLICATION_SLOT slot_name { PHYSICAL [ RESERVE_WAL ] | LOGICAL output_plugin }

  建立物理或邏輯複製槽。Create a physical or logical replication slot.

  slot_name 插槽名稱 要建立的槽的名稱。必須是有效的複製槽名稱

  LOGICAL 輸出外掛 用於邏輯解碼的輸出外掛的名稱

  RESERVE_WAL 指定此物理複製槽立即保留WAL。否則,只有在流複製客戶端連線時才會保留WAL 。

START_REPLICATION [ SLOT slot_name ] [ PHYSICAL ] XXX/XXX [ TIMELINE tli ]

指示伺服器開始流式傳輸 WAL,從 WAL 位置XXX/XXX開始。如果指定了TIMELINE選項,則流式傳輸從時間線tli開始;否則,將選擇伺服器的當前時間線。伺服器可以回覆錯誤,例如,如果請求的 WAL 部分已被回收。成功時,伺服器會以 CopyBothResponse 訊息響應,然後開始將 WAL 流式傳輸到前端。

如果透過slot_name提供了一個槽的名稱,它將在複製過程中被更新,以便伺服器知道哪些 WAL 段以及hot_standby_feedback在哪些事務上,仍然是備用資料庫所需要的。

如果客戶端請求的時間線不是最新的,但屬於伺服器的歷史記錄,則伺服器將從請求的起始點開始流式傳輸該時間線上的所有 WAL,直到伺服器切換到另一條時間線為止。如果客戶端請求在舊時間線的末尾進行流式傳輸,則伺服器會立即以 CommandComplete 進行響應,而不會進入 COPY 模式。

在流式傳輸非最新時間線上的所有 WAL 之後,伺服器將透過退出 COPY 模式來結束流式傳輸。當客戶端透過退出 COPY 模式確認這一點時,伺服器將傳送一個包含一行兩列的結果集,指示此伺服器歷史記錄中的下一個時間線。第一列是下一個時間線的 ID(型別int8),第二列是發生切換的 WAL 位置(型別text)。通常,切換位置是流式傳輸的 WAL 的末尾,但在某些特殊情況下,伺服器可以從舊時間線傳送一些它在提升之前尚未重放的 WAL。最後,伺服器傳送 CommandComplete 訊息,並準備好接受新命令。

START_REPLICATION SLOT slot_name LOGICAL XXX/XXX [ ( option_name [ option_value ] [, ...] ) ]

指示伺服器開始流式傳輸 WAL 以進行邏輯複製,從 WAL 位置XXX/XXX開始。伺服器可以回覆錯誤,例如,如果請求的 WAL 部分已被回收。成功時,伺服器會回覆 CopyBothResponse 訊息,然後開始將 WAL 流式傳輸到前端。

CopyBothResponse 訊息中的訊息與START_REPLICATION ... PHYSICAL記錄的格式相同

與所選插槽關聯的輸出外掛用於處理流式傳輸的輸出。

DROP_REPLICATION_SLOT slot_name

刪除複製槽,釋放任何保留的伺服器端資源。如果該槽當前正被活動連線使用,則此命令失敗。

BASE_BACKUP [ LABEL 'label' ] [ PROGRESS ] [ FAST ] [ WAL ] [ NOWAIT ] [ MAX_RATE rate ] [ TABLESPACE_MAP ]

指示伺服器開始流式傳輸基礎備份。系統將在備份開始前自動進入備份模式,並在備份完成後退出。接受以下選項:

相關文章