前端/后端协议

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 ]

指示服务器开始流式传输基础备份。系统将在备份开始前自动进入备份模式,并在备份完成后退出。接受以下选项:

 

posted @ 2024-05-26 05:36  wongchaofan  阅读(31)  评论(0编辑  收藏  举报