MoreRSS

site iconTonyBai | 白明修改

重复
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

TonyBai | 白明的 RSS 预览

“简单”不是“容易”:Go开发者应该懂的5个道理

2025-09-04 08:04:54

本文永久链接 – https://tonybai.com/2025/09/04/simple-is-not-easy

大家好,我是Tony Bai。

在软件工程领域,有些演讲如同灯塔,其光芒足以穿透时间的迷雾,持续为后来者指引方向。Clojure语言的创造者Rich Hickey在2011年的Strange Loop大会上发表的“Simple Made Easy”,正是这样一例。他以一种近乎哲学家的思辨,对我们行业中最被滥用、最被误解的两个词——“简单”(Simple)“容易”(Easy)——进行了本源性的解构。

时至今日,这场演讲对于以“简单”著称的Go语言社区,依然具有重要的警示意义。我们常常自豪于Go的语法“简单”,工具链“容易”上手,但我们追求的,究竟是真正的“简单”,还是仅仅是表面的“容易”?

本文将和你一起重温Hickey的这场经典演讲,并结合Go语言的实践,提炼出每一位Gopher都应该深刻理解的五个核心道理。这既是对一个经典演讲的回顾,更是一次对我们日常编码决策和技术选型标准的反思。

道理一:精确你的词汇——“简单”与“容易”是两回事

Hickey的第一记重拳,就砸向了我们混乱的词汇表。他从词源学出发,为这两个概念划定了清晰的界限:

  • 简单 (Simple):源于拉丁语sim-plex,意为“一个褶皱”或“一股编绳”。它的反义词是复杂 (Complex),意为“交织、缠绕在一起”。因此,“简单”描述的是事物的内在状态,关乎其是否存在交织和纠缠。它是一个客观属性。

  • 容易 (Easy):源于拉丁语adjacens,意为“靠近的、在旁边的”。它的反义词是困难 (Hard)。因此,“容易”描述的是事物与我们的相对关系,关乎其是否与我们的认知、技能或工具相近。它是一个相对概念。

这个区分至关重要。当我们说“我喜欢用Go,因为它很简单”时,我们真正的意思往往是“它对我来说很容易”,因为:

  • 它很熟悉 (Familiar):它的语法类似C,没有复杂的泛型或宏。
  • 它很就手 (At hand):安装方便,工具链开箱即用。

Hickey警告说,我们整个行业都对“容易”——尤其是“熟悉”和“就手”——有一种不健康的迷恋。这种迷恋让我们倾向于选择那些看起来像我们已知事物的东西,从而拒绝学习任何真正新颖但可能更简单的东西

对于Go开发者:我们需要警惕,不要将Go的“语法简洁”(一种形式上的“容易”)与系统的“结构简单”划等号。一个用简洁语法写成的、充满了全局状态和隐式依赖的Go程序,其本质是复杂的。

道理二:警惕“容易”的复杂性——状态、对象与继承的陷阱

Hickey指出,许多我们认为“容易”的编程范式,恰恰是复杂性的最大来源,因为它们将不同的关注点“编织”在了一起。

1. 状态(State)是万恶之源

var x = 1; x = 2; 这种可变状态,在Hickey看来,是软件中最根本的“交织”——它将值(Value)时间(Time)紧密地缠绕在一起。你永远无法在不考虑时间点的情况下,获得一个确定的值。

对于Go开发者:虽然Go不是一门纯函数式语言,但我们应该在力所能及的范围内,尽量推崇不可变性。

  • 优先使用值传递:对于小型结构体,按值传递而非指针传递,可以避免意外的副作用。
  • 警惕共享的可变状态:在并发编程中,与其用sync.Mutex保护一堆共享的可变数据,不如思考如何通过channel传递不可变的“消息”,从根本上消除状态的交织。

2. 对象 (Objects) 是复杂性的打包机

传统的面向对象编程,将状态、身份(Identity)和值这三个独立的概念打包进了一个叫做“对象”的东西里。你无法轻易地将它们分开处理。

