[控场AI]
· 7 分钟阅读· 3,620 字

Cloudflare Hyperdrive 如何用一份规范副本解决 MySQL 缓存一致性难题

Cloudflare Hyperdrive 如何用一份规范副本解决 MySQL 缓存一致性难题

Hyperdrive 因共享缓存存了翻译后的字节导致 MySQL 空结果,改用规范副本修复。

Cloudflare Hyperdrive 正式支持 MySQL,其 Client 与 Endpoint 双节点共享同一数据中心缓存以复用连接和结果。问题出在缓存编码:实现沿袭 Postgres 时期的假设,把写回 Worker 的翻译后字节直接缓存,而 MySQL 的 CLIENT_DEPRECATE_EOF 能力协商使两个节点使用不同能力集,Endpoint 与 Client 各自做 EOF/OK 包翻译,共享缓存后互相误读,导致约 0.001% 的查询返回空结果或连接挂起。修复规则是无论哪个节点产生结果,都只存一份以 Hyperdrive 自有能力编码的规范副本,翻译只在出口方向进行。上线后连接成功率回到 99.99%。GA 前还修复了握手协商 utf8mb3 损坏 emoji、COM_RESET_CONNECTION 后编码被重置为服务器默认值等共 20 个问题。

Cloudflare Hyperdrive 如何用一份规范副本解决 MySQL 缓存一致性难题

Cloudflare Hyperdrive 是一个为 Workers 设计的分布式连接池:Client 节点运行在 Worker 旁边,Endpoint 节点靠近数据库,两者共用数据中心缓存,让边缘请求复用已建立的数据库连接。MySQL 支持在经历约 18 个月 beta 后正式 GA,而这次发布背后真正的故事,是一个藏在缓存编码细节里的一致性 bug。

Cloudflare Hyperdrive 拓扑示意图

Hyperdrive 在 Worker 与数据库之间做了什么

Worker 运行在边缘,数据库通常位于单一区域。Cloudflare 自己的测算是,两端建立连接至少需要五次网络往返;如果每个请求都新建连接,握手开销会超过查询本身。Hyperdrive 的解法是保持连接常热:Endpoint 节点维护一个开放连接池,供大量 Worker 请求共享。

除连接池外,Hyperdrive 还在每个数据中心的 CDN 缓存中存放高频重复查询的结果。官方给出的数字是:连接池让查询快 3 倍,重复查询最高快 100 倍,缓存命中时延低于 5 毫秒。迁移方式也很轻——替换连接字符串即可,支持的 Node.js 驱动包括 mysql2 和 mysql。

两个节点、一份缓存,以及一个编码分歧

架构在两端各放一个缓存,当 Client 与 Endpoint 位于同一数据中心时共享同一份缓存。这对性能是合理默认值:任一节点都能在不访问数据库的情况下响应重复查询。但这也意味着两个进程写入同一个键空间,bug 就藏在这里。

Cloudflare 对底层机制的描述很具体:缓存条目以查询线上报文的哈希为键(带一定归一化),值为结果字节。要让这套机制成立,同一条响应无论由哪个节点写入,都必须以相同字节存储。而实现继承了 Postgres 时期的一个假设——写回 Worker 的字节就是值得缓存的字节。

空结果背后的 MySQL 能力协商不匹配

MySQL 让这个假设变得不安全,原因在于其线上协议的能力协商细节。连接建立时会协商 capabilities,其中 CLIENT_DEPRECATE_EOF 会改变结果集的结束方式:元数据 EOF 包消失,最后一个包变成 OK 包。MySQL 5.7.5 及以上支持该能力,但客户端必须主动请求新行为。

流行的 Node.js 驱动 mysql2 不会请求它,而 Hyperdrive 会。于是 Endpoint 与 Client 使用不同的能力集,池化层需要把每个结果集翻译成 Worker 驱动期望的形态——插入 EOF 包、把 OK 包转回去。缓存翻译后的字节会在两个方向同时破坏翻译:Client 读到 Endpoint 以 Hyperdrive 自有编码写入的结果时,会不经翻译直接发给 Worker;Endpoint 读到 Client 已翻译的字节时,会交给另一个 Client 再翻译一次,导致提前发出 EOF 包或直接挂起。

症状很窄,解释却很机械:某高流量客户约 0.001% 的查询返回空结果,问题只在开启缓存的实例上出现,调查一路追到 MySQL 协议文档才找到这个假设。

MySQL 线上协议采用客户端/服务器能力协商机制:连接握手阶段双方交换 32 位 capability 标志位,用于声明各自支持的可选特性,只有双方都置位的能力才会在会话中启用。常见的标志包括 CLIENT_PROTOCOL_41、CLIENT_SSL、CLIENT_MULTI_STATEMENTS、CLIENT_DEPRECATE_EOF 等。能力协商本质上是协议层的可选特性开关,服务端必须按客户端实际请求的能力集来组织响应字节,否则客户端解析时会出现包边界错位。

