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 开启,则进行加密协商。
加密协商过程如下:
加密协商阶段是在连接建立后进行的第一个阶段,为了保证后续的认证协商阶段中会话信息不会泄漏,需要先对连接进行通信加密。
server 处理流程:
认证协商
当加密协商阶段完成或跳过后,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 通信协议技术细节。
版权声明: 本文为 InfoQ 作者【HashData】的原创文章。
原文链接:【http://xie.infoq.cn/article/79bb33d4588740cffc766a831】。文章转载请联系作者。
评论