标签 Go 下的文章

从 0 到 1.5 亿 QPS:Uber 核心存储架构的十年演进与缓存设计哲学

本文永久链接 – https://tonybai.com/2025/09/01/uber-150-million-reads

大家好,我是Tony Bai。

在 Uber 这样体量的公司,其核心在线存储系统不仅要处理 PB 级的海量数据,还要以毫秒级的延迟响应每秒上亿次的请求。这一切是如何实现的?本文将深度整合 Uber 工程团队这几年公开发布的三篇文章,和大家一起穿越其核心存储架构的十年演进史:从最初为解决 MySQL 扩展性难题而生的 Schemaless,到拥抱 SQL 和强一致性的分布式数据库 Docstore,再到最终通过集成式缓存 CacheFront 将读取性能推向 1.5 亿 QPS 的极致。这是一个关于在 MySQL 之上构建分布式巨兽的真实故事,充满了工程上的权衡、妥协与创新。

Schemaless 的诞生——戴着镣铐的舞蹈

故事的起点,是 Uber 早期对 PostgreSQL 的依赖,以及随后因性能和扩展性问题向 MySQL 的迁移。然而,即便是 MySQL,在面对 Uber 业务爆炸式增长带来的写入和分片(sharding)压力时,也很快捉襟见肘。Schemaless——Uber 的第一个自研存储系统——正是在这样的背景下诞生的。

核心动机:解决 MySQL 的扩展性瓶颈

Schemaless 的设计目标非常明确:在 MySQL 之上构建一个水平可扩展的、对开发者透明的分片层。它并非要取代 MySQL,而是要成为 MySQL 的“放大器”。其核心设计充满了对当时工程约束的精巧妥协:

  • 无模式 (Schemaless):这并非真的没有模式,而是“读时模式”(schema-on-read)。数据以 JSON blob 的形式存储在 MySQL 的一个简单表中。这极大地简化了数据库端的管理,但也给应用层带来了数据解析和验证的负担。
  • 仅追加 (Append-only) 与不可变性 (Immutability):为了简化系统设计和避免复杂的并发控制,Schemaless 的核心数据单元——Cell——被设计为不可变的。更新操作实际上是写入一个新的 Cell 版本。这使得系统非常健壮,但也让其难以用作通用数据库。
  • 二级索引:通过一个独立的索引系统,Schemaless 实现了对非主键字段的查询,这在当时是一个重要的创新。

Schemaless 成功地解决了 Uber 早期的规模化问题,证明了在成熟数据库之上构建抽象层的可行性。但它的“极简主义”设计,也为后来的演进埋下了伏笔。

Docstore 的演进——从 NoSQL 回归 SQL 的怀抱

随着时间的推移,Schemaless 的局限性日益凸显。其仅追加的 API 和“读时模式”对开发者不够友好,导致许多团队转向了当时流行的 Cassandra。然而,Cassandra 的最终一致性模型给应用开发者带来了巨大的心智负担,同时其运维复杂性和资源效率也未能满足 Uber 的严苛要求。

在亲身经历了 Schemaless 和 Cassandra 的优缺点后,Uber 团队做出了一个关键决策:将 Schemaless 演进为一个通用的、支持事务的分布式 SQL 数据库。Docstore 就此诞生。

设计哲学:两全其美

Docstore 的目标是提供“两全其美”的体验:既有 NoSQL 文档模型的灵活性,又有传统关系型数据库的模式强制和强一致性。

  • 写时模式 (Schema-on-write):与 Schemaless 相反,Docstore 默认强制执行模式。表结构(列、类型)被明确定义,数据库负责保证数据的规整性。这极大地提升了数据的可靠性和开发效率。
  • 灵活的文档模型:Docstore 支持嵌套数据类型和“关联”(Associations),允许开发者在同一张表中模拟关系模型(一对多、多对多)和层级化的文档模型。
  • 开发者控制的数据局部性:通过引入分区键 (Partition Key) 的概念,Docstore 允许开发者显式地控制哪些数据应该物理上存储在一起,这对于优化查询性能至关重要。

