写点什么

PostgreSQL 技术内幕(九)libpq 通信协议

作者:HashData
  • 2023-07-13
    北京
  • 本文字数:3964 字

    阅读完需:约 13 分钟

PostgreSQL技术内幕(九)libpq通信协议

libpq 通信协议是基于 TCP/IP 协议的一套消息通信协议,它允许 psql、JDBC、PgAdmin 等客户端程序传递查询给 PostgreSQL 后端服务器,并接收返回查询的结果。


在这次的直播中,我们为大家介绍了 libpq 通信协议的实现原理和执行机制,以下内容根据直播文字整理而成。


libpq 通信协议简介

通信协议,是指通信双方按控制信息交换规则的标准、约定的集合,即网络上的传输规则。两个实体要成功地通信,必须“说同样的语言”。


libpq 协议在 TCP/IP 模型网络分层中,属于应用层协议的一种。在进行 libpq 协议通信之前,要先完成连接的建立。libpq 协议描述了交互所需的认证握手过程、数据请求应答过程与错误处理过程。


从交互层次来看,libpq 通信协议包括建立连接、数据查询、终止链接阶三个阶段, 接下来我们将围绕这三个阶段不同的状态和模式展开阐述。


建立连接阶段(starup 阶段)

用户使用 libpq 驱动创建与数据库的连接,并发送授权信息,如果一切正常,服务端会反馈状态信息,连接成功创建。

客户端 connect 和服务端 accept 过程示意图

如上图所示,libpq 建立连接阶段大致分为三个步骤:连接建立阶段、加密协商阶段、认证协商阶段,其中连接建立阶段是 tpc 协议的内容,对应代码实现就是 connect 和 accept 函数。加密协商和认证协商是 libpq 通信建立连接阶段的重要流程。

建立连接

用户使用 libpq 创建连接的流程,从代码阶段主要分为三步:

1.创建 PGconn 类型连接对象 conn;

2.连接数据库(通过接口 PQconnectdb /PQconnectdbParams/ PQsetdbLogin);

3.判断连接对象 conn 的状态,若为 CONNECTION_OK ,则连接成功。

用户通过 libpq 与 server 建立连接的过程,主要涉及到两种状态类型:

轮询状态类型 PostgresPollingStatusType 和连接状态类型 ConnStatusType。

  • 轮询状态:

轮询主要是用于等待 conn 对象创建 socket 、写入连接参数、等待 server 返回结果、等待连接认证等。此过程中 conn 对象已经创建,但未完成与 server 的服务连接过程。


连接不是瞬间完成的,需要有一系列的处理过程,在此过程中的等待流程由轮询控制,是一个短暂的过程。PGRES_POLLING_FAILED 和 PGRES_POLLING_OK 为轮询终止的状态条件,PGRES_POLLING_READING,PGRES_POLLING_WRITING 为需要持续询问当前 conn 的状态条件。

在空的 conn 对象建立后,轮询进入初始状态 PGRES_POLLING_WRITING,调用 PQconnectPoll 询问到是否需要等待 conn 建立完成。若仍需等待,则继续等待和状态轮询直到连接建立完成,否则错误返回。


  • 连接状态:

PQconnectPoll 函数会为客户端连接推进连接状态机,为 CONNECTION_MADE 状态进行处理,这里的主要工作就是启动认证请求。


在连接过程的任何时候,都可以通过调用 PQstatus 来检查连接的状态。如果此时调用返回 CONNECTION_BAD,则连接过程失败;如果调用返回 CONNECTION_OK,则连接准备就绪。这两种状态都可以从 PQconnectPoll 的返回值中检测到。


在异步连接过程期间(并且仅在期间)也可能出现其他状态,比如 writing ,代表客户端要给服务端发送认证、协商信息;reading 代表等待服务端返回信息包。


加密协商

加密协商阶段是在连接建立后进行的第一个阶段,为了保证后续的认证协商阶段中会话信息不会泄漏,需要先对连接进行通信加密。


加密协商阶段是可选的,只有开启 GSSAPI 认证或者 SSL 认证才会执行。在这一阶段,客户端调用 PQconnectPoll 函数中,ConnStatusType 连接状态处于 CONNECTION_NEEDED,然后调用 connect 函数去连接服务端,连接状态会转变为 CONNECTION_STARTED。


这个时候,服务端 postmaster 会执行如下调用:StreamConnection 函数会使用服务器端口创建与客户端的新连接,将 port->sock 设置为新连接的 FD。连接新建成功后,postmaster 会调用 BackendStartup 为该客户端连接创建 postgres 后端服务子进程。


下一步,PQconnectPoll 函数会尝试为此连接推进状态机,为 CONNECTION_MADE 状态进行处理,启动认证请求,并构建启动包。


如果编译宏参数 ENABLE_GSS 或者 USE_SSL 开启,则进行加密协商。

加密协商过程如下:

client->server(协议版本信息)
复制代码

加密协商阶段是在连接建立后进行的第一个阶段,为了保证后续的认证协商阶段中会话信息不会泄漏,需要先对连接进行通信加密。

server 处理流程:

ServerLoop->BackendStartup->BackendInitialize->ProcessStartupPacket(处理加密)
复制代码

认证协商

当加密协商阶段完成或跳过后,libpq 协议将开始进行认证阶段。认证阶段由 Startup message 消息开始,消息格式以消息长度开始,随后紧跟协议版本号,然后是键值对形式的连接信息,如用户名、数据库以及其他 GUC 参数和值。


前端发出 Startup message 消息后,后端会进行认证应答,认证应答信息的类型为“R ”,其内容大致分为 3 种情况:完成认证(相当于不需要认证,此时用户不需要验证密码)、提供认证方式与所需的参数、认证错误。