CLIENT_DEPRECATE_EOF(MySQL 5.7.5 引入)改变了结果集的结束方式:传统协议在列定义之后发送一个 EOF 包(0xFE)标记元数据结束,再逐行发送数据,最后以 EOF 包收尾;启用该能力后,元数据结束不再发 EOF,结果集末尾改用 OK 包(0x00 开头,携带 affected rows、warning 数等状态信息)收尾。这减少了一次往返,但要求客户端与服务端对“包何时结束”有完全一致的解读。mysql2 等驱动默认不请求该能力,因此服务端会退回旧式 EOF 分隔;若中间层擅自按新能力解析,就会出现把数据行误判为结果集结束、或把 OK 包当作行数据的情况——这正是空结果与挂起症状的协议级成因。

修复:每条结果只存一份规范副本

Cloudflare 的修正是一条关于缓存内容的规则:无论哪个节点产生结果,一律以 Hyperdrive 自有能力编码存储一份规范副本。Client 在出口方向做翻译,Endpoint 读取自己能理解的格式。上线后空行报告停止,客户端连接成功率达到厂商设定的 99.99% 目标。

从一个厂商的 bug 做泛化通常廉价,但这条结论站得住:共享缓存有两个写入方,就必须有唯一的序列化约定;翻译层也必须明确自己位于缓存的哪一侧。同样的纪律适用于任何自建的池化或缓存数据路径,包括放在推理栈前面的那些缓存。

走向 GA 的那些小 bug

从 beta 到 GA 共二十项修复,其中两个编码缺陷值得单独拎出来,因为它们展示了同一类失败在更低风险场景下的样子。启动握手协商的是 utf8——utf8mb3 的废弃别名,每个码点最多三字节——写入数据库的 emoji 回来时是坏的,修复方式是协商 utf8mb4。

第二个来自清理步骤。Hyperdrive 用 COM_RESET_CONNECTION 在两次借出之间清空会话状态,但该命令会把连接恢复到服务器默认值而非启动时协商的编码。服务器默认 latin1 时,归还的连接就是 latin1,池化层不得不在每次重置后重新设置编码。这两个 bug 都属于只测 happy path 的测试套件会漏掉、而数据特殊的客户会撞上的类型。

utf8mb3 是 MySQL 对旧版三字节 UTF-8 实现(utf8 别名)的正式更名,最早出现在 8.0.28 的文档与错误信息中,目的是让用户意识到它并非完整 UTF-8。它每个字符最多 3 字节,覆盖基本多文种平面(BMP)内的字符,但无法表示 emoji、部分罕见汉字与音乐符号等四字节码点。utf8mb4 才是完整的 UTF-8 实现,每字符最多 4 字节,自 MySQL 5.5.3 起提供。

MySQL 的字符集分多个层级:服务器默认、数据库默认、表默认、列默认,以及连接级(由 character_set_client、character_set_connection、character_set_results 三个会话变量控制)。连接级字符集在握手时协商,但 COM_RESET_CONNECTION 只把会话状态恢复到服务器默认值,不会重放握手时的协商结果。因此若服务器默认是 latin1,重置后的连接就退化为 latin1,四字节字符在写入或读出时会被截断或替换为 ?。这也是连接池实现普遍需要在重置后显式重设编码的原因。

如何解读 99.99% 这个数字

Cloudflare 报告的是 Client 连接成功率,与 beta 阶段设定要验证的 99.99% 生产可用性目标一致。这是厂商自己的数字,文章没有引用独立测量。对计划迁移的团队而言,产品页和开发者文档说明 Hyperdrive 支持任何 MySQL 或 Postgres 数据库,包括托管服务和 Postgres 兼容引擎,文档中也列出了受支持的驱动。

对于区域数据库加读多写少、全球分布的应用,运维路径是清晰的:把一条读路径接入池化器,在真实流量上测缓存命中率,并盯住这次发布真正翻转的两个指标——空结果或畸形响应的比例,以及连接成功率。这也是任何缓存一致性工作都应遵循的度量方式:先量化异常响应,再谈性能收益。

给自建缓存路径的三条经验

  • 共享缓存必须约定唯一序列化格式,不能按写入方各自的编码落盘。
  • 翻译层要明确位于缓存哪一侧,否则双向翻译会互相污染。
  • 编码协商(utf8mb4、COM_RESET_CONNECTION 后的重置)要写进测试用例,而不是等客户用特殊数据发现。
#Cloudflare Hyperdrive#MySQL 连接池#缓存一致性#CLIENT_DEPRECATE_EOF#mysql2 驱动#边缘计算数据库#Hyperdrive GA
分享:

相关推荐