架构核心:MySQL 之上的 Raft 与强一致性

Docstore 的架构是一个精巧的分层设计,其核心是在 MySQL 之上构建了一个强一致的复制层。

  • 分层架构:系统分为无状态的查询引擎层和有状态的存储引擎层。查询引擎负责路由、分片、鉴权等,而存储引擎负责数据的持久化。
  • 分区与复制:数据被分片(shard)后,分布在多个分区 (Partition) 中。每个分区是一个由 3-5 个 MySQL 节点组成的复制组,跨可用区部署以实现高可用。

  • Raft 共识协议:每个分区内部运行 Raft 共识协议来保证数据的一致性。所有写操作都由 Leader 节点发起,并通过 Raft 的复制日志同步到 Follower 节点。
  • 严格可串行化 (Strict Serializability):得益于 Raft,Docstore 在分区级别提供了最高的一致性保证——严格可串行化。这意味着开发者可以像操作单机数据库一样思考事务,而无需担心并发异常。

  • 事务的实现:Docstore 巧妙地将 MySQL 的原生事务能力暴露给了上层。一个 Docstore 事务,其本质就是一个在 Leader 节点上执行的 MySQL 事务,这个事务本身(而非其结果)作为 Raft 日志的单元被复制。这既保证了 ACID 语义,又实现了高可用。

Docstore 的诞生,标志着 Uber 存储系统的一次成熟蜕变。它从一个专用的键值存储,演变成了一个功能丰富的、通用的分布式 SQL 数据库,并成功地在 Uber 内部取代了 Cassandra,成为众多核心业务的首选。

CacheFront 的极致优化——迈向 1.5 亿 QPS

随着 Docstore 的广泛应用,新的挑战再次出现。许多业务场景呈现出典型的“读多写少”模式,读取 QPS 可能是写入的数十倍甚至上百倍。仅仅依靠 Docstore 存储引擎的 NVMe SSD 已经无法经济高效地满足对超低延迟和超高吞吐量的极致追求。

为了解决这个问题,Uber 团队没有让每个业务团队各自为战地搭建缓存,而是选择了一条更艰难但更具价值的道路:为 Docstore 构建一个深度集成的、透明的分布式缓存层——CacheFront

核心目标:透明、高效、一致

CacheFront 的设计目标清晰而宏大:

  1. 对用户透明:开发者无需修改代码或引入新的客户端,只需开启配置即可享受缓存带来的好处。
  2. 极致的低延迟:显著降低 P75、P99 甚至 P99.9 的读取延迟。
  3. 成本效益:用相对廉价的缓存资源(Redis)来卸载昂贵的数据库存储层负载。
  4. 更强的一致性:解决传统旁路缓存(Cache-Aside)模式中常见的缓存与数据库不一致问题。

架构与设计:缓存即服务,深度集成

CacheFront 被无缝地集成在 Docstore 的查询引擎层。所有读取请求都会先经过缓存层,缓存未命中时再穿透到存储引擎,并将结果异步写回缓存。这个看似简单的“缓存旁路”模式,在 Uber 的规模下,充满了工程上的挑战与创新。

缓存失效的“圣杯”:CDC 的妙用

缓存系统中最难的问题永远是缓存失效 (Cache Invalidation)。CacheFront 没有采用简单的 TTL(Time-To-Live)过期策略,因为它无法保证数据的一致性。其真正的“杀手锏”是利用了 Docstore 内建的变更数据捕获(Change Data Capture, CDC)服务——Flux

  • Flux 会持续地追踪(tail)底层 MySQL 的二进制日志(binlog)。
  • 当任何数据(包括条件更新)发生变更时,Flux 会捕获这些事件,并在亚秒级内向 Redis 发送失效或更新指令。
  • 这种基于 CDC 的异步失效机制,极大地缩短了数据不一致的时间窗口,提供了远强于 TTL 的最终一致性保证。

追求更强的一致性:同步失效的引入

随着业务对一致性要求的提高,仅仅依赖异步的 CDC 已经不够。CacheFront 的第二次重大升级,是实现了同步缓存失效

通过对 Docstore 存储引擎的改造,现在每一次写事务在提交前,都能返回该事务所影响的所有行的主键。查询引擎层在收到这些主键后,可以在写请求返回给客户端之前,同步地向 Redis 发送失效指令

这一改进,结合仍在后台运行的 Flux(作为兜底),为 CacheFront 提供了近乎读己所写 (Read-your-own-writes) 的强一致性保证,使得更多对一致性敏感的业务得以放心地使用缓存。

可观测性与弹性设计

为了在 Uber 的规模下可靠运行,CacheFront 还构建了一系列令人印象深刻的弹性与可观测性特性:

  • Cache Inspector:一个独立的 CDC 消费者,它会延迟一分钟消费 binlog,并持续地将数据库中的“真相”与缓存中的数据进行对比,实时度量缓存的不一致率(Staleness),并将其作为核心 SLO 指标。
  • 跨地域缓存预热 (Cache Warming):通过复制 Redis 的写操作流(而非数据本身)到灾备区域,并在灾备区域模拟“读请求”来填充缓存,实现了高效且数据一致的跨地域缓存预热。
  • 自适应超时 (Adaptive Timeouts):动态调整对 Redis 的请求超时时间,以匹配当前 P99.99 的网络延迟,避免了因超时设置不当导致的大量缓存穿透。
  • 熔断器 (Circuit Breakers):当某个 Redis 节点出现故障时,能快速熔断对该节点的请求,防止雪崩效应。
  • 智能分片:缓存的分片键与数据库的分片键故意设计为不同,以避免当某个 Redis 集群故障时,压力集中冲击到数据库的单个分片上。

成果:支撑 1.5 亿 QPS 的巨兽

经过多年的迭代,CacheFront 取得了惊人的成果:

  • 支撑超过 1.5 亿 QPS 的峰值读取,缓存命中率高达 99% 以上。
  • P75 延迟降低 75%,P99.9 延迟降低 67%
  • 通过缓存卸载,为一个核心用例节省了约 57,000 个 CPU 核心的数据库容量。
  • 缓存不一致率维持在 99.99% 以上的极高水平。

小结:没有银弹,唯有持续演进

Uber 存储架构的约十年演进史(2016~2025),是一个从解决眼前问题到构建长远平台,从拥抱 NoSQL 灵活性到回归 SQL 强一致性,最终通过极致的缓存优化来平衡成本与性能的经典故事。

它为所有构建大规模后端系统的工程师提供了宝贵的启示:

  1. 基于成熟组件构建:Docstore 的成功,在于它没有重新发明轮子,而是巧妙地站在了 MySQL 这个巨人的肩膀上。
  2. 演进式架构:没有一劳永逸的架构。系统必须能够根据业务需求的变化而持续演进,甚至进行方向性的调整。
  3. 缓存不是“银弹”,而是系统工程:一个生产级的缓存系统,远不止是“放一个 Redis 在前面”那么简单。它需要深度的系统集成、精巧的一致性保障机制和强大的可观测性与弹性设计。

最终,支撑 Uber 全球业务的,并非某一项神秘的“黑科技”,而是一系列坚实的、经过深思熟虑的、在真实世界的炮火中不断迭代和完善的工程决策。

参考资料


想系统学习Go,构建扎实的知识体系?

我的新书《Go语言第一课》是你的首选。源自2.4万人好评的极客时间专栏,内容全面升级,同步至Go 1.24。首发期有专属五折优惠,不到40元即可入手,扫码即可拥有这本300页的Go语言入门宝典,即刻开启你的Go语言高效学习之旅!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