前端通过认证应答信息提供的认证方式(如果有的话)向后端发送认证请求,认证请求消息中包含后端所需要的认证参数,例如密码或密码的 MD5 值等。


认证错误消息 ErrorResponse 会导致后端直接关闭连接,停止认证协商。


认证请求的类型为“ P ”,其内容需要根据上下文进行推断,例如之前认证应答消息中的认证方式为 MD5,则认证请求消息中的内容就为密码的 MD5 值。


前端向后端发送认证请求后,后端会再次根据认证请求中的内容进行认证应答,直到认证完成或认证错误。所以,认证阶段完成的标志是:后端发送的内容为认证完成的认证应答消息或者发送了 ErrorResponse 的认证错误消息。


当认证完成时,后端会在认证应答信息后发送一些其他协议,来通知前端一些必要的参数,其中有:

  • 类型为“ S ”的 ParameterStatus :是一个 Key-value 对,进行参数设置;

  • 类型为“ K ”的 BackendKeyData:描述了一个取消请求的 Key,主要用户在开始阶段时 Cancel request 需要的 Key 值,用于在一个新建会话中中断另一个会话中阻塞操作;

  • 类型为“ Z ”的 ReadyForQuery:代表后端已经准备好开始一个新的数据请求。


  • 至此,一个建立连接的过程已经完全准备完成。建立连接的状态图如下:

连接建立流程逻辑示意图

数据查询阶段(normal 阶段)

数据查询阶段,客户端和服务端所有通信都通过消息流进行。消息的第一个字节标识消息类型,随后四个字节标识消息内容的长度(该长度包括这四个字节本身),具体的消息内容由消息类型决定。


服务端支持消息类型为 PostgresMain 函数;客户端支持处理消息类型为 pqParseInput3 函数。数据查询阶段常用的通信模式有三种,分别为 Simple query、Extended query 和 Copy data。


Simple query 模式:客户端通过 Query 消息发送一个文本命令给服务端,服务端处理请求,回复查询结果。查询结果通常包括两部分内容:结构和数据。结构通过 RowDescription 消息传递,包括列名、类型 OID 和长度等;数据通过 DataRow 消息传递,每个 DataRow 消息中包含一行数据。


每个命令的结果发送完成之后,服务端会发送一条 CommandComplete 消息,表示当前命令执行完成。客户端的一条查询请求可能包含多条 SQL 命令,每个 SQL 命令执行完都会回复一条 CommandComplete 消息,查询请求执行结束后会回复一条 ReadyForQuery 消息,告知客户端可以发送新的请求。消息流如下:

Simple query 消息流示意图

Extended query 模式:Extended query 协议将以上 Simple query 的处理流程分为若干步骤,每一步都由单独的服务端消息进行确认。Extended query 协议通常包括 5 个步骤,分别是 Parse、Bind、Describe、Execute 和 Sync,这里不再展开讲述。


Extended query 协议可以使用服务端的 prepared-statement 功能,即先发送一条参数化 SQL,服务端收到 SQL(Statement)之后对其进行解析、重写并保存,这里保存的 Statement 也就是 Prepared-statement,可以被复用;执行 SQL 时,直接获取事先保存的 Prepared-statement 生成计划并执行,避免对同类型 SQL 重复解析和重写;随后,服务端会在适当的条件下缓存计划,以备后续复用。


PGQUERY_EXTENDED 查询协议将一个 SQL 的执行过程拆分成三个层次,相邻的两个层次间抽象出 statement 和 portal 对象,每个层次允许单独重复调用,并且在当前连接的生命周期内,也允许再次调用,使整个 SQL 的执行过程具有了可重复利用性,对中间结果的保存减少了重复调用,节省了执行开销,也提高了执行速度。Extended query 完整消息流如下图所示:

Extended query 消息流示意图


Copy data 模式:为高效地导入/导出数据,libpq 支持 Copy 命令,Copy 操作会将当前连接切换至一种截然不同的消息通信方式。


Copy data 对应三种模式:copy-in 导入数据,对应命令 COPY FROM STDIN;copy-out 导出数据,对应命令 COPY TO STDOUT;copy-both 用于 walsender,在主备间批量传输数据。


以 copy-in 为例,服务端收到 COPY 命令后,进入 COPY 模式,并回复 CopyInResponse。随后客户端通过 Copydata 消息传输数据,CopyComplete 消息标识数据传输完成,服务端收到该消息后,发送 CommandComplete 和 ReadyForQuery 消息,消息流如下图所示:

Copy data 消息流示意图

终止阶段

这一阶段流程相对简单,客户端请求结束后,可以主动发送消息断开连接。服务端接收到客户端的终止消息后,直接退出进程。


总结

通过 libpq 与 PostgreSQL 建立连接是一个比较复杂的过程,主要通过 libpq 所在的 client 端进行驱动:发起请求,等待响应。


在建立连接轮询状态机、建立连接流程状态机和设置环境变量状态机中,有些状态会存在多次转换以完成连接建立的过程。经过连接建立、加密协商、认证协商三个阶段之后,一个连接到 PostgreSQL 的 PGconn 连接对象就准备完成,应用程序可以通过该对象进行后续各种业务的执行,向 Server 发起请求,并解析返回结果。


本次分享为大家介绍了如何使用 libpq 建立与 PostgreSQL Server 的连接,并使用连接发送业务请求。对 libpq 协议感兴趣的同学可关注 HashData 公众号,了解更多 libpq 通信协议技术细节。

发布于: 刚刚阅读数: 3
用户头像

HashData

关注

还未添加个人签名 2021-03-10 加入

云原生企业级数据仓库

评论

发布
暂无评论
PostgreSQL技术内幕(九)libpq通信协议_HashData_InfoQ写作社区