对于Go开发者:Go在这一点上做得相对出色。Go的struct更接近于纯粹的数据聚合(C-style struct),而不是带有复杂继承体系和封装状态的“对象”。我们应该保持并发扬这一优点:

  • 让Struct保持简单:让它专注于承载数据。
  • 将行为(方法)与数据分离:Go的方法是附加在类型上的函数,而非封装在对象内部。这鼓励我们编写更多无状态的、可测试的纯函数来处理数据。

3. 继承 (Inheritance) 是类型的强耦合

继承在Hickey看来是“定义上的交织”。子类与父类被紧密地绑定在一起,形成了一个难以分割的整体。

对于Go开发者:Go通过组合优于继承的设计,从语言层面避免了这个问题。我们应该充分利用接口(interface)和结构体嵌入(struct embedding)来实现代码的复用和多态,而不是去模拟继承。接口定义了行为契约,而结构体嵌入则允许我们“借用”实现,这两者都比继承提供了更松散的耦合。

道理三:拥抱“简单”的工具箱——值、函数、数据与队列

如果状态、对象、继承是复杂性的来源,那么我们应该拥抱什么?Hickey为我们提供了一个“简单”的工具箱:

  • 值 (Values):不可变的数据。一个值永远不会改变,因此它与时间无关,可以在任何地方被安全地共享和传递。
  • 函数 (Functions):无状态的行为。给定相同的输入,永远返回相同的输出。
  • 数据 (Data):使用通用的数据结构(map, list, set)来承载信息,而不是为每一种信息都创建一个新的class。这使得我们可以编写通用的、可复用的数据处理函数。
  • 队列 (Queues):将“何时”与“何地”的决策解耦。当组件A需要组件B做事时,A不应直接调用B,而是应该将一个消息放入队列中。这打破了组件间的时空耦合。

对于Go开发者:Go的语言特性与这个“简单”工具箱惊人地契合!

  • 值与函数:Go鼓励值语义,并且其函数是一等公民。编写纯函数在Go中也可以是自然而然的事情。
  • 数据:Go内置的map和slice就是强大的通用数据结构。我们应该抵制为简单的数据集合过度封装struct和方法的诱惑。
  • 队列channel正是队列思想的完美体现! 它将goroutine之间的通信从直接调用(时间、空间耦合)解耦为异步消息传递。Hickey的理论为“多用channel,少用共享内存和锁”这一Go社区的最佳实践,提供了坚实的哲学基础。

道理四:你的目标是简单的“制品”,而非简单的“构件”

Hickey强调,我们必须区分构件(Constructs)——我们编写的代码、使用的语言和库——和制品(Artifacts)——那个真正在服务器上运行、为用户提供服务的程序。

我们常常沉迷于构件的“容易性”:“看,我只用了16个字符,没有分号!”,而忽略了这些“容易”的构件可能产生极其复杂的制品。一个充满了可变状态和隐式依赖的程序,无论写起来多么“容易”,其最终的制品都将是难以理解、难以修改、难以调试的。

对于Go开发者

  • 超越gofmt:代码格式的统一只是最浅层次的“容易”。我们更应该关注代码的结构是否简单,模块间的依赖是否清晰。
  • 警惕interface{} (或 any):any是一个“容易”的工具,它让我们可以绕过类型系统。但它会产生复杂的制品,因为我们在运行时丢失了类型信息,增加了不确定性。
  • 思考长期影响:在选择一个库或框架时,不要只看它的入门教程有多“容易”。更要思考它会给你的系统带来怎样的长期复杂性。一个“魔法般”的框架可能会在短期内提升开发速度,但当问题出现时,你将深陷其复杂的内部机制中无法自拔。

道理五:“简单”需要思考,而“容易”往往是捷径

Hickey用一个跑步的例子生动地说明了这一点:只有短跑选手才能从一开始就全力冲刺。软件开发是一场马拉松。如果你只追求起步时的“容易”,你很快就会被自己制造的复杂性拖垮。