成为更完整的 Go 工程师,从补上这堂系统编程课开始

本文永久链接 – https://tonybai.com/2025/09/01/system-programming-in-go

大家好,我是Tony Bai。

作为一名 Go 工程师,我们无疑是幸运的。这门语言为我们提供了简洁的语法、强大的并发模型和一套设计精良的标准库。我们能以极高的效率,构建出高性能的 Web 服务、数据管道和云原生应用。

我们熟练地使用 http.ListenAndServe 启动服务,用 go build 创造可移植的二进制文件,用 io.Copy 在源与目标之间传递数据。我们享受着 Go 带来的便利,在应用层快速地创造着价值。

但你是否在某个瞬间,曾感到自己的知识体系中,似乎缺少了点什么?

  • 当你面对一个线上服务的疑难杂症,追查到标准库的边界后,便感到前路茫茫,不知如何再向下深入。
  • 当你希望构建一个更底层的工具,需要精细地控制进程、处理信号、或者在多个服务间进行最高效的本地通信时,你发现自己对 os/exec, syscall 这些包的理解,还停留在“知道有这么个东西”的层面。
  • 你渴望成为一名架构师或资深专家,但你意识到,自己对应用程序与操作系统之间那层看不见的交互,还知之甚少。

这种感觉,就像一位武功高强的剑客,招式精妙,但内力修为尚有欠缺。这缺失的一环,正是那堂经典的、能让你洞悉底层运作原理的“系统编程课”。

一堂被“跳过”的必修课

在 Go 语言诞生之前,许多后端工程师的成长路径都绕不开一本圣经——《UNIX 环境高级编程》(APUE)。它系统地教会了我们,一个程序是如何通过文件描述符、进程、信号、管道、Socket 这些基本元素,与操作系统内核进行“对话”的。这堂课,是构建坚实后端知识体系的基石。

而 Go 语言的巨大成功,在某种程度上,让新一代的开发者有机会“跳过”了这堂硬核的必修课。这并非坏事,它证明了语言的进步。但对于追求技术卓越的我们来说,知识体系中的这块拼图,必须被补上。

因为不理解系统编程,你对 Go 的理解就永远无法完整。你无法真正领会 io.Reader/Writer 接口设计的哲学之美,无法看透 net 包背后网络轮询器的惊人效率,也无法自信地处理那些最棘手的、跨越应用层与系统层边界的问题。

补上这堂课,成为一名更“完整”的工程师

这个微专栏——《Go 系统编程:揭秘进程控制、I/O 与 IPC》——正是为了帮助你,系统性地、用 Go 语言的现代视角,补上这堂至关重要的课

它不是一本枯燥的 API 手册,而是一次充满“探案”乐趣的底层探索之旅。我们将聚焦于后端开发中最核心的三大主题:文件 I/O、进程管理、以及进程间通信(IPC)。像侦探一样,从一个简单的 Go 函数出发,层层深入,直达操作系统内核,亲眼见证每一个经典概念的真实运作过程。

学完这个专栏,你将获得什么?
* 坚实的知识根基:你将不再满足于“知其然”,而是能“知其所以然”,建立起一套完整的、从应用层到系统层的知识体系,让你成为一名知识结构更完整的工程师。
* 精准的问题定位能力:面对与文件、进程、IPC 相关的诡异问题,你将拥有从文件描述符、进程信号、管道状态等底层视角进行分析和定位的能力。
* 编写更健壮、更专业的代码:你将学会如何正确地管理文件句柄、如何让服务在 kill 命令下优雅退出、如何为你的应用选择最合适的 IPC 机制。
* 解锁 Go 的全部潜力:你会发现 os/exec, io, syscall 等包的背后,蕴藏着巨大的能量,可以用来构建出远超普通 Web 应用的、强大的底层工具与服务。

专栏大纲:你的底层探索路线图

我为你精心设计了一条由浅入深、层层递进的学习路径,共包含8 篇核心正文:

第00讲 | 系统调用:Go 程序如何直接与操作系统内核“对话”?

简介: 本篇是整个专栏的基石,也是一把“总钥匙”。我们将揭开 Go 程序静态编译、轻松部署背后的最大秘密——不依赖 libc 的独立系统调用机制。学完它,后续所有章节对你来说都将豁然开朗。

模块一:揭秘 I/O:从文件描述符到接口哲学

  • 第 01 讲 | 文件 I/O:从文件描述符到 io.Reader/Writer 的抽象

    简介: 深入 UNIX“一切皆文件”的哲学。我们将从内核的整数文件描述符(FD)出发,看 Go 如何将其封装为 *os.File,并最终升华为 io.Reader/Writer 这一“神来之笔”的接口设计。

  • 第 02 讲 | 文件系统:用 Go 精准操控文件元数据与目录

    简介: 超越简单的读写,成为文件的“管理者”。本讲将带你深入 os.FileMode 的位掩码世界,用代码实现 chmod,并彻底辨析硬链接与符号链接的本质区别。最后,你将学会用 filepath.WalkDir 优雅地漫游整个目录树。

模块二:揭秘进程:生命周期与后台守护

  • 第 03 讲 | 进程的生命周期:从创建、通信到优雅退出

    简介: 这是从编写“脚本”到构建“健壮系统”的分水岭。我们将揭示 Go 为何选择 os/exec 而非 fork,并通过管道与子进程进行 I/O 对话,最终掌握结合信号与 context 实现服务优雅退出的黄金准则。

  • 第 04 讲 | 实战:用 Go 编写一个健壮的守护进程 (Daemon)

    简介: 一次系统编程的“成人礼”。我们将亲手复刻一个经典的 Daemonize 函数,经历一次“失败的冒险”,从而深刻理解 fork 在 Go 中的危险性,并最终掌握通过 os/exec 创建守护进程的正确道路。

模块三:揭秘 IPC:进程间的对话艺术

  • 第 05 讲 | 经典管道:匿名管道与命名管道 (FIFO) 的 Go 实现

    简介: 铺设第一条进程间通信的道路。我们将回顾 os/exec 背后的匿名管道,并重点实践命名管道(FIFO),让任意两个不相关的进程,也能像读写普通文件一样进行通信。

  • 第 06 讲 | 高性能共享:消息队列与共享内存

    简介: 一次充满“探案”和“反转”的硬核探索。我们将对比 System V 和 POSIX 消息队列,在实践中发现它们的 Go 亲和力差异。然后,我们将深入 IPC 的性能之巅——共享内存,并分别用System V Shmem和 mmap 亲手实现一个跨进程的“零拷贝”通信方案。

  • 第 07 讲 | 网络化 IPC:Go 的王牌——Socket 编程

    简介: 专栏的升华与收官之作。我们将见证 Go 如何用 net.Listener 和 net.Conn 将繁琐的 Socket API 变得无比优雅,并揭示其“goroutine-per-connection”模型背后的网络轮询器秘密。你将明白,Go 为何能成为“云原生第一语言”。

如果你已经不满足于仅仅是“使用”Go,而是渴望真正地“理解”和“掌控”它;
如果你想在技术进阶的道路上,拥有更坚实的底层基础和更广阔的视野;
如果你相信,成为一名更完整的工程师,是你职业生涯的下一个目标

那么,这个微专栏就是为你准备的。

扫描上方二维码,或点击这里,立即订阅《Go 系统编程:揭秘进程控制、I/O 与 IPC》。

让我们一起,补上这堂至关重要的课,开启一段充满挑战与收获的硬核之旅。我们专栏里见!


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 Go语言精进之路1 Go语言精进之路2 Go语言第一课 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats