psql-srv 深入解析:用 Rust 模拟 PostgreSQL 服务器,让现有应用零改动接入你的缓存层

发布时间:2026/10/13 12:19:07
psql-srv 深入解析:用 Rust 模拟 PostgreSQL 服务器,让现有应用零改动接入你的缓存层
缓存后端数据库【免费下载链接】readysetReadyset is a MySQL and Postgres wire-compatible caching layer that sits in front of existing databases to speed up queries and horizontally scale read throughput. Under the hood, ReadySet caches the results of cached select statements and incrementally updates these results over time as the underlying data changes.项目地址https://gitcode.com/gh_mirrors/re/readyset点击查看免费下载psql-srv是 ReadySet 仓库psql-srv/README.md中专门用于“模拟 PostgreSQL 服务器”的 crate。它的价值在于当你开发新的数据库或缓存层时可以不用修改任何现有应用程序就让它们通过标准的 PostgreSQL wire protocol 直接连上你的系统。本文以该 README 为骨架结合仓库源码讲清 psql-srv 的定位、核心PsqlBackend接口、协议状态机、认证与数据类型实现以及它在 ReadySet 产品中的实际用法帮助你在自己的项目中快速上手这套“协议适配层”的完整开发套路。为什么需要模拟一个 PostgreSQL 服务器在开发新型数据库或缓存层时最直接也最真实的测试方式是用现成的应用去连接你的系统。但传统做法往往要求开发者修改应用的连接串、驱动甚至业务代码工作量很大且容易引入偏差。psql-srv 解决的正是这个问题它充当一个“PostgreSQL 服务器”在 wire protocol 层面与libpq/ 各种 PostgreSQL 客户端驱动完全兼容然后把查询与执行逻辑委托给你自己实现的 backend。应用只需要把连接地址指向你的服务其余一切都像连接真实 PostgreSQL 一样工作。现有应用psql / JDBC / libpq ... │ 标准 PostgreSQL wire protocol ▼ psql-srv协议解码/编码 状态机 │ 调用你实现的 PsqlBackend 方法 ▼ 你的查询处理逻辑缓存层、新数据库等ReadySet 正是这样使用 psql-srv 的它作为一个 MySQL/PostgreSQL wire 兼容的缓存层架设在既有数据库之前readyset/src/psql.rs 中直接调用psql_srv::run_backend(...)来处理每一个 PostgreSQL 客户端连接。快速上手两步接入README 给出了最精简的使用路径一共两步为你的后端实现PsqlBackendtraitcrate 的主接口调用run_backend把 backend 实例和一个连接字节流交给它。之后每当客户端发送QUERY、PARSE、DESCRIBE、BIND或EXECUTE消息时psql-srv 会调用你 backend 上的对应方法并把你的响应编码回客户端。README 中提到serve_one.rs示例作为极简实现参考在当前仓库中最贴近的“最小实现范例”是 psql-srv/src/protocol.rs 测试模块里的Backend一个记录了所有入参、能返回硬编码结果的哑实现以及本文后面给出的简化示例。入口函数run_backend的完整签名psql-srv/src/lib.rs如下pub async fn run_backendB: PsqlBackend( backend: B, channel: tokio::net::TcpStream, enable_statement_logging: bool, tls_acceptor: OptionArcTlsAcceptor, tls_mode: TlsMode, )参数说明参数含义backend实现了PsqlBackend的对象承载实际的查询/执行逻辑channel连接到 PostgreSQL frontend客户端的 TCP 字节流frontend 关闭连接时run_backend返回enable_statement_logging是否把客户端发来的语句记录到日志对应 runner 中info!(target: client_statement, ...)tls_acceptor可选执行 TLS 握手并产出TlsStream为None时强制TlsMode::Disabledtls_mode允许的客户端连接方式Disabled/Optional/Required核心接口PsqlBackend traitPsqlBackend是 psql-srv 与你的业务逻辑之间的唯一契约完整定义见 psql-srv/src/lib.rs。实现它需要提供pub trait PsqlBackend { type Resultset: StreamItem ResultPsqlSrvRow, Error Unpin; fn version(self) - String; // 启动时随 ParameterStatus 发给客户端的 server_version async fn set_auth_info(mut self, user: str, password: OptionRedactedString) - Result(), Error; // 认证成功后建立会话上游连接 fn on_application_name(mut self, _application_name: str) {} // 可选钩子客户端提供 application_name 时调用一次 async fn on_init(mut self, database: str) - ResultCredentialsNeeded, Error; // 初始化 backend返回认证需求 fn credentials_for_user(self, user: str) - OptionCredentials; // 查用户凭据 async fn on_query(mut self, query: str) - ResultQueryResponseSelf::Resultset, Error; // 简单查询协议 async fn on_prepare(mut self, query: str, parameter_data_types: [Type], statement_type: PreparedStatementType) - ResultPrepareResponse, Error; // 扩展协议PREPARE async fn on_execute(mut self, statement_id: u32, params: [PsqlValue], result_transfer_formats: [TransferFormat]) - ResultQueryResponseSelf::Resultset, Error; // 扩展协议EXECUTE async fn on_close(mut self, statement_id: DeallocateId) - Result(), Error; // 释放预备语句 fn in_transaction(self) - bool; // 决定 ReadyForQuery 的状态位 async fn load_extended_types(mut self) - ResultHashMapOid, i16, Error; // 从上游加载自定义类型 Oid-长度 }几个关键设计点type Resultset是一个异步流Stream这意味着结果集可以懒加载、逐行产出天然适合流式返回大结果集而不必全量物化。CredentialsNeeded有三个取值None无需认证直接进入 Ready 状态、Cleartext明文密码认证、ScramSha256SASL/SCRAM 认证对应 psql-srv/src/lib.rs 的定义。Credentials只有两种Any该用户接受任意凭据和CleartextPassword(String)明文密码比对见 psql-srv/src/lib.rs。注意 SCRAM 认证场景下on_init返回ScramSha256后协议层会用credentials_for_user拿到的明文密码计算 SCRAM 挑战。最小实现示例参考 psql-srv/src/protocol.rs 的测试 Backend一个最简实现大致长这样节选核心方法impl PsqlBackend for MyBackend { type Resultset stream::Iterstd::vec::IntoIterResultPsqlSrvRow, Error; fn version(self) - String { 14.5 ReadySet.to_string() } async fn on_init(mut self, database: str) - ResultCredentialsNeeded, Error { Ok(CredentialsNeeded::None) // 无需认证 } fn credentials_for_user(self, _user: str) - OptionCredentials { None } async fn on_query(mut self, query: str) - ResultQueryResponseSelf::Resultset, Error { Ok(QueryResponse::Select { schema: Arc::new(vec![ Column::Column { name: col1.into(), table_oid: None, attnum: None, col_type: Type::INT4, type_modifier: ATTTYPMOD_NONE, }, ]), resultset: stream::iter(vec![ Ok(vec![PsqlValue::Int(88)].into()), ]), }) } // ... 其余方法按需实现 }请求-响应模型FrontendMessage 与 BackendMessagepsql-srv 将 PostgreSQL 协议消息建模为两个枚举前端消息 FrontendMessage客户端 → 服务器定义于 psql-srv/src/message/frontend.rs包括Authenticate、Bind、Close、Describe、Execute、Parse、Query、SSLRequest、StartupMessage、SaslResponse、Sync、Flush、Terminate。消息类型字节与协议文档一致见 psql-srv/src/codec/decoder.rsQQuery、PParse、BBind、DDescribe、EExecute、CClose、SSync、HFlush、XTerminate、pPasswordMessageSSL 请求码为80877103。一个值得注意的性能细节requires_flush()psql-srv/src/message/frontend.rs规定扩展查询协议中的Parse/Bind/Describe/Close/Execute不需要立即 flush只有Sync需要这样一整条扩展协议交互链可以缓冲后一次性写出减少系统调用。后端消息 BackendMessage服务器 → 客户端定义于 psql-srv/src/message/backend.rs覆盖AuthenticationOk、AuthenticationSasl*、BindComplete、CloseComplete、CommandComplete、DataRow、EmptyQueryResponse、ErrorResponse、NoData、ParameterDescription、ParameterStatus、ParseComplete、ReadyForQuery、RowDescription、SSLResponse以及若干PassThrough*变体直接透传tokio-postgres的行/描述消息。其中ReadyForQuery的状态字节定义了三种会话状态psql-srv/src/message/backend.rsIIdle空闲不在事务块中TTransaction在事务块中EFailed transaction失败的事务块E状态下客户端发来的查询会被拒绝直到Sync结束该块。该状态由backend.in_transaction()决定psql-srv/src/protocol.rs。响应类型 Response协议层处理完消息后产出Responsepsql-srv/src/response.rsEmpty、Message单条、Messages多条、Stream含可选 header 的RowDescription、结果集流、可选 trailer。Response::write会把结果流逐行写成DataRow消息并为单结果集自动补发CommandCompleteSELECT n标签见 psql-srv/src/response.rs。运行时架构Runner 与 Channelrun_backend的底层是Runnerpsql-srv/src/runner.rs它把三件事组合在一起backend你的查询处理逻辑channelChannelC封装了tokio_util::codec::FramedC, Codec初始缓冲 4096 字节、backpressure 边界 64KBpsql-srv/src/channel.rs负责读写、TLS 与协议编解码protocol维护协议状态的状态机。Runner::main_loop反复从 channel 读取FrontendMessage交给protocol.on_request生成Response并写回出错时调用protocol.on_error生成ErrorResponse。TLS 的处理方式很特别当客户端先发SSLRequest时主循环返回RestartWithTlsRunner 用TlsAcceptor完成握手后重建 Channel 并重启主循环如果客户端在未配置tls_acceptor的情况下要求 TLS则会返回内部错误警告psql-srv/src/runner.rs。协议状态机从建连到执行Protocol维护一个明确的状态机psql-srv/src/protocol.rs其合法流转如下StartingUp → SslHandshake → StartingUp 客户端先请求 SSL StartingUp → Ready on_init 返回 None StartingUp → AuthenticatingCleartext → Ready StartingUp → AuthenticatingSasl → AuthenticatingSasl → Ready Ready → Extended 进入扩展查询协议 Extended → Ready / Extended → Error → Ready 错误后等待 Sync 复位各阶段处理的核心逻辑都集中在Protocol::on_requestpsql-srv/src/protocol.rsStartupMessage校验database参数必须存在调用backend.on_init(database)。按返回的认证需求进入 Cleartext 或 SASL 流程无认证需求则直接发送get_ready_message——包含AuthenticationOk、一组ParameterStatusclient_encodingUTF8、DateStyleISO、TimeZoneUTC、server_versionbackend.version()等以及ReadyForQuerypsql-srv/src/protocol.rs。Query简单查询协议直接调backend.on_query(query)返回的Select会被包装成RowDescription 结果流 ReadyForQuerySimpleQuery变体则逐条透传tokio-postgres的行与命令完成消息支持单条消息内多个 SQL 语句。Parse/Bind/Describe/Execute扩展查询协议Parse调backend.on_prepare把返回的PreparedStatementData含参数 schema、行 schema、statement id存入protocol.prepared_statements并同步给 Channel 的 Codec解码后续 Bind 中的参数需要类型信息回复ParseCompleteBind按prepared_statement_name找到预备语句把参数值与result_transfer_formats存入portals生成 portal回复BindComplete。格式码format code的展开规则空列表 → 全部Text单值 → 应用到所有列多值 → 必须与列数一致否则报IncorrectFormatCountpsql-srv/src/protocol.rsDescribe按Portal/PreparedStatement分别返回RowDescription对 portal或ParameterDescriptionRowDescription/NoData对预备语句。当row_schema为空时返回NoDataExecute调backend.on_execute(statement_id, params, result_transfer_formats)Select结果以流式返回写命令则以CommandComplete返回。Sync/Flush/TerminateSync把状态复位到Ready并回复ReadyForQueryTerminate直接返回空响应结束会话。错误恢复State::Error下丢弃除Sync外的所有消息直到Sync复位——这正是 PostgreSQL 扩展协议“出错后必须 Sync”的语义psql-srv/src/protocol.rs。认证实现Cleartext 与 SCRAM-SHA-256psql-srv 支持两种密码认证Cleartexton_init返回Cleartext后协议层向客户端发AuthenticationCleartextPassword收到密码后调用backend.credentials_for_user比对Any直接放行CleartextPassword精确比较成功后调用set_auth_info并进入 Readypsql-srv/src/protocol.rs。SCRAM-SHA-256完整实现了 RFC5802。支持SCRAM-SHA-256与SCRAM-SHA-256-PLUS两种机制名迭代次数 4096、nonce 24 字节、salt 12 字节psql-srv/src/scram.rs。实现还包含通道绑定channel binding仅支持tls-server-end-point类型当 TLS 开启而客户端声明“支持但未使用”通道绑定时按 RFC 判断为降级攻击并拒绝认证psql-srv/src/protocol.rs。ReadySet 的默认 PostgreSQL 认证方法即scram-sha-256见 readyset/src/psql.rs 中POSTGRES_AUTHENTICATION_METHOD环境变量默认值scram-sha-256。数据模型PsqlValue、Column 与 TransferFormatPsqlValuepsql-srv/src/value.rs是可在 PostgreSQL 客户端与服务器间传输的值枚举覆盖Null、Bool、各种整数/浮点、Text/VarChar/BpChar、Numeric(Decimal)、时间日期族Timestamp/TimestampTz/Date/Time、ByteArray、MacAddress、Inet/Cidr、Uuid、Json/Jsonb、Bit/VarBit、Point/Box/Path、Array、匿名复合类型Row以及PassThrough不认识的类型原样二进制透传。它实现了postgres_types::FromSql任何不支持的类型都会被包进PassThrough而非报错。Columnpsql-srv/src/lib.rs描述列列名、表 OID可空、属性号 attnum可空、类型Type、type_modifier无修饰时为ATTTYPMOD_NONE -1。TransferFormatpsql-srv/src/message/mod.rs只有Text(0) 与Binary(1) 两种。类型长度表为了向客户端报告RowDescription中的data_type_sizepsql-srv 维护了从Type到 typlen 的完整映射如INT44、FLOAT88、UUID16、NUMERIC变长 -1 等见 psql-srv/src/protocol.rs遇到未知/自定义类型时会调用backend.load_extended_types()从pg_catalog.pg_type拉取 Oid → typlen 映射并缓存。错误处理Error 到 SQLState 的映射psql-srv 的错误枚举定义在 psql-srv/src/error.rs并通过FromError for BackendMessage转换成带SqlState的ErrorResponse错误SQLStateAuthenticationFailure/NoUserSpecifiedINVALID_PASSWORDMissingPortal/MissingPreparedStatementUNDEFINED_PSTATEMENTParseErrorINVALID_PSTATEMENT_DEFINITIONTableNotFoundUNDEFINED_TABLEUnimplemented/Unsupported/UnsupportedTypeFEATURE_NOT_SUPPORTEDUnexpectedMessage/ScramPROTOCOL_VIOLATIONInternalError/UnknownINTERNAL_ERRORInvalidAuthorizationSpecificationFatal INVALID_AUTHORIZATION_SPECIFICATIONConnectionClosedFatal ADMIN_SHUTDOWN源自上游数据库的tokio_postgres::Error若携带 DB 错误则其 severity、code、detail、hint、position 等字段会被完整搬进ErrorResponsepsql-srv/src/error.rs。在 ReadySet 中的实际用法psql-srv 不是“示例玩具”它就是 ReadySet PostgreSQL 网关的协议底座。readyset/src/psql.rs 展示了完整的接入方式impl ConnectionHandler for PsqlHandler { type UpstreamDatabase PostgreSqlUpstream; type Handler PostgreSqlQueryHandler; async fn process_connection( mut self, stream: net::TcpStream, backend: readyset_adapter::BackendPostgreSqlUpstream, PostgreSqlQueryHandler, ) { psql_srv::run_backend( readyset_psql::Backend::new(backend) .with_authentication_method(self.authentication_method), stream, self.enable_statement_logging, self.tls_acceptor.clone(), self.tls_mode, ) .await; } }这里readyset_psql::Backend包装了readyset_adapter::Backend并实现PsqlBackend把 psql-srv 协议层与 ReadySet 的查询路由、缓存、上游转发逻辑连接起来连接建立前的错误则通过psql_srv::send_immediate_err直接向客户端发送错误包后关闭readyset/src/psql.rs。测试与验证psql-srv 的协议行为有大量单元测试背书全部内联在 psql-srv/src/protocol.rs 的tests模块中值得学习的有ssl_request对TlsMode::Disabled/Optional/Required三种模式分别断言SSLResponse的S/N字节及状态复位authentication_flow_successful/authentication_flow_failure用虚拟 Backend 走完整 Cleartext 认证流程断言返回的 7 条消息序列AuthenticationOk 6 个ParameterStatusReadyForQuery(I)以及错误路径的AuthenticationFailurequery_read/query_write/query_error验证简单查询协议下读、写、错误三种响应形态包括RowDescription的字段描述、CommandComplete::Delete(5)与事务内/外的ReadyForQuery状态位parse、bind、describe、execute系列验证扩展查询协议各环节对Protocol内部prepared_statements/portals状态的更新。小结psql-srv 把 PostgreSQL wire protocol 的繁重细节编解码、状态机、认证、TLS、错误映射、类型系统完整封装对外只暴露一个PsqlBackendtrait。对开发者而言这意味着接入一个 PostgreSQL 兼容服务的主要成本只剩“实现你的查询逻辑”本身实现PsqlBackend、调用run_backend即可让 psql、JDBC、libpq 等既有客户端原样连入。若想进一步深入建议按此顺序阅读仓库代码先看 psql-srv/README.md 建立整体认知再读 psql-srv/src/lib.rs 把握 trait 与数据结构最后对照 psql-srv/src/protocol.rs 的状态机与测试用例理解每条消息的流转。赞分享缓存后端数据库【免费下载链接】readysetReadyset is a MySQL and Postgres wire-compatible caching layer that sits in front of existing databases to speed up queries and horizontally scale read throughput. Under the hood, ReadySet caches the results of cached select statements and incrementally updates these results over time as the underlying data changes.项目地址https://gitcode.com/gh_mirrors/re/readyset点击查看免费下载相关推荐用 Go Micro MCP 让现有微服务零改造接入 AI Agent用 Go Micro MCP 让现有微服务零改造接入 AI Agent 导读 本文以 Go Micro 博客平台为例完整演示如何通过一行 mcp.Wit后端微服务AI AgentRPC框架Go Micro 平台示例用 MCP 网关让既有微服务零改动接入 AI AgentGo Micro 平台示例用 MCP 网关让既有微服务零改动接入 AI Agent 导读 examples/mcp/platform 是一个基于 Go Mic后端微服务AI AgentRPC框架SafeLine反向代理模式零侵入接入现有Web服务SafeLine反向代理模式零侵入接入现有Web服务 引言Web安全防护的新范式 在当今数字化时代Web应用安全已成为企业不可忽视的核心需求。传统的安全防WAF网络安全应用安全上一篇10分钟用ECC做出第一份HTML演示文稿指南下一篇TAICHI-flet扩展开发指南如何为你的应用添加新功能创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考