选择“简单”的道路,往往需要在开始时付出更多的思考:

  • 你需要花时间去分解问题,识别出其中真正独立的概念。
  • 你需要抵制住使用熟悉但复杂的工具的诱惑。
  • 你需要设计清晰的边界和接口。

这个前期的“思考”成本,就是Hickey图表中那条“简单”路线在起步阶段不如“容易”路线陡峭的原因。但从长远来看,这条路会越走越顺,而那条追求“容易”的捷径,最终会通向复杂性的泥潭。

对于Go开发者

在开始一个新项目或新功能时,问自己几个问题:
- 我真的需要引入这个新的外部依赖(如ORM、大型框架)吗?还是可以用标准库更简单地实现?
- 这个接口的设计是否将不同的关注点(如数据获取和业务逻辑)交织在了一起?
- 我是在设计一个能应对当前问题的最简单的方案,还是在为一个想象中的复杂未来进行过度设计?

小结:选择做一名“简单”的工程师

Rich Hickey的演讲像一面镜子,映照出我们作为工程师在日常工作中不自觉的偏见和思维惰性。它挑战我们去重新审视我们对“好代码”和“生产力”的定义。

对于Gopher而言,我们手中握着一门在设计上就倾向于“简单”的语言。但语言本身并不能保证我们写出简单的系统。真正的“简单”是一种选择,一种需要我们时刻保持警惕、不断反思的思维纪律。

下一次,当你面对一个技术决策时,请停下来问自己:我是在选择那条“容易”的、熟悉的下坡路,还是那条需要一些前期思考,但最终通往光明和简单的上坡路?

答案,将决定你和你所构建的系统的最终命运。


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

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


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

© 2025, bigwhite. 版权所有.

Gopher直通大厂,就从这第一课开始!

2025-09-03 08:52:21

本文永久链接 – https://tonybai.com/2025/09/03/gopher-first-lesson-to-big-factory

大家好,我是Tony Bai。

很多计算机专业的同学们都在问:想进大厂,要先学好哪门编程语言?

从应用广泛程度来说,学好Go语言肯定错不了!我们来看一下大厂们都用Go在做哪些开发:

阿里用于基础服务、网关、容器、服务框架等开发。

字节跳动用于即时通信(IM)、K8s、微服务等开发。

腾讯用于微信后台、云服务、游戏后端等开发。

滴滴用于数据平台、调度系统、消息中间件等开发。

此外,美团、百度、京东、小米等都在业务中大量使用Go语言做开发。可见,同学们只要玩转Go语言,大厂都会张开双臂欢迎你们。

大厂为何如此青睐Go语言呢?有三点重要原因:

  • 简单易上手: Go语法简洁,学习成本低,代码易维护;
  • 生产力与性能有效结合: Go拥有卓越的并发性能,内置调度器和非抢占式模型,保证了超高的稳定性;
  • 使用快乐且前景广阔: 优良的开发体验,包括得心应手的工具链、丰富健壮的标准库、广泛的社区支持等。

总的来说,Go相对于C/C++,性能并没有明显差距,可维护性还更好;相对于Python,Go性能大幅领先,入门难度则相差无几。

直通大厂,同学们请看《Go 语言第一课》这本书,书中详细介绍了Go的设计哲学与核心理念,全面讲解了Go的重要语法特性。没有基础也完全不必担心,本书手把手式教学,小白立即轻松上手。


扫描上方二维码,即可五折购书(在有效期内)

现在,让我们进入课堂,开始Go语言学习的第一课吧。

Part.1 零基础起步,Go开发全掌握

本书为读者设计了一条循序渐进的学习路线,可以分为三个部分。

首先讲述Go语言的起源与设计哲学;

然后说明开发环境的搭建方法;

最后详细介绍Go的重要语法与语言特性,以及工程实施的一些细节。

初次学习Go开发的同学们一定要注意,动手实践是学习编程的不二法门,在进入第二部分学习时,就要根据书中内容同步搭建实验环境,一步一个脚印地走稳走好。

Go的设计哲学

本部分先介绍了Go语言在谷歌公司内部孵化的过程,描述了其在当今云计算时代的广泛应用。


Go的第一版官网

重点说明了Go的5个核心设计哲学:

  • 简单: 仅有25个关键字,摒弃了诸多复杂的特性,便于快速上手;
  • 显式: 要求代码逻辑清晰明确,避免隐式处理带来的不确定性;
  • 组合: 通过类型嵌入提供垂直扩展能力,通过接口实现水平组合,灵活扩展功能;
  • 并发: 原生支持并发,用户层轻量级线程,轻松支持高并发访问;
  • 面向工程: 注重解决实际问题,围绕Go的库、工具、惯用法和软件工程方法,都为开发提供全面支持。

读者理解了Go的设计哲学就能明确它擅长的方向,澄清心中的疑问,也掌握了使用Go进行编程的指导原则。

Part.2 搭建Go开发环境

这部分先针对Windows、macOS、Linux三种主流操作系统给出了多种安装方法,包括使用安装包、使用预编译二进制包、通过源码编译,说明如何管理多个Go版本。

然后基于经典的“Hello World”示例,演示编译运行的方法,讲解Go的基本程序结构,包括包声明、导入包、main函数等内容。接着深入讲解Go包的定义、导入、初始化与编译单元。

// ch3/helloworld/main.go
package main
import "fmt"
func main() {
    fmt.Println("hello, world")
}

详细讲解Go Module的核心概念,结合创世项目案例、社区共识、官方指南,给出清晰的项目布局建议。梳理了Go依赖管理的演化历程,重点讲解基于Go Module的依赖管理操作,包括添加、升级/降级、移除、替换等操作。

经过这部分的学习,读者可以掌握Go的编译与运行方法、项目的组织与管理,具备工程化的能力。

Part.3 Go语言特性详解

这部分是本书的重点,覆盖基础语法知识、并发、泛型、测试等内容;在结构上由浅入深,层层递进,读者只要坚持学练结合,就能全盘掌握Go的关键知识。

基础语法知识包含以下内容:

  • 变量与类型: 说明变量的声明方法、变量的作用域。
  • 基本数据类型: 详细讲解布尔型、数值型、字符串型的特性与常用操作。
  • 常量: 重点讲解Go常量的创新性设计,包括无类型常量、隐式转换、实现枚举。
  • 复合数据类型: 讲解数组、切片、map类型、结构体的声明与操作。
  • 指针类型: 解释指针的概念,说明其用途与使用限制。
  • 控制结构: 详细介绍if、for、switch语句的用法,实现分支、循环功能。
  • 函数: 说明函数的声明、参数、多返回值特性,以及defer的使用与注意事项。
  • 错误处理: 讲解了error接口的错误处理,以及异常处理的panic机制。
  • 方法: 详解Go方法的声明与本质,通过类型嵌入模拟“实现继承”。
  • 接口: 说明接口类型的定义、实现方法与注意事项。

并发是Go的“杀手锏”级高阶特性,书中详述了Go并发的原理,给出了并发实现方案,即通过channel通信实现goroutine间同步,而非共享内存。说明channel与select结合使用的惯用法。


CSP模型

泛型是Go 1.18版本的新增特性,解决了为不同类型编写重复代码的痛点。书中介绍了Go泛型设计演化简史,讲解泛型语法、类型参数、类型约束,并给出了代码示例。


接口类型的扩展定义

最后讨论Go代码的质量保障方法,介绍了Go内置的测试框架,包括单元测试、示例测试、测试覆盖率以及性能基准测试,帮助读者快速且方便地组织、编写、执行测试,并得到详尽的测试结果反馈。


Go测试覆盖率报告

Part.4 作者介绍

本书作者Tony Bai(白明),资深架构师,行业经验超20年,现于汽车行业某独角兽Tier1企业担任车云平台架构组技术负责人。

出于对技术的追求与热爱,他发起了Gopher部落技术社群,也是tonybai.com的博主。

Tony Bai老师早在2011年Go语言还没发布Go 1.0稳定版本时,他就在跟随、实践。当Go在大规模生产环境中逐渐替代了C、Python,Go便成为他编写生产系统的第一语言。

后来,Tony Bai老师在极客时间上开设课程讲解Go语言开发,引领学员从入门到建立思维框架,走向大厂。累计2.4w名学员学习这门课程并纷纷给出高分评价。

如今,Tony Bai老师基于在线课程将内容整理成书,并补充了之前缺失的重要语法点(如指针、测试、泛型等),并对已有内容进行了精炼,同时更新至Go 1.24版本。

相信这本书会帮助更多读者轻松学会Go语言,解决实际工作问题,获得职业成功。

Part.5 结语

《Go 语言第一课》这本书可以说既懂新手痛点,又懂工程实战。本书从Go的设计哲学入手,然后给出保姆级的环境搭建、代码组织指南,最后通过由浅入深的语法讲解,覆盖从基础到高阶的所有核心特性。

本书具备三大特点。

第一是高屋建翎,开篇即剖析Go语言的设计哲学和编程思想,帮助读者透彻理解Go的核心理念,了解Go的特长,知道如何使用以获得最佳效果。


精彩书摘

第二是路径完整,覆盖Go入门的基础知识与概念,打通基础知识-语法特性-工程实践全流程,助力读者从新手进化为合格的Go开发工程师。


精彩书摘

第三是保姆级讲解,搭建环境是一步一图,讲解语法时辅以大量精心设计的示例代码,简洁明了,帮助读者直观地理解和掌握重点与难点内容。书中还针对Go开发中易犯的错误给出了贴心的避坑提示。


精彩书摘

本书适合各个层次的读者。对于Go初学者,可以循序渐进地掌握Go编程;对于动态编程语言的开发者,可以通过本书平滑转投Go阵营;对于Go的技术爱好者,可以增进认知,培养专业开发水准。

现在翻开《Go 语言第一课》,开启Go开发之旅,高并发服务端、云原生应用开发,都将轻松掌控!

今日互动

说说你对Go语言的看法?

点击右侧链接,在原文留言区参与互动,并点击在看和转发活动到朋友圈,我们将选1名读者获得赠书1本,截止时间9月15日。


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

© 2025, bigwhite. 版权所有.

亚马逊CTO Werner Vogels的9条军规

2025-09-02 16:15:08

本文永久链接 – https://tonybai.com/2025/09/02/amazon-cto-werner-vogels-9-commandments

大家好,我是Tony Bai。

最近,在一次私密的炉边谈话中,亚马逊CTO、互联网基础设施的奠基人之一Werner Vogels,分享了他二十年来构建高可用系统的核心经验。

以下是他那些直击要害、毫不含糊的九条法则,每一条都值得我们深思。










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

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


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

© 2025, bigwhite. 版权所有.

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

2025-09-01 21:59:55

本文永久链接 – 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语言高效学习之旅!


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

© 2025, bigwhite. 版权所有.

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

2025-09-01 08:33:18

本文永久链接 – 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技能再上一个新台阶!


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

© 2025, bigwhite. 版权所有.

“无聊”设计的终极奥义:为什么“做可能奏效的最简单的事”是最高法则?

2025-08-31 08:02:00

本文永久链接 – https://tonybai.com/2025/08/31/the-simplest-thing-that-could-possibly-work

大家好,我是Tony Bai。

在我们解读了Github工程师Sean Goedecke关于“无聊即可靠”的系统设计API设计理念之后,他再次带来了一篇精彩的的文章——《Do the simplest thing that could possibly work》。这既是对前两篇文章思想的延续,更是将其核心哲学提炼为一条终极黄金法则:在软件设计的每一个环节,都应“做可能奏效的最简单的事”。

这条法则,在今天这个充斥着“无限扩展”、“优雅分布式”、“完美分层”等宏大叙事的时代,显得尤为重要。Goedecke认为,工程师们最大的误区,就是试图一开始就设计出那个“理想”系统,而忽略了当下最核心的问题。

本文将继续和大家一起来深入剖析Goedecke这篇文章,领略其提出法则的真谛,探讨它如何帮助我们对抗软件工程中三大根深蒂固的敌人:对“大泥球”的恐惧、对“简单”的误解,以及对“未来扩展性”的过度痴迷。对于追求务实与高效的Go开发者来说,这套思想武器库,无疑是构建健壮、可维护系统的最佳指南。

核心法则:先深入理解,再做最简单的事

Goedecke的核心论点可以概括为两步:

  1. 花时间去深度理解当前的系统和需求。
  2. 然后,做那件能够解决当前问题的、最简单的事。

这与许多工程师的直觉相悖。我们总是被教导要“高瞻远瞩”,要设计一个能够应对未来各种可能性的“完美”架构。但Goedecke认为,这恰恰是通往失败的错误路径。

让我们以他文中的Go应用场景为例:为一个现有的Go服务添加限速功能。

  • “理想系统”思维:马上想到引入Redis,实现一个精巧的“漏桶算法”,构建一套独立的、可水平扩展的速率限制微服务。这套方案技术上无懈可击,充满了“工程美感”。
  • 作者的“极简工作法”思维
    1. 最简单的一步是什么? 检查一下我们正在使用的边缘代理(如Nginx, Envoy)是否已经内置了速率限制功能?也许只需要几行配置就能解决问题。
    2. 如果不行,次简单的一步是什么? 能否在应用内存中维护一个计数器?“可是重启会丢失数据!”——那么,丢失这些数据对业务的实际影响是什么?真的那么致命吗?
    3. 如果还不行,再下一步呢? 如果数据不能丢失,且服务是多实例部署,那么引入外部依赖(如Redis)才成为那个“能奏效的最简单的事”。

这个思考过程的精髓在于,它强迫我们不断地质问自己:“真的有必要吗?” 直到我们确认,更复杂的方案是解决当前真实存在的约束的唯一途径时,才去实施它。这正是极限编程中“YAGNI”(You Ain’t Gonna Need It)原则的终极体现。

“简单”的真谛:少即是多,平庸即伟大

一个普遍的现象是,初级工程师热衷于使用他们新学会的各种工具——数据库、缓存、消息队列、代理——来构建复杂的系统,并在白板上画出纵横交错的箭头,这让他们感觉像在做“真正的工程”。

然而,软件设计的精髓,如同武学大师的境界,在于学习何时做得更少,而非更多

Goedecke指出,伟大的软件设计往往看起来平庸无奇(underwhelming)。它不会让你惊叹于其复杂精巧的结构。相反,当你面对一个伟大的设计时,你通常会感到惊讶:“哦,原来这个问题这么简单?”或者“太好了,我们实际上并不需要做那些复杂的事情。”

Unicorn web服务器是伟大的设计,因为它利用了Unix进程这一极其“无聊”但无比可靠的原语,就解决了请求隔离、水平扩展和崩溃恢复等核心问题。标准的Rails REST API是伟大的设计,因为它用最枯燥的方式,完美地满足了CRUD应用的需求。

对三大反对意见的深刻辩驳

当然,“做最简单的事”这一法则总会面临三个经典的质疑。Goedecke对这些质疑的回应,构成了文章最精彩的部分。

1. 反对意见一:“这难道不会导致‘大泥球’(Big Ball of Mud)吗?”

“做最简单的事”听起来像是鼓励走捷径、写“hack代码”。我们都见过那种由无数“hack”堆砌而成的、无法维护的“大泥球”系统。

Goedecke的反驳: “Hack”代码根本不简单!
- “Hack”只是“更容易想到”:一个临时的补丁或权宜之计,通常是我们能最快想到的方案,但这并不意味着它是最简单的。
- “Hack”增加了系统的认知负荷:每一个“hack”都为代码库引入了一个需要被“特殊记忆”的例外。随着“hack”的增多,系统的整体复杂性是在增加,而非减少。
- 真正的“简单”方案需要深度思考:找到一个正确的、优雅的修复方案,往往需要对系统有更深入的理解,并探索多种可能性。这个“正确的修复”通常比“hack”本身要简单得多。

结论:做“最简单的事”不是放弃工程,恰恰相反,它要求我们投入更多的精力去做真正的工程设计,以找到那个最根本、最简洁的解决方案,而不是用一个又一个复杂的“补丁”去掩盖问题。

2. 反对意见二:“‘简单’的定义是什么?这难道不是一个空洞的同义反复吗?”

如果“最简单”就等同于“好设计”,那么“做最简单的事”不就等于说“做好设计”这句废话吗?

Goedecke借鉴了Rich Hickey在著名演讲《Simple Made Easy》中的思想,对”简单“给出了一个直观的定义:

  1. 更少的“活动部件”:一个简单的系统,是你需要同时思考的东西更少的系统。
  2. 更低的内部耦合:一个简单的系统,是由具有清晰、直接接口的组件构成的。

基于这个定义,他给出了一个实用的决断法则简单的系统更加稳定。

如果在两个方案之间抉择,一个方案在需求不变的情况下需要持续的维护、监控和干预(比如部署和维护一个Redis集群),而另一个则不需要,那么后者就是更简单的。

因此,对于Go的速率限制例子,内存方案比Redis方案更简单,因为它减少了外部依赖、监控需求和部署复杂性这些“活动部件”。

3. 反对意见三:“难道我们不应该构建可扩展(scalable)的系统吗?”

这是来自大型科技公司工程师最常见的呐喊:“内存限流根本无法扩展!”

Goedecke的反驳: 对“扩展性”的痴迷,是SaaS工程领域最大的原罪。

  • 过早的扩展性设计通常是无效的:你无法准确预测一个非凡系统在流量增长几个数量级后,瓶颈会出现在哪里。为遥远的未来进行过度设计,往往是在解决一个根本不存在或被错误预测的问题。
  • 过早的扩展性设计会使代码库僵化:为了所谓的“独立扩展”,你可能会过早地将一个单体服务拆分为多个微服务。这引入了网络通信、分布式事务等一系列极其困难的工程问题,使得实现某些功能变得异常艰难。“我见过很多次这种拆分,但真正从中受益的,可能只有一次。”
  • 务实的扩展策略:最多为当前流量的2倍或5倍做准备。然后,保持系统的简单和灵活,以便在真正的瓶颈出现时,能够快速地识别和解决它。

在Go社区,我们经常看到关于“单体 vs 微服务”的讨论。Goedecke的观点为我们提供了清晰的指引:保持单体的简单性,直到拆分的必要性变得无可辩驳。 一个设计良好、简单的Go单体应用,其扩展能力远超大多数人的想象。

小结:拥抱当下,而不是预测未来

Goedecke在文末总结道,软件开发有两种基本方式:

  1. 预测未来:预测半年或一年后的需求,并为此设计一个“最佳”系统。
  2. 拥抱现在:为当前已知的、真实的需求,设计一个“最佳”系统。

他悲观地认为,我们人类作为一个集体,预测系统未来走向的能力非常有限。我们甚至很难完全理解一个系统当前的状态。因此,第一种方式往往导致糟糕的设计。

唯一的理性选择是第二种:做那个可能奏效的、最简单的事

这要求我们放弃作为工程师的某种虚荣心,不再追求构建那些看起来“令人印象深刻”的复杂系统。相反,我们应该拥抱“无聊”,致力于创造那些看似平庸,却异常健壮、稳定且易于理解和修改的系统。

这,或许就是从“优秀”走向“卓越”的工程师,其设计哲学的终极奥义。


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

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


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

© 2025, bigwhite. 版权所有.