本文永久链接 – https://tonybai.com/2025/09/07/the-power-of-an-interface-for-performance 我的《Go语言第一课》已上市,赠书活动正在进行中,欢迎点击此链接参与。 大家好,我是Tony Bai。 我们通常如何看待性能优化?答案往往是:更快的算法、更少的内存分配、更底层的并发原语、甚至用SIMD指令压榨CPU的每一个周期。我们痴迷于“引擎盖之下”的实现细节,坚信更好的代码和更强的硬件能带来更高的性能。 然而,TigerBeetle数据库创始人Joran Dirk Greef在Strange Loop上的一场精彩的演讲(https://www.youtube.com/watch?v=yKgfk8lTQuE),用一场耗资百万美元的数据库比赛,颠覆了这一传统认知。他通过无可辩驳的基准测试数据证明:在分布式系统中,接口(Interface)的设计,而非代码实现或硬件堆砌,才是决定性能上限的真正瓶颈。 在深入探讨之前,我们必须对本文的“接口”一词进行关键澄清。对于Go开发者而言,“接口”通常指代语言层面的interface类型,一种实现行为契约以及多态的工具。但本文中所说的“接口”,则是一个更宏观、更广义的概念,它指的是系统与系统之间、或用户与系统之间进行通信的交互模式、契约与协议。你的REST API设计、gRPC的.proto文件、微服务间的调用时序,都属于这个“广义接口”的范畴。 这场演讲虽然以数据库为载体,但其揭示的“接口即天花板”的原理,对于每一位设计和使用Go API、微服务的工程师来说,都无异于一声惊雷。它迫使我们重新审视,我们日常构建的系统,是否在设计之初,就已为自己埋下了无法逾越的性能枷锁。 赛场设定:一场关于“转账”的终极对决 Greef的实验设计极其巧妙,他回归了OLTP(在线事务处理)的本质,重拾了图灵奖得主Jim Gray定义的最小交易单元:“借贷记”(Debit-Credit),即我们熟知的“转账”操作。 这个工作负载的核心是:在两个账户之间转移价值,并记录一笔历史。它的关键挑战在于竞争(Contention)。在高流量的真实世界系统中,总会有大量的交易集中在少数“热门”账户上,这就是帕累托法则(80/20原则)的体现。 传统接口:交互式事务 大多数通用数据库处理这种事务的标准接口是“交互式”的,即一个业务操作需要多次网络往返才能完成: 1. 第一步(读):客户端发起一个网络请求,SELECT Alice和Bob的账户余额。 2. 第二步(计算):数据返回到客户端,应用代码在本地检查余额是否充足。 3. 第三步(写):客户端发起第二个网络请求,在一个事务中UPDATE两个账户的余额,并INSERT一条转账记录。 这个看似天经地义的流程,隐藏着一个致命的缺陷。 百万美元的“滑铁卢”:当硬件和实现都失灵 Greef设立了三组“选手”来进行一场性能对决: Postgres (单机): 经典的、备受尊重的开源数据库。 “迈凯伦” (16节点集群): 一个匿名的、顶级的云原生分布式数据库,年费超过一百万美元。 TigerBeetle: Greef自己设计的、专为OLTP优化的新一代数据库。 比赛结果令人瞠目结舌: 在零竞争下,“迈凯伦”集群的性能甚至不如单机Postgres。 随着竞争率提升,16台机器的“迈凯伦”性能暴跌,甚至出现了节点越少、性能越高的荒谬情况。 在整个高竞争测试期间,这百万美元硬件的CPU利用率从未超过12%。 为什么? 硬件在空转,代码在等待。钱,并没有买来性能。 性能的枷锁:跨网络持有锁 问题的根源,就出在那个“交互式事务”的接口设计上。 当一个事务开始时,数据库为了保证ACID,必须锁定被操作的行。在这个接口模型中,锁的持有时间 = 数据库处理时间 + 两次网络往返(RTT)的时间 + 客户端应用的处理时间。 Greef指出,数据库内部的处理时间可能是微秒级的,但一次跨数据中心的网络往返,轻易就是几十甚至上百毫秒。这意味着,数据库中最宝贵的锁资源,其生命周期被廉价且缓慢的网络I/O牢牢绑架了。 [...]
本文永久链接 – https://tonybai.com/2025/09/06/gopher-pseudocode-translation-guide 大家好,我是Tony Bai。 你是否曾在阅读顶会论文时,感觉其中的算法描述像一本晦涩难懂的“天书”?那些看不太懂的数学符号、奇特的箭头和看似代码又无法编译的语句(如下图),是不是常常让你望而却步,感叹理论与实践之间隔着一道鸿沟? 别担心,这不是你的问题。你遇到的正是连接学术象牙塔与工程世界的桥梁——伪代码 (Pseudocode)。它并非一门具体的编程语言,而是算法世界的“通用语” (Lingua Franca),旨在剥离所有语言特定的语法噪音,只保留逻辑的纯粹核心。 对于我们工程师而言,掌握伪代码的阅读技巧,就像是学会了一门新的“翻译”艺术。这门艺术能让你直接与算法设计者的思想对话,将世界上最聪明的头脑的智慧,转化为你手中坚实、高效的主流编程语言的代码。 本文就是你的学术伪代码“翻译”指南。我们将从最基础的符号“字母表”开始,探索不同风格的伪代码“文体”,通过完整的“翻译”实战,让你最终不仅能读懂,更能欣赏伪代码之美,并自信地将任何算法“天书”都转化为优雅的编程语言实现(本文将以Go语言为例)。 罗塞塔石碑 —— 破译伪代码的核心符号 任何翻译工作都始于对基本词汇的掌握。伪代码的符号系统虽然看似五花八门,但其核心元素却非常稳定。让我们一同来构建我们的“罗塞塔石碑”,将最常见的伪代码符号与 Go 语言进行映射。 1. 赋值操作: ← (The Left Arrow) 这是伪代码最具标志性的符号,代表赋值。 伪代码: max_val ← A[0] i ← 1 为什么用 ← 而不是 =? 为了在学术上严格区分赋值 (Assignment) 和 相等判断 (Equality Test)。在算法和数学语境中,= 通常是逻辑断言,表示“等于”。← 则清晰地表达了“将右边的值赋予左边变量”这一动作,杜绝了歧义。 Go “译文”: maxVal := A[0] // 声明并赋值 i := 1 // [...]
本文永久链接 – https://tonybai.com/2025/09/05/go-proxy-revise-background-refresh-pacing 大家好,我是Tony Bai。 2025年8月14日,Go开发者Ted Unangst发表了一篇措辞犀利的博文——《What is the go proxy even doing?》。他用服务器日志作为证据,公开质疑Go官方模块代理(proxy.golang.org)对其个人代码托管服务humungus.tedunangst.com产生了“洪水般”的、看似毫无意义的巨大流量。这个事件迅速在社区发酵,将一个通常在后台默默工作的核心基础设施,推上了风口浪尖。当然在我的印象中,这已经不是Go社区第一次“抱怨” 官方Go proxy的“诡异”行为给一些小型站点带来的烦恼了。 不过不同的是,这次Go团队的前技术leader、核心成员Russ Cox (rsc) 迅速响应,在Go的官方issue追踪系统中创建了两个关键问题(#75120 和 #75191),不仅承诺调查并解决问题,更罕见地、极其详尽地公开了Go Module Proxy的内部工作原理、缓存策略以及导致此次事件的深层原因。 这场由一篇博文引发的“悬案”及其官方复盘,为我们提供了一个绝佳的机会,去深入理解Go Module Proxy这个我们每天都在使用,却又知之甚少的系统。它背后的“背景刷新”机制,究竟是为了提升开发者体验的“优化”,还是在某些边缘情况下会演变成对小型开源社区的“DDoS”? 事件回顾:来自小型服务器的“呐喊” Ted Unangst的博文主要控诉了以下几个现象: 持续的背景流量:即使没有任何新版本发布,proxy.golang.org也会以几分钟一次的频率,持续尝试从他的服务器hg clone(克隆)多个仓库。由于他的服务器设置了24小时内只允许一次克隆的速率限制,这些请求大多被429 Too Many Requests拒绝,但在日志中形成了持续的“背景辐射”。 “惊群效应”(Thundering Herd):当他推送一个新版本(一个新tag)并本地执行go mod tidy后,短短14秒内,他的服务器就遭到了来自Google不同IP地址的、数十个并发的hg clone请求。他将其形容为“洪水来了”。 低效的拉取策略:Proxy每次都执行完整的hg clone,而不是更高效的hg pull,这对于非Git的VCS(版本控制系统)来说,意味着巨大的带宽浪费。 Unangst的质疑直击要害:“为什么你们要这样构建一个分布式系统?……难道Google认为从我的服务器下载比从他们自己的云存储下载更便宜吗?” Go官方的深度复盘:揭开代理的神秘面纱 Russ Cox的官方回应堪称透明沟通的典范。他不仅承认了问题的存在,还详细解释了Proxy的设计理念和实现细节,让我们得以一窥其内部运作。 Go Module Proxy的核心目标 可用性与可靠性:作为Go生态的中央缓存,确保开发者在任何上游代码仓库宕机时,依然能获取到模块。 降低延迟:通过主动的背景刷新,提前将热门或近期被访问过的模块信息更新到缓存中,使得开发者在执行go get等命令时,能立即获得响应,而不是等待Proxy实时回源。 缓存与刷新策略的权衡 Proxy缓存多种类型的数据,每种都有不同的刷新策略,而这些策略正是问题的根源: 模块Zip包: [...]
本文永久链接 – 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) [...]
本文永久链接 – 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 [...]
本文永久链接 – 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. 版权所有.
本文永久链接 – 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 [...]
本文永久链接 – 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)。像侦探一样,从一个简单的 [...]
本文永久链接 – 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的核心论点可以概括为两步: 花时间去深度理解当前的系统和需求。 然后,做那件能够解决当前问题的、最简单的事。 这与许多工程师的直觉相悖。我们总是被教导要“高瞻远瞩”,要设计一个能够应对未来各种可能性的“完美”架构。但Goedecke认为,这恰恰是通往失败的错误路径。 让我们以他文中的Go应用场景为例:为一个现有的Go服务添加限速功能。 “理想系统”思维:马上想到引入Redis,实现一个精巧的“漏桶算法”,构建一套独立的、可水平扩展的速率限制微服务。这套方案技术上无懈可击,充满了“工程美感”。 作者的“极简工作法”思维: 最简单的一步是什么? 检查一下我们正在使用的边缘代理(如Nginx, Envoy)是否已经内置了速率限制功能?也许只需要几行配置就能解决问题。 如果不行,次简单的一步是什么? 能否在应用内存中维护一个计数器?“可是重启会丢失数据!”——那么,丢失这些数据对业务的实际影响是什么?真的那么致命吗? 如果还不行,再下一步呢? 如果数据不能丢失,且服务是多实例部署,那么引入外部依赖(如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”代码根本不简单! [...]
本文永久链接 – https://tonybai.com/2025/08/30/python-an-origin-story 大家好,我是Tony Bai。 在编程语言的星空中,很少有哪颗星像Python一样,以如此温和而坚定的姿态,从一个不起眼的个人项目,成长为照亮地球未来的科技灯塔。如今,当我们谈论数据科学、人工智能时,Python几乎是绕不开的默认选项。但这一切的起点,竟源于一位程序员在阿姆斯特丹的圣诞假期里,为了“打发时间”而开始的一个“私活”项目。 近期,一部名为《Python: The Documentary》的纪录片,通过对创始人Guido van Rossum及众多早期核心贡献者的访谈,为我们揭开了这段波澜壮阔的起源故事。这既是一部技术编年史,更是一部关于设计哲学、社区力量、时代机遇与人性光辉的启示录。 本文将沿着这部纪录片的脉络,和大家一起穿越回那个个人电脑尚不普及、Usenet是唯一交流渠道的年代,探寻和复盘Python成功的真正基因。 孕育:一次对“专家思维”的反叛 故事始于80年代的荷兰CWI(国家数学和计算机科学研究中心),一个诞生了Algol系列语言的学术圣地。当时,编程语言的设计理念被一种根深蒂固的经济关系所主导:计算机极其昂贵,而程序员相对廉价。因此,语言被设计得尽可能贴近硬件,以榨干机器的每一分性能,至于程序员需要花费多少时间去理解和编写,则在其次。 正是在这种背景下,CWI的ABC语言项目应运而生。项目成员Lambert Meertens在尝试向艺术家教授编程时发现,现有的语言充满了对底层硬件知识的隐式要求,这对非技术背景的人来说是一堵无法逾越的高墙。ABC的目标,就是创造一门“易学、易教、易用”的语言,让初学者无需关心“那些凌乱的硬件细节”。 Guido van Rossum,当时还是一名年轻的程序员,被雇佣来将ABC的原型实现为一个完整的系统。他在这门语言上投入了三年半的心血。然而,ABC生不逢时。在那个没有互联网的年代,分发软件需要邮寄软盘,ABC几乎没有触及到它的目标受众,并最终被CWI高层扼杀。 这次失败的经历,却在Guido心中埋下了一颗种子。 诞生:在C与Shell之间的“甜蜜点” 项目被砍后,Guido被调往一个名为Amoeba的分布式操作系统项目。他的任务是为这个系统编写大量的用户应用程序。他很快发现,使用C语言来完成这些任务极其痛苦和低效,而使用Shell脚本又缺乏足够的编程能力。 “天啊,如果我们能用ABC来写这些工具就好了,”Guido回忆道,“每个工具可能只需要半页代码,我几周就能搞定,而不是看起来要花上好几年。” 但他同样意识到,ABC过于抽象,它刻意回避了与文件系统、进程等操作系统底层交互的能力。此时,一个清晰的需求浮现了:市场需要一种能够弥合C语言的强大能力与Shell脚本的易用性之间鸿沟的语言。 当时,Perl是这个生态位的有力竞争者,但Guido和他的同事们并不欣赏它。“我们觉得它作为一门编程语言并不好,几乎和Basic一样糟糕,只是糟糕的方式不同。” 正是在这种“一个能打的都没有”的背景下,Guido做出了一个改变历史的决定。 1989年的圣诞假期,他没有选择休息,而是开始了自己的“私活”项目:创造一门属于自己的编程语言。 设计哲学:“禅”的雏形 Guido的新语言,自然地继承了ABC的许多思想遗产,其中最著名、也最具争议的,就是使用缩进来组织代码块。但他同样抛弃了ABC中那些他认为不切实际的部分,朝着更务实的方向前进。 更重要的是,Python从诞生之初就树立了一种与当时主流脚本语言Perl截然相反的哲学。Perl的座右铭是“条条大路通罗马”(There’s more than one way to do it),鼓励天马行空的语法和技巧。而Python则悄然确立了自己的核心原则:“应该有且最好只有一种显而易见的方法来做事”(There should be one– and preferably only one –obvious way to do it)。 这种对明确性(explicitness)和可读性(readability)的极致追求,后来被社区核心成员Tim Peters总结为著名的《The Zen of Python》。当人们困惑于Python的设计哲学时,Tim Peters用诗一般的语言给出了答案: [...]
本文永久链接 – https://tonybai.com/2025/08/29/good-api-design 大家好,我是Tony Bai。 在解读《Everything I know about good system design》一文时,我们曾提炼出一个核心观点:“无聊即可靠”。这个看似反直觉的法则,在追求创新与复杂的软件工程世界里,如同一股清流。现在,这个“无聊”哲学将从宏观的系统设计,延伸至微观但至关重要的领域——API设计。 Sean Goedecke在其后续力作《Everything I know about good API design》中,再次强调了这一理念。他认为,一个伟大的API,必然是“无聊”的。它不应追求新奇或有趣,而应像一把用了多年的锤子,让使用者拿起就能用,无需思考。 对于身处云原生和微服务浪潮之巅的Go开发者而言,API是我们日常呼吸的空气。本文将再次进入Goedecke的思想空间,学习他的API设计精髓,并将其提炼为九条具体的、可操作的法则。我们将探讨,如何通过拥抱“无聊”,在开发者熟悉性与系统灵活性之间找到完美平衡,构建出真正经得起时间考验的Go API。 法则一:追求无聊,API是工具而非产品 对于API的构建者,API是倾注心血的产品;但对于消费者(也就是开发者)而言,API纯粹是工具。他们在乎的是如何用最少的心智负担,最快地实现目标。任何让他们停下来思考“这个API为什么这么设计?”的时间,都是浪费。 一个伟大的API,必然是“无聊”的。 它的设计应该如此符合行业惯例和直觉,以至于开发者在阅读文档前就能猜到十之八九。 如果是在Go的世界里,这意味着: RESTful: 遵循HTTP方法论。GET用于检索,POST用于创建,PUT/PATCH用于更新,DELETE用于删除。 命名一致: 在JSON payload中全局统一使用snake_case或camelCase。 结构可预测: 错误响应体遵循统一结构,如{“error”: {“code”: “invalid_argument”, “message”: “user_id cannot be empty”}}。 当你的API“无聊”到开发者可以几乎不假思索地使用时,你就为他们提供了最高效的工具。 法则二:视兼容性为生命,“绝不破坏用户空间” Linus Torvalds的名言“我们绝不破坏用户空间”是API维护者的最高信条。API一旦发布,就如同一份公开签订的契约,你对所有下游消费者负有神圣的责任:避免伤害他们。 破坏性变更(Breaking Change)是API的原罪,包括但不限于: 删除或重命名字段 修改字段类型 (int -> string) 重构JSON结构 (user.address -> [...]
本文永久链接 – https://tonybai.com/2025/08/28/go-primer-published 大家好,我是Tony Bai。 前不久,在知乎上看到一个关于 Go 社区的帖子,其中一条评论让我感慨良多: “GopherChina 都没了,国内还有几人坚持?Tony Bai好像还在更新” 短短一句话,道尽了社区的变迁与坚持的不易。这句来自读者的回答,让我内心欣慰,也让我有机会停下来,审视自己在这条路上走了多远,以及为什么还要继续走下去。 答案或许很简单,就是三个字:长期主义。 我的个人博客 tonybai.com,从 2004 年断断续续更新至今,已经走过了二十个年头。而我在 Go 语言这条路上的“长期主义”,则始于 2011 年。那时,Go 尚处襁褓,在国内几乎无人问津。我凭借着一股直觉和热爱,一头扎了进去,成为了国内最早一批的 Go 语言探索者。 十余年来,这份坚持从未间断。从早期的博客分享,到后来出版的《Go语言精进之路》;从 GopherChina 大会的讲台,到几乎每日更新的 GopherDaily,我一直在尽我所能地为社区贡献。 这份坚持也延续到了今年。从年初开始,我在公众号上陆续推出了多个“微专栏”系列,深入探讨 Go 源码与实践的细节;与此同时,我的新课程《Go语言进阶课》也已在极客时间上线,希望能带领大家向更深层次迈进。 布道,其实是一件极具价值的事情——传递自己的观点,影响一群人,做成一件事。 今天,我的这份“长期主义”清单上,又将增添新的一项。我想借此机会,向一直支持我的朋友们,正式宣布一个喜讯。 官宣喜讯:历时一年半,2.4w 人订阅的《Go语言第一课》成书! 四年前,我在极客时间开设了专栏《Go语言第一课》。令我欣慰的是,这个专栏得到了广大 Gopher 的认可和喜爱。截至今日,它已经影响了超过 2.4 万名订阅者(截至2025.8),在编程语言类专栏里取得了相当不错的成绩。 为了让这份经过市场检验的优质内容,能以一种更经典、更触手可及的方式,帮助更多人踏入 Go 语言的大门,我与人民邮电出版社异步图书合作,历时一年多的精心打磨,终于将它变成了纸质书 — 我的第二本“小黄书”: 我必须强调,这本书并非专栏的简单复制。在近一年多的时间里,我倾注了大量心血,进行了一次彻底的精修与增补: 内容与时俱进:全书内容与最新的 Go 1.24 版本 同步(注:交稿时的最新版本为Go 1.24),确保知识的前沿性与准确性。 知识体系更完整:我特别补充和深化了专栏中因篇幅所限未能详尽展开的关键内容,如指针类型的深入探讨、测试的最佳实践、以及泛型的全面讲解,使其作为一本入门读物更加系统和完备。 全面精炼与优化:基于三年来数万读者的宝贵反馈,我对全书的结构、文字表述、示例代码和图示进行了地毯式的优化,力求为读者提供“保姆级”的丝滑阅读体验。 为了让大家更直观地感受这本书是如何从“道”到“术”,构建一个完整而系统的知识体系的,我在这里分享本书的核心目录结构: [...]
本文永久链接 – https://tonybai.com/2025/08/27/go-interface-embrace-data 大家好,我是Tony Bai。 在 Go 语言的世界里,接口(interface)一直被视为其设计哲学的基石之一——它只关心一个类型能做什么(行为),而不关心它是什么(结构)。这种基于方法集的鸭子类型,赋予了 Go 独一无二的灵活性和解耦能力。然而,随着 Go 1.18 泛型的到来,一个深刻的问题被摆上了台面:当我们需要编写对数据的结构而非行为具有通用性的代码时,现有的约束机制是否足够? GitHub 上的 Issue #51259,“proposal: spec: support for struct members in interface/constraint syntax”,正是这场“灵魂拷问”的中心。它提出的一个看似简单的想法——让接口能够描述结构体字段——却引发了一场关于 Go 语言核心哲学的深度辩论:我们是应该坚守“行为至上”的纯粹性,还是应该拥抱一个更务实的、能感知数据结构的泛型系统? 在这篇文章中,我就和大家一起来看看Go社区和Go团队关注这个提案的讨论过程,以及基于当前现状的临时决议。 问题的根源:当泛型遇到结构 想象一下这个常见的场景:你需要编写一个通用的函数,来处理一组具有共同字段的结构体,比如各种类型的 Kubernetes 资源,它们都内嵌了 metav1.ObjectMeta 和 metav1.TypeMeta。或者,在图形学应用中,你需要处理多种都包含 X、Y 字段的 Point 结构。 在 Go 1.18 之后,我们很自然地会想到使用类型联合(union)来约束泛型函数: type Point2D struct { X, Y float64 } type Point3D struct { [...]
本文永久链接 – https://tonybai.com/2025/08/26/good-system-design 大家好,我是Tony Bai。 在技术圈,我们常常被各种“炫技式”的系统设计建议所包围。从入门级的“你一定没听说过队列吧?”到专家级的“在数据库里存布尔值简直是灾难”,这些建议要么过于肤浅,要么过于精巧,往往脱离了大多数工程实践的真实上下文。就连《设计数据密集型应用》这样的经典之作,虽然深刻,却也可能与我们日常面对的大多数问题有些距离。 那么,究竟什么是好的系统设计?如果说软件设计是如何组织代码,那么系统设计就是如何组织服务。其基本元素不再是变量和函数,而是应用服务器、数据库、缓存、队列、事件总线和代理。 近日,一篇名为《我所知道的关于优秀系统设计的一切》的文章在工程师社群中引发了广泛共鸣。其核心观点让人耳目一新:优秀的系统设计,往往看起来平平无奇,甚至有些“无聊”。这种“无聊”,恰恰是系统长期稳定、易于维护的标志。 在本文中,我就和大家一起深入这篇文章的核心思想,看看这位作者所说的“无聊即可靠”的系统设计法则究竟都是哪些! 识别优秀设计:于无声处听惊雷 优秀的设计是什么样的?它往往是“无感的”。当你发现一个功能实现起来比预想的要简单,或者你几乎从不需要去关心系统的某个部分,因为它总是在默默地、可靠地工作时,你就身处一个优秀的设计之中。 这引出了一个悖论:优秀的设计是自我掩饰的,而糟糕的设计往往看起来更令人印象深刻。一个充斥着分布式共识、多种事件驱动模式、CQRS 等“高级”概念的系统,常常让我们心生警惕。这背后,要么是为了弥补某个根本性的错误决策,要么就是赤裸裸的过度设计。 许多工程师看到复杂的系统,会惊叹:“这里发生了好多系统设计!” 但事实恰恰相反,复杂通常是优秀设计缺位的体现。当然,有些系统的复杂性是业务本身带来的,它们不可避免。但一个能正常工作的复杂系统,永远是从一个能正常工作的简单系统演化而来的。从零开始构建一个复杂系统,几乎注定会走向失败。 这与 Go 语言的哲学高度契合。Go 本身就是一门“无聊”的语言,它刻意回避了许多其他语言中的复杂特性,以换取无与伦比的简洁性、可靠性和工程效率。同样,优秀的 Go 系统设计,也应该追求这种“无聊”的可靠性。 状态与无状态:系统设计的核心难题 软件工程中最困难的部分,永远是状态管理。只要你需要在任何时间段内存储任何信息,一系列棘手的决策就会接踵而至。相反,不存储任何信息的应用被称为“无状态”的。 法则一:最大限度地减少有状态组件。 虽然我们应该最小化所有组件,但有状态的组件尤其危险,因为它们会进入“坏的状态”。一个无状态的服务(比如 PDF 转 HTML 服务)可以被容器编排系统(如 Kubernetes)轻易地杀死和重启,从而实现故障自愈。但一个有状态的服务(如数据库)却不能。如果数据库中出现一条格式错误的“脏数据”导致应用崩溃,你就必须手动介入修复。 在实践中,这意味着我们应该努力将系统划分成两种角色: 1. 少数的有状态服务:它们负责与数据库等持久化存储打交道,是状态的“守护者”。 2. 大量的无状态服务:它们负责处理业务逻辑、计算等任务,本身不存储任何持久化状态。 要严格避免让五个不同的服务去写同一张数据库表。更好的模式是,让其中四个服务通过 API 请求或发布事件的方式,与那个唯一的“状态守护者”服务通信,将所有写逻辑都封装在守护者服务内部。对于读逻辑,虽然可以稍微放宽,但将读操作也收敛到一个服务中,依然是更优的选择,只是有时为了性能,我们会容忍一些服务直接读取数据库副本。 数据库:状态的心脏 既然状态管理是核心,那么承载状态的数据库自然就是系统的心脏。以下是围绕关系型数据库(如 PostgreSQL)的一些关键实践。 法则二:精心设计“刚刚好”的 Schema 和索引。 Schema 设计:Schema 设计需要在灵活性和规范性之间找到平衡。一旦数据量达到百万级别,修改 Schema 将会是一场噩梦。但如果过度追求灵活性,例如将所有数据都塞进一个 JSON 字段,或者使用 EAV(实体-属性-值)模型,虽然初期开发快,但会将巨大的复杂性和潜在的性能问题转移到应用层代码中。一个好的标准是:你的表结构应该能让一个新人大致读懂应用的业务模型。 索引:索引是数据库性能的命脉。要根据最常见的查询模式来创建索引。例如,如果你经常按 WHERE user_id [...]
本文永久链接 – https://tonybai.com/2025/08/26/go-concurrency-cost-hierarchy 大家好,我是Tony Bai。 Go语言的并发模型以其简洁直观著称,但这种简单性背后,隐藏着一个跨越五个数量级的巨大性能鸿沟。当你的高并发服务遭遇性能瓶颈时,你是否也曾陷入“性能猜谜”的困境:是sync.Mutex太慢?是atomic操作不够快?还是某个channel的阻塞超出了预期?我们往往依赖直觉和pprof的零散线索,却缺乏一个系统性的框架来指导我们的判断。 最近,我读到一篇5年前的,名为《A Concurrency Cost Hierarchy》的C++性能分析文章,该文通过精妙的实验,为并发操作的性能成本划分了六个清晰的、成本呈数量级递增的层级。这个模型如同一份性能地图,为我们提供了告别猜谜、走向系统化优化的钥匙。 本文将这一强大的“并发成本层级”模型完整地移植并适配到Go语言的语境中,通过一系列完整、可复现的Go基准测试代码,为你打造一份专属Gopher的“并发成本清单”。读完本文,你将能清晰地识别出你的代码位于哪个性能层级,理解其背后的成本根源,并找到通往更高性能层级的明确路径。 注:Go运行时和调度器的精妙之处,使得简单的按原文的模型套用变得不准确,本文将以真实的Go benchmark数据为基础。 基准测试环境与问题设定 为了具象化地衡量不同并发策略的成本,我们将贯穿使用一个简单而经典的问题:在多个Goroutine之间安全地对一个64位整型计数器进行递增操作。 我们将所有实现都遵循一个通用接口,并使用Go内置的testing包进行基准测试。这能让我们在统一的环境下,对不同策略进行公平的性能比较。 下面便是包含了通用接口的基准测试代码文件main_test.go,你可以将以下所有代码片段整合到该文件中,然后通过go test -bench=. -benchmem命令来亲自运行和验证这些性能测试。 // main_test.go package concurrency_levels import ( "math/rand" "runtime" "sync" "sync/atomic" "testing" ) // Counter 是我们将要实现的各种并发计数器的通用接口 type Counter interface { Inc() Value() int64 } // benchmark an implementation of the Counter interface func benchmark(b *testing.B, c [...]
本文永久链接 – https://tonybai.com/2025/08/25/documents-the-architects-programming-language 大家好,我是Tony Bai。 从初级到高级,开发者的职业路径通常是清晰的:写出更好的代码。但当站在高级工程师的十字路口,是转向管理还是深入技术成为架构师?许多人选择了后者,却发现这个角色的定义模糊不清。最近,stackoverflow的一篇精彩的博客文章《文档:架构师的编程语言》提出了一个深刻的洞见:高级开发者将代码部署到代码构成的系统中,而架构师将想法部署到人构成的系统中。 本文将和大家一起来学习一下文章中的观点和方法,并探讨为何高效的文档写作,是工程师实现这一关键角色转变的核心技能。 架构师之路:一个定义模糊的岔路口 对于许多热爱技术的资深工程师来说,放弃编码转向管理岗是一个艰难的抉择。架构师(Architect)或首席工程师(Principal Engineer)的职业路径,似乎提供了一个两全其美的方案:既能继续深入技术,又能扩大个人影响力。 然而,架构师的角色究竟与高级开发者有何不同?毕竟,他们看起来都在做相似的事情:写代码、审查 PR、讨论部署流水线。文章作者一针见血地指出了核心区别: 高级开发者知道如何将代码部署到由代码构成的系统中。 架构师知道如何将想法部署到由人构成的系统中。 这并非一句空洞的比喻。它意味着架构师的核心工作,是超越单纯的代码实现,去解决那些真正阻碍项目前进的、更大的“人的问题”:沟通、说服和决策。 文档:部署“想法”的“基础设施即代码” 在软件世界里,我们无法仅仅通过一次 git push 就启动一个跨越数月的大型项目、重写一个核心服务,或者为一个新产品选定技术栈。这些重大决策需要跨团队、跨职能的协作、输入和共识。 那么,我们如何可靠地、可重复地将一个复杂的“技术想法”部署到由不同观点、不同背景的人组成的组织中呢?作者给出的答案是:文档。 Confluence, Google Docs, Notion… 这些工具就是架构师的“部署平台”。一篇精心撰写的文档,是推动想法落地最有效的“传输协议”和“基础设施即代码”。它能: 异步地将你的想法传递给所有利益相关者。 结构化地呈现问题背景、方案和权衡。 持久化地记录决策过程,供未来追溯。 最高效地利用关键人物(通常是最忙碌的人)的碎片化时间。 优秀技术文档的原则与技巧 许多程序员对写作感到畏惧,认为其主观且难以掌握。但文章指出,编写优秀的技术文档并不需要文学天赋,只需要掌握几个简单的技巧。 技术文档宣言 作者提出了一个类似“敏捷宣言”的文档价值观: 随时记下东西 胜过 担心如何组织它们 文档化的文化 胜过 走过场的行为 思考什么才重要 胜过 使用模板 某个时间点的文档 胜过 持续更新 核心思想是:先写下来,再求完美。 与其纠结于完美的格式,不如先把你知道的记录下来。 两个魔法技巧:要点和标题 要点 (Bullet Points):这是架构师最好的朋友。它强迫你以结构化、信息密集的方式思考,而不是追求华丽的辞藻。对于读者而言,要点易于快速扫描,能在最短时间内获取核心信息。 标题 (Headers):使用有意义的标题来组织你的要点,就像在编程中将一个大函数重构成多个小函数一样。一个清晰的“上下文(Context)”标题,能迅速帮助读者(包括未来的你)回忆起项目的背景和约束。 文档的生命周期:一次性的“脚本”,而非“活服务” [...]
本文永久链接 – https://tonybai.com/2025/08/25/go-is-still-not-good 大家好,我是Tony Bai。 在技术圈,平静的湖面下往往暗流涌动。对于Go语言社区而言,这股潜藏已久的暗流,被近期的一篇名为《Go is still not good》的博文彻底引爆。作者Thomas Habets,一位自称拥有超过十年Go使用经验的资深开发者,在他的这篇文章中系统性地列举了他眼中Go语言的“七宗罪”。这篇文章迅速登上Hacker News热榜,吸引了超过700条评论,形成了一场规模空前的社区大辩论。 参与者中不乏Go的早期采纳者、贡献者和日常重度使用者。他们争论的焦点,早已超越了语法糖的优劣,直指Go语言最核心的设计哲学——那些曾被誉为“简单”和“务实”的基石,如今在一些开发者眼中,却成了束缚发展、埋下隐患的“原罪”。 在这篇文章中,我就和大家一起跟随这场激辩,逐一剖析这引发轩然大波的“七宗罪”,看看从中能得到哪些有益的启示。 第一宗罪:歧义之空——nil 的双重身份 这是Go语言中最著名的“陷阱”,也是原文作者打响的第一枪。一个持有nil指针的接口变量,其自身并不等于nil。 package main import "fmt" type Error interface { Error() string } type MyError struct{} func (e *MyError) Error() string { return "my error" } func GetError() *MyError { // 假设在某种条件下,我们返回一个 nil 的具体错误类型指针 return nil } func main() { [...]
本文永久链接 – https://tonybai.com/2025/08/24/junior-engineer-survival-guide-in-ai-age 大家好,我是Tony Bai。 这是一个对初级工程师而言,最好也最坏的时代。 说它“最好”,是因为我们从未拥有过如此强大的工具。一名刚走出校门的毕业生,在入职的第一天,就能手握Claude Code、ChatGPT、Gemini Cli、Cursor、Copilot 等强大的 AI 编程助手。面对一个从未接触过的复杂任务——比如,为一个 Go 项目编写一个复杂的 gRPC 中间件——他可能只需要组织几次提示词,一段看起来完美、功能齐全、甚至带着单元测试的代码就诞生了。 那种“我什么都行”的强大感和即时满足感,是十年前的我完全无法想象的。 但说它“最坏”,也恰恰源于此。在这种令人沉醉的“魔幻”体验背后,一个直击灵魂的问题正在浮现: 这种惊人的“效率”,是在加速你的成长,还是在为你铺设一条通往“能力空心化”的捷径? 今天,我想和大家一起聊聊这个话题。这不仅是一份给初级工程师的生存指南,更是我们每一个身处 AI 浪潮中的技术人,都应该进行的深刻反思。 “浅层技能” vs “内功心法”:AI 正在拉开的差距 要理解 AI 带来的潜在风险,我们首先需要区分两种截然不同的技能:“浅层技能”与“内功心法”。 “浅层技能”,关注的是“是什么”(”What”)。在 AI 时代的初期,这主要体现为: 擅长编写精妙的 Prompt。 能快速地从 AI 处获得“能用”的代码片段。 熟练地进行“复制-粘贴-修改”的“胶水”工作。 而如今,随着 Gemini CLI、Claude Code 这类编码智能体(Coding Agent)以及深度集成在 IDE 中的 AI 工具的兴起,这种“浅层技能”又演化出了一个更集成、也更具迷惑性的新形态。 “复制-粘贴”的动作消失了。取而代之的,是 Agent 直接读取你的整个代码库,然后给你一个可以直接应用的变更集(diff)。在这里,“浅层技能”表现为: 将高阶的、模糊的任务指令(‘重构这个文件’、‘修复这个bug’)下发给 Agent。 对 Agent [...]
本文永久链接 – https://tonybai.com/2025/08/23/proposal-errors-asa 大家好,我是Tony Bai。 自 Go 1.13 引入 errors.Is 和 errors.As 以来,Go 语言的错误处理进入了一个结构化、可追溯的新时代。然而,errors.As 的使用方式,对于追求代码简洁与优雅的 Gopher 而言,始终存在一丝“不和谐”:开发者必须预先声明一个目标错误类型的变量,然后将其指针传入函数。 随着 Go 1.18 泛型的正式落地,一个酝酿已久的问题浮出水面:我们能否利用类型参数,彻底重塑这一核心错误检查机制,终结那些恼人的样板代码?GitHub 上的 Issue #51945 正是这场变革的中心舞台。它不仅是一个新函数AsA的提案,更深刻地揭示了 Go 社区是如何在 API 设计、性能、向后兼容性与语言哲学之间反复权衡,以决定 errors.As 的未来。那么,AsA 会是 errors.As 的下一站吗?在这篇文章中,我就和大家一起来看一下Go社区和Go团队针对这一提案的讨论和决策过程。 现状之痛:errors.As 的人体工程学难题 要理解为何需要“重塑”,我们必须先审视 errors.As 带来的便利与痛点,我们先来看一下现状: // Go 1.13 至今的标准模式 err := someOperation() if err != nil { var myErr *MyCustomError if [...]
本文永久链接 – https://tonybai.com/2025/08/22/go-simd-package-preview 大家好,我是Tony Bai。 多年以来,对于追求极致性能的 Go 开发者而言,心中始终有一个“痛点”:当算法需要压榨 CPU 的最后一点性能时,唯一的选择便是“下降”到手写汇编,这让利用 SIMD (Single Instruction, Multiple Data) 指令集提升程序性能这条路显得尤为陡峭难行。 今年6月份,漫长的等待终于迎来了曙光。Go Runtime 负责人 Cherry Mui提出了在Go标准库中增加simd包的官方提案#73787。这才过去两个月左右时间,Cherry Mui就给我们带来惊喜!其主导的SIMD 官方提案迈出了决定性的一步:第一个可供尝鲜的预览版实现已登陆 dev.simd 分支! 这不再是纸上的设计,而是开发者可以立刻下载、编译、运行的真实代码。 这不仅是一个新包的诞生,更预示着 Go 语言在高性能计算领域,即将迈入一个全新的、更加现代化的纪元。本文将带着大家一起深入这个万众期待的 simd 包预览版,从其实现原理到 API 设计,再到上手实战,全方位初探 Go 原生 SIMD 将如何帮助我们解锁 CPU 的终极性能。 什么是 SIMD?为何它如此重要? SIMD,即“单指令多数据流”,是一种并行计算的形式。它的核心思想,是用一条指令同时对多个数据执行相同的操作。 想象一下你有一叠发票需要盖章。传统方式(非 SIMD)是你拿起一枚印章,在一张张发票上依次盖章。而 SIMD 则像是你拥有了一枚巨大的、排列整齐的多头印章,一次下压,就能同时给多张发票盖好章。 在现代 CPU 中,这种能力通过特殊的宽位寄存器(如 128-bit, 256-bit, 512-bit)和专用指令集(如 x86 的 SSE, [...]
本文永久链接 – https://tonybai.com/2025/08/21/go-rust-official-voices 大家好,我是Tony Bai。 最近,在阅读 Rust 核心团队负责人 Niko Matsakis 庆祝十周年的系列博文时,我注意到了一个有趣的现象。我下意识地将他的文字,与我长期关注的 Go语言之父Rob Pike以及Go 团队前技术负责人 Russ Cox 的文章放在一起对比。 这时我发现,两者窗外的风景截然不同。 一边,Niko Matsakis 这样写道: “Graydon(Rust创始人)为我们设定了正确的‘北极星’……‘是的,我们可以拥有好东西’,我常这么想。这句话也捕捉到了 Rust 的另一种特质,那就是试图挑战关于‘权衡’的传统智慧。” 另一边,Russ Cox 在一篇关于 Go 模块依赖的重要文章中,开篇即是: “本文定义了 Go 模块,这是对 go get 命令支持的版本化依赖的提议。这篇文章是七篇文章中的第一篇,描述了一个关于版本化 Go 的全面提案。” 可以看到,一种声音像一位哲学家,在讨论愿景和原则;另一种,则像一位总工程师,直接给出工程计划。 这并非偶然的文笔差异。 一门编程语言核心团队的写作风格,不只是表面的文字选择,而是其设计哲学、治理模式和社区文化的直接反映。 它在很大程度上预示了这门语言的演进方向,以及它最终会吸引哪一类开发者。 今天,我想和你一起分析这两种迥异的“官方之声”,并尝试回答一个核心问题: 在 Rust 的哲学思辨与 Go 的工程决断之间,究竟隐藏着怎样的语言灵魂与未来? Rust 的“探索式叙事”——在复杂世界中寻求赋能 如果你长期阅读 Rust 官方博客或 Niko Matsakis 的个人博客,会发现一种独特的叙事模式:愿景驱动,讨论权衡,社区对话。 [...]
本文永久链接 – https://tonybai.com/2025/08/20/large-scale-logging-made-easy 当日志规模达到 PB 级别,传统的关系型数据库(如 PostgreSQL 或 MySQL)往往力不从心,不仅性能急剧下降,运维成本也变得难以承受。在 FrOSCon 2025 大会上,VictoriaMetrics 的联合创始人兼CTO、fasthttp作者、资深 Go 工程师Aliaksandr Valialkin 发表了题为“大规模日志处理变得简单”的演讲,深入剖析了专为日志设计的数据库如何通过一系列精巧的工程设计,实现单机处理 PB 级数据的惊人性能。 本文将和大家一起听演讲,并了解其分享的核心技术——包括列式存储、时间分区、日志流索引和布隆过滤器——并看看为什么这些技术能将日志查询速度从理论上的 70 小时超大幅缩短至 10 秒,以及为何传统数据库在这场竞赛中注定落败。 什么是“大规模日志”?一个与时俱进的定义 在探讨解决方案之前,演讲者 Aliaksandr Valialkin 首先抛出了一个引人深思的问题:究竟什么是“大规模日志”? 业界通常用每日的数据量来衡量,是 GB、TB 还是 PB?然而,这个定义是浮动的。Aliaksandr 提出了一个更具工程实践意义的定义,它将问题从抽象的数字拉回到了具体的物理约束上: 当你的日志无法装入单台计算机时,它就达到了“大规模”。 这个定义的巧妙之处在于,它将“规模”与具体的硬件能力和软件效率紧密地联系起来。一台搭载着普通硬盘、运行着 PostgreSQL 的服务器,可能在处理每日 GB 级日志时就会捉襟见肘。然而,一台配备了高速 NVMe 硬盘、拥有数百 CPU 核心和 TB 级内存的“巨兽”,在运行像 VictoriaLogs 这样的专用数据库时,其处理能力可能是前者的数千倍。在这种情况下,即便是每日 PB 级的日志,也可能不属于“大规模”的范畴。 这个定义为我们接下来的讨论奠定了基础:在诉诸昂贵且复杂的分布式集群(水平扩展)之前,我们是否已经通过选择正确的工具,充分压榨了单机(垂直扩展)的潜力? 单机处理 PB 级日志:一场从 70 [...]
本文永久链接 – https://tonybai.com/2025/08/18/rust-in-2025 大家好,我是Tony Bai。 2025 年 5 月 15 日,Rust 语言迎来了其 1.0 版本发布的十周年纪念日。这是一个充满里程碑意义的时刻,不仅是对Rust过去十年辉煌成就的回顾,更是展望未来的关键节点。值此之际,Rust 语言团队负责人、核心开发者 Niko Matsakis 发表了一系列题为“Rust in 2025”的纲领性博客文章,系统性地阐述了他个人对 Rust 未来发展的深邃思考。本文将融合 Niko 在十周年庆典上的感言与“Rust 2025”系列的技术蓝图,和大家一起解读一下Niko对下一个时代Rust演进路径的擘画。 回望十年 —— 指引 Rust 航程的两大“北极星” 任何对未来的展望,都必须植根于对过去的深刻理解。在十周年庆典的感言中,Niko Matsakis 将 Rust 的非凡成功,归功于其传奇创始人 Graydon Hoare 从一开始就为这门语言设定的两个坚定不移的“北极星”。它们不仅塑造了 Rust 的技术内核,更铸就了其独特的社区文化。 技术北极星:拒绝妥协,“我们可以拥有好东西” Graydon Hoare 最初为 Rust 设定的目标是“创建一种‘不会吃掉你衣物’的系统编程语言”。这个看似风趣的目标背后,是一种对行业“常识”的根本性挑战。Niko 将其精炼为一句充满信念的口号:“是的,我们可以拥有好东西 (Yes, we can have nice things)”。 这句话的深层含义在于,Rust 拒绝接受在软件开发中长期存在的、看似不可避免的“魔鬼交易”: [...]
本文永久链接 – https://tonybai.com/2025/08/18/ai-app-dev-guide-for-gopher 大家好,我是Tony Bai。 过去两年,人工智能(AI)以前所未有的姿态,从学术的象牙塔走入了软件工程的每一个角落。以大语言模型(LLM)为代表的生成式AI以及智能体AI,正在重塑我们开发、交付甚至构思软件的方式。 作为一个 Gopher,我们习惯于在云原生、微服务的世界里追求极致的性能与简洁。但当我们抬起头,看到 AI 的浪潮席卷而来,看到 Python 生态的繁荣,心中难免会产生疑问: Go 语言在 AI 时代的位置在哪里? 我们现有的技能树,如何与 AI 的新范式结合? 如果现在要入局 AI,一条清晰、高效、不走弯路的学习路径是怎样的? 这篇文章,就是我为你准备的答案。它不是一篇制造焦虑的快餐文,而是一份力求全面、客观、深入的“入局指南”。我们将系统性地梳理 Go 在 AI 时代的定位、生态全景,并为你规划一条从入门到实践的完整路径。 如果你准备好了,就请泡上一杯咖啡,让我们开始这次深度探索。 战略定位:Go 在 AI 应用开发中的“生态位” 首先,我们必须清晰地认识到,在 AI 领域,不同的编程语言扮演着不同的角色。Go 的核心价值不在于“模型研究”,而在于“模型能力的工程化与产品化”。 当一个强大的预训练模型(如 GPT-5、Claude Opus 4.1或Google Gemini 2.5 Pro)通过 API 暴露出来后,它就成了一种新的“计算资源”。如何高效、稳定、大规模地调用这种资源,并将其无缝集成到现有的软件系统中,这正是 Go 的主战场。 Go 的四大核心优势,决定了它在这个生态位上的不可或缺性: 性能与并发: AI 应用后端往往是高并发、I/O 密集的,Go 的并发模型和性能表现是其构建健壮服务的基础。 部署与运维: 静态编译的单一二进制文件,完美契合云原生时代的容器化部署,极大降低了 [...]
本文永久链接 – https://tonybai.com/2025/08/17/best-linux-os-for-robotics-in-2025 大家好,我是Tony Bai。 如果你正投身于机器人技术领域,选择正确的操作系统至关重要。随着人工智能、自动化和机器学习的进步,机器人正变得前所未有的复杂。在为这些智能机器提供动力方面,Linux凭借其开源的灵活性、稳定性以及对机器人框架的广泛支持,仍然是首选。 在本文中,我们将探讨2025年最佳的机器人Linux操作系统,帮助你为你的项目找到完美的发行版——无论你是从事工业自动化、人工智能驱动的机器人技术,还是业余爱好者的创作。我们还将介绍专注于机器人的Linux发行版的最新发展,让你保持领先。 1. Ubuntu机器人操作系统 机器人技术正以前所未有的速度发展,改变着医疗、自动化、制造乃至太空探索等行业。任何机器人系统的基础都是其操作系统,它决定了系统的效率、安全性和性能。 Ubuntu机器人操作系统 截至2025年,Ubuntu已成为机器人领域的最佳Linux操作系统。凭借其与机器人操作系统(ROS)的无缝集成、优化的实时性能以及对AI驱动机器人技术的扩展支持,Ubuntu成为开发者、研究人员和行业的首选。 为什么Ubuntu是机器人领域的最佳Linux操作系统 Ubuntu在机器人领域的主导地位并非偶然——它建立在多年的持续发展和强大的社区支持之上。以下是Ubuntu脱颖而出的一些关键原因: 1. 与ROS(机器人操作系统)的无缝集成 ROS已成为使用最广泛的机器人中间件,提供了一系列工具和库,帮助开发者构建复杂的机器人应用程序。由于ROS最初就是为Ubuntu设计的,因此集成非常无缝。 ROS 2与Ubuntu:到2025年,Ubuntu为ROS 2提供了内置支持,ROS 2提供了实时功能、安全增强和对多机器人系统更好的支持。 预装ROS软件包:Ubuntu通过预配置的软件包简化了ROS的安装,为开发者节省了大量时间。 强大的开发者社区:由于Ubuntu是机器人领域使用最多的操作系统,因此有庞大的支持网络可用于故障排除、教程和协作。 2. 针对嵌入式和边缘设备进行优化 并非所有机器人系统都是大型工业机器——许多现代机器人是需要轻量级和高效软件的小型嵌入式设备。Ubuntu Core是Ubuntu的最小化版本,专为边缘计算和嵌入式机器人技术而优化。 基于事务的更新:Ubuntu Core提供自动、故障安全的更新,确保机器人系统保持最新状态,而不会有破坏功能的风险。 注重安全的设计:Ubuntu Core包含内置的安全功能,如应用程序沙箱和验证启动机制,这对于在敏感环境中运行的机器人至关重要。 低系统资源占用:凭借其轻量级的特性,Ubuntu Core能在小型机器人硬件上高效运行,包括树莓派(Raspberry Pi)、NVIDIA Jetson和定制AI板卡。 3. 安全性与长期维护 安全性是机器人技术中的一个主要问题,尤其是在医疗和国防等行业。Ubuntu背后的公司Canonical提供扩展安全维护(ESM),确保基于Ubuntu的机器人系统获得长期的安全更新。 定期安全补丁:这可以防止可能被黑客利用的漏洞,使Ubuntu成为机器人项目最安全的选择之一。 行业采用:许多航空航天、汽车和工业自动化公司因其安全优先的方法而信任Ubuntu。 4. 硬件兼容性与行业采用 Ubuntu支持广泛的硬件,从AI驱动的机械臂到自动驾驶无人机。无论你是在开发工业机器人还是个人助理机器人,Ubuntu都为大量的传感器、执行器和计算单元提供驱动程序、库和支持。 可与流行的硬件平台配合使用,例如: NVIDIA Jetson AI驱动的机器人套件 树莓派(用于小型机器人项目) Intel RealSense(用于3D深度感应机器人) 定制的基于ARM的机器人系统 因为Ubuntu是一个开源操作系统,制造商也可以为其特定的机器人应用定制内核并进行优化。 Ubuntu机器人技术的最新发展(2025年) 过去一年,Ubuntu的机器人技术生态系统取得了显著进步。以下是2025年一些最激动人心的更新: 1. [...]
本文永久链接 – https://tonybai.com/2025/08/17/create-pointer-to-simple-types 大家好,我是Tony Bai。 在 Go 中创建一个指向基本类型(如 int 或 string)的指针,为何比创建一个指向结构体的指针更繁琐?这个长期存在的“人体工程学”问题,由 Go 语言的共同创造者之一 Rob Pike 在提案 #45624 中再次带入公众视野,并由此引发了一场长达数年、充满深度思辨的社区大讨论。最终,在权衡了多种方案的利弊后,社区逐渐形成共识,Go 提案委员会倾向于接受 new(v) 语法。本文将和大家一起回顾这场关于指针初始化的“十年之辩”,深入探讨各种方案的优劣,并解读为何 new(v) 可能成为最终赢家。 背景:一个困扰开发者多年的“小”问题 在 Go 中,我们可以用 p := &S{a: 3} 这样简洁的语法,一步到位地创建一个指向已初始化结构体的指针。但如果我们想创建一个指向 int 值 3 的指针,就必须写成: a := 3 p := &a 这种不对称性在处理大量使用指针来表示“可选”字段的场景时(例如,与 JSON、Protobuf 或 AWS SDK 交互),会变得异常繁琐。开发者往往不得不在项目中定义或引入大量的辅助函数,如: func StringPtr(s string) *string { return &s [...]
本文永久链接 – https://tonybai.com/2025/08/16/brand-new-os-impossible 大家好,我是Tony Bai。 对于许多心怀浪漫主义的开发者来说,“从零开始编写一个属于自己的操作系统”,或许是技术生涯中最终极、最性感的梦想。这几乎是现代编程世界的“创世纪”,是掌控计算机每一个比特的至高权力。 然而,最近一位名为 Wildan M 的工程师,在他的一篇博文中,用一次亲身参与 Redox OS 项目的经历,给我们所有人泼了一盆冷水。他的结论简单而又颠覆: 现在,从零开始编写一个全新的、能被广泛采用的操作系统,已几乎是一项不可能完成的任务。 而其真正的难点,并非我们想象中那个神秘而复杂的内核,而在于内核之外,那座看不见的、庞大到令人绝望的“冰山”。 冰山一角:内核,那个“最简单”的部分 故事的主角是 Redox OS,一个雄心勃勃的项目。它旨在用内存安全的 Rust 语言,构建一个现代的、基于微内核架构的、可以替代 Linux 和 BSD 的完整操作系统。 当我们谈论“写一个 OS”时,我们通常指的是编写内核。那么 Redox OS 的内核有多复杂呢?文章给出了惊人的数据: * 代码量: 约 3 万行 (30k LoC)。 * 启动速度: 大多数情况下,不到 1 秒。 在短短十年间,Redox 团队已经完成了动态链接、Unix 套接字等核心功能。这无疑是令人敬佩的工程壮举。但 Wildan 指出,这仅仅是浮出水面的冰山一角。一个能启动的内核,距离一个“能用”的操作系统,还有着遥远的距离。 冰山之下:生态移植的“五层地狱” 当作者兴致勃勃地想为 Redox OS 贡献力量,尝试将一些现代程序(如 Go, Node.js, Rust [...]
本文永久链接 – https://tonybai.com/2025/08/15/some-changes-in-go-1-25 大家好,我是Tony Bai。 北京时间2025年8月13日,Go 团队如期发布了 Go 语言的最新大版本——Go 1.25。按照惯例,每次 Go 大版本发布时,我都会撰写一篇“Go 1.x 中值得关注的几个变化”的文章。自 2014 年的 Go 1.4 版本起,这一系列文章已经伴随大家走过了十一个年头。 不过,随着我在版本冻结前推出的“Go 1.x 新特性前瞻”系列,以及对该大版本可能加入特性的一些独立的解读文章,本系列文章的形式也在不断演变。本文将不再对每个特性进行细致入微的分析,因为这些深度内容大多已在之前的《Go 1.25 新特性前瞻》一文中详细讨论过。本文将更聚焦于提炼核心亮点,并分享一些我的思考。 好了,言归正传,我们来看看Go 1.25带来了哪些惊喜! 语言变化:兼容性基石上的精雕细琢 正如 Go 一贯所做的,新版 Go 1.25 继续遵循 Go1 的兼容性规范。最令 Gopher 们安心的一点是:Go 1.25 没有引入任何影响现有 Go 程序的语言级变更。 There are no languages changes that affect Go programs in Go 1.25. 这种对稳定性的极致追求,是 Go [...]
本文永久链接 – https://tonybai.com/2025/08/14/rs-py-ts-trifecta 大家好,我是Tony Bai。 在 AI 浪潮席卷而来的今天,一个深刻的问题正摆在所有开发者面前:我们手中的编程语言,将如何被这股力量重塑?我们未来的技能投资,应该押注在哪里? 最近,Rust 核心团队的 Niko Matsakis,在他的一篇博文中给出了一个大胆的预测:Rust、Python 和 TypeScript 将成为未来的主导语言(移动端除外),形成新的“三驾马车”。 他的核心论点极具说服力:AI 正在削弱我们的“语言忠诚度”。过去,我们选择最熟悉的语言以求最快,因为学习新语言和其生态的成本太高。但现在,AI 正在改变这一切。 Niko 写道:“当我使用 AI 助手构建项目时,我的思维方式不同了。我更多地考虑有哪些库可用,我的基本性能需求是什么,以及我期望与哪些平台集成。” 换言之,AI 正在帮助我们克服学习曲线的“坑”,让我们得以回归本源,为任务选择“基础最扎实”的语言。 而这“三驾马车”,恰好占据了最关键的生态位: Rust: 系统与性能的基石,以其无与伦比的内存安全和效率成为底层开发的首选。 Python: 数据与实验的引擎,凭借其在科学计算和机器学习领域的绝对统治力,主导原型设计和数据应用。 TypeScript: Web 与应用的界面,作为 Web 的“母语”,在浏览器和众多跨平台应用中拥有不可替代的地位。 这个预测听起来逻辑严谨,几乎无懈可击。然而,当我们把目光从理想的“基础”,转向现实世界的复杂工程实践时,一些强大的“挑战者”浮出水面,它们的故事,同样值得倾听。 一种新的编程范式:“想法导向编程” 在深入辩论之前,我们必须先理解 Niko 提出的一个核心概念,它支撑着整个预测的基石——“想法导向编程” (Idea-Oriented Programming, IOP)。 这并非是那种懒散、模糊地对 AI 说“给我做个XX”的“氛围编程 (Vibe Coding)”。IOP 是一种严谨的编程范式,它重新定义了人与 AI 的关系: “开发者更像是首席架构师,而你的编码工具就像是你的学徒。你思考目标和关键设计,制定清晰的计划,并将重活累活授权给工具——然后你审查它们的产出,并进行调整。” 在这种模式下,AI 不是“神灯精灵”,而是你的“学徒”。它负责处理繁琐的实现细节,而你,则被解放出来,专注于更高层次的、创造性的工作。正是这种角色的转变,使得“语言基础”变得比“个人熟练度”更重要。 然而,这个看似完美的预测,真的无懈可击吗? 挑战者一:Go [...]
本文永久链接 – https://tonybai.com/2025/08/13/bit-manipulation-in-go 大家好,我是Tony Bai。 在编程这门手艺中,我们时常扮演着“建筑师”的角色,用一行行优雅的高级语言,构建起宏伟的应用大厦。但你是否曾停下脚步,好奇地探寻过这座大厦最深处的基石——那些由 0 和 1 构成的、既简单又神秘的二进制世界? 当你阅读 Go 标准库(比如 sync.Mutex 或 os.FileMode)的源码时,看到那些 &、|、^、<< 符号以一种眼花缭乱的方式组合在一起,你感受到的是困惑,还是一丝兴奋? 那感觉,就像是魔法学院的学生,无意中翻开了一本古老的“咒语书”。书上的符号看似简单,却蕴含着驱动整个魔法世界运转的底层力量。 这本“咒语书”,就是位运算(Bit Manipulation)。它不是什么过时的黑科技,而是隐藏在现代软件工程之下的一门永恒的、优雅的艺术。 这不是“屠龙之技”,而是一场思维的“魔术表演” 很多人对位运算的印象,还停留在那些刁钻的面试题上。但真正的位操作艺术,远不止于此。它是一种思维方式,一种用最纯粹、最底层的方式与计算机对话的技艺。 掌握这门艺术,能为你带来什么? 1. “点石成金”的性能魔法 位运算直接在二进制层面操作数据,其速度快如闪电。在性能敏感的场景,它能将原本笨重的计算(结合for、if等),优化成几次轻盈的位移和与或,带来数量级的性能提升。这就像魔术师在众目睽睽之下,瞬间完成了看似不可能的任务。 2. “芥子纳须弥”的空间魔法 一个 int64 变量,在位操作大师的手中,是 64 个可以独立控制的微观世界。通过精巧的位掩码,你可以在极小的空间内,存储和管理海量的状态信息。这种对空间的极致利用,本身就是一种令人赞叹的艺术。 注:“芥子纳须弥”是一个源自佛教经典的成语,用来形容一个看似微小的空间,却能容纳极其巨大或广阔的世界。芥子:指的是芥菜的种子,非常微小。须弥:指的是须弥山(Sumeru),在佛教传说中,是世界的中心,一座无比宏伟、巨大的神山。所以,“芥子纳须弥”的字面意思就是“在小小的芥菜种子里,容纳下整座须弥山”。 3. “洞悉本质”的认知魔法 这是我认为最迷人的一点。学习位运算,会为你开启一扇“天眼”,让你能够穿透高级语言的层层封装,直视数据的二进制本质。你将开始理解,为什么一个简单的权限判断,用 & 会比用 == 更具智慧;为什么一个哈希函数,需要用 ^ 和 << 来制造“混乱”。这种认知的提升,会让你在阅读源码、设计系统时,获得前所未有的快感和深度。 揭秘“二进制魔术”的秘密 如果说位运算是一场精彩的魔术,那我非常乐意为你揭开这场魔术背后的秘密。 在我的全新微专栏 《用Go解锁位运算之美》中,我们将一起,从最基础的“手法”练起,逐步掌握那些令人拍案叫绝的“魔术流程”。我们将以经典的思想为蓝图,用工程化的 Go 语言为舞台,上演一场属于程序员的二进制魔术秀。 你将从这场“表演”中学到什么? 一套“基本手法”:你将掌握定位、消除、分离二进制位 1 [...]
本文永久链接 – https://tonybai.com/2025/08/12/go-identity-crisis 大家好,我是Tony Bai。 最近,在国外的 Go 社区(Reddit r/golang)上,一个帖子引发了我的深思。发帖者是一位资深的 Gopher,他用一种略带困惑的语气写道: “我感受到来自新 Go 开发者的巨大压力,他们想把 Go 变成他们最喜欢的语言。” 他列出了一份“愿望清单”,上面是新 Gopher 们最常要求增加的特性: 注解 (Annotations),像 Java 或 Python 那样 更多原生类型,比如 Set、Stream 三元运算符 元编程能力 同时,他还观察到了一些行为模式:引入大量依赖来完成简单的任务、用熟悉的 Java 式架构来封装地道的 Go 行为、甚至完全不使用强大的标准库…… 这个帖子像一块石头投入了平静的湖面,瞬间激起了数百条评论。这不仅仅是一场关于“增加什么功能”的技术讨论,它更像是一场关于 Go 语言“我是谁?”、“我从哪里来?”、“我要到哪里去?”的哲学辩论。 这,正是 Go 语言正在经历的一场深刻的“身份危机”。 “原住民” vs “新移民”的哲学冲突 要理解这场危机的本质,我们可以把 Go 社区形象地看作一个正在迅速发展的“新大陆”。这里有两类居民: “原住民” (The Natives):他们是早期来到这片大陆的开拓者,被 Go 语言最初的承诺所吸引——简单、明确、可预测。他们选择 Go,正是因为它打破了传统语言不断堆砌特性、直到每个人都满意的怪圈。 “新移民” (The Immigrants):随着 Go [...]
本文永久链接 – https://tonybai.com/2025/08/11/why-go-not-embrace-iouring 大家好,我是Tony Bai。 在 Linux I/O 的世界里,io_uring 如同划破夜空的流星,被誉为“终极接口”。它承诺以无与伦比的效率,为数据密集型应用带来革命性的性能提升。正如高性能数据库 ScyllaDB 在其官方博文中所展示的,io_uring 能够将系统性能推向新的高峰。 然而,一个令人费解的问题摆在了所有 Go 开发者面前:作为云原生infra和并发编程的标杆,Go 语言为何对这颗唾手可得的“性能银弹”表现得如此审慎,甚至迟迟未能将其拥抱入标准库的怀抱?一场在 Go 官方仓库持续了五年之久的 Issue 讨论(#31908),为我们揭开了这层神秘的面纱。这并非简单的技术取舍,而是 Go 在其设计哲学、工程现实与安全红线之间进行反复权衡的结果。本文将深入这场讨论,为您揭秘阻碍 io_uring 在 Go 中落地的三大核心困境。 io_uring:一场 I/O 模型的革命 要理解这场争论,我们首先需要明白 io_uring 究竟是什么,以及它为何具有革命性。 在 io_uring 出现之前,Linux 上最高效的 I/O 模型是 epoll。epoll 采用的是一种“拉(pull)”模型:应用程序通过一次 epoll_wait 系统调用来询问内核:“有我关心的文件描述符准备好进行 I/O 了吗?”。内核响应后,应用程序需要再为每个就绪的描述符分别发起 read 或 write 系统调用。这意味着,处理 N 个 I/O 事件至少需要 N+1 次系统调用。 [...]
本文永久链接 – https://tonybai.com/2025/mm/dd/debugging-Incidents-in-google 大家好,我是Tony Bai。 尽管 Google 的 SRE 手册为我们描绘了理想的运维蓝图,但在“炮火连天”的生产事故现场,工程师的真实反应往往是另一番景象。 最近,一篇发表于 ACM Queue 的研究深入剖析了 Google 工程师(包括 SRE 和 SWE)在处理复杂分布式系统生产问题时的真实行为模式。这项研究通过对大量事后复盘(postmortem)的分析和深度访谈,揭示了不同角色工程师在思维模型、工具选择上的显著差异,并总结出了一套普遍适用的“调试构建块”。对于每一位构建和维护大规模服务的工程师来说,这些来自一线的洞察无疑是一份宝贵的实战指南。 研究背景:理论与现实的鸿沟 Google 的研究人员旨在通过经验主义方法,理解工程师在真实生产事件中的端到端调试过程。他们分析了过去一年的事后复盘文档,并对 20 个近期事件的一线响应者进行了深度访谈,最终描绘出了一幅生动的“战场地图”。 研究方法:从数据到访谈的深度挖掘 为了确保研究的经验性和深度,Google 团队采用了多阶段的研究方法: 阶段 0 & 1: 数据驱动的筛选与分析:研究人员首先对过去一年的事后复盘(postmortem)文档进行了大规模分析,提取了缓解时间、根本原因等量化数据。然后,他们从中精心挑选了 20 个具有代表性的近期事件,并深入阅读了相关的复盘文档和内部聊天记录,以构建对事件过程的初步理解。 阶段 2 & 3: 真人访谈与旅程绘制:随后,团队对这 20 个事件的一线响应者(on-callers)进行了深度访谈,以填补文档中缺失的细节和“当事人的真实感受”。最终,他们为每个事件绘制了详细的“用户旅程图”,并通过聚合这些视图,提炼出了通用的调试模式、工具和核心问题。 调试的核心构建块:四个反复出现的“循环” 研究发现,工程师的调试之旅并非一条直线,而是在几个核心的“循环”(Loop)中反复迭代。在找到根本原因之前,on-call 工程师的首要任务永远是“止血”——尽快恢复服务。 检测:通过告警、用户升级或主动巡检发现问题。核心问题是:“这个问题的严重程度如何?” 分类循环:这是快速评估阶段。工程师需要判断告警是否为噪音,评估问题的“爆炸半径”(影响范围和严重性),并决定是否需要立即处理或升级(即拉入其他团队或利益相关者)。这个循环在一次事件中可能会被多次触发,因为随着更多信息的涌入,对严重性的判断可能会改变。 调查循环:这是假设驱动的核心阶段。工程师基于已有信息形成关于潜在原因的理论,然后使用各种监控工具收集数据来验证或推翻这些理论。这个循环同样会反复发生,直到找到一个高置信度的原因。 缓解/根因循环: 缓解:在压力下,工程师首先尝试采取临时措施来恢复服务。核心问题是:“我应该采取哪种缓解措施?我有多少信心这是正确的做法?” 有时,错误的缓解措施甚至会使问题恶化。 根因分析:一旦服务恢复,压力减小,团队会进入根因分析阶段,这可能涉及更深入的代码更改和撰写事后复盘,以防止问题再次发生。 SRE vs. SWE:两种心智模型的碰撞 研究中最有趣的发现之一,是 [...]
本文永久链接 – https://tonybai.com/2025/08/09/true-streaming-support-in-jsonv2 大家好,我是Tony Bai。 Go 开发者长期以来面临一个痛点:标准库 encoding/json 在处理大型 JSON 数据时,即使使用 Encoder/Decoder,也因其内部的全量缓冲机制而导致巨大的内存开销。备受期待的 encoding/json/v2 提案(#71497)旨在从根本上解决这一问题。通过引入全新的底层包 encoding/json/jsontext,v2 实现了真正的流式处理能力。本文将通过具体的、可量化的基准测试,向你展示 v1 的内存陷阱,并演示如何使用 json/v2 高效地实现流式处理大规模 JSON 数据,彻底告别内存爆炸的烦恼。 json/v1 的“伪流”之痛:一个内存陷阱基准 为了直观地感受 json/v1 在处理大数据时的局限性,我们来建立一个基准测试。我们将分别进行编码(Marshal)和解码(Unmarshal)操作,并观察其内存使用情况。 关于内存评估:我们通过比较操作前后的 runtime.MemStats.TotalAlloc 来精确测量该操作自身导致的堆内存总分配量。一个真正的流式处理,其内存分配量应该是一个很小的常数(例如,I/O 缓冲区的大小),而与数据总量无关。 场景一:v1 编码一个巨大的 JSON 数组 我们创建一个包含 100 万个空结构体的 slice,然后使用 json.Encoder 将其写入 io.Discard。 // jsonv2-streaming/v1/marshal.go package main import ( "encoding/json" "io" "log" "runtime" ) func main() [...]
本文永久链接 – https://tonybai.com/2025/08/08/go-tui-primer 大家好,我是Tony Bai。 最近,AI 圈最火的莫过于Anthropic推出的“Claude Code”– 一款基于终端的编码智能体工具: 当你在终端窗口里,看着 AI 实时地帮你生成、修改、编译、测试和运行一个 Web 应用,并且能立刻看到输出和反馈时,那种感觉只能用“震撼”来形容。 作为一名 Go 开发者,我当时脑子里冒出的第一个念头就是: “如果我能用 Go,在自己的终端里,也实现一套这样的工作流,那该多酷?” 想象一下:你写一个 CLI 工具,它可以连接到任何一个大模型 API。你给它一个需求,比如“帮我写一个处理用户注册的 Go HTTP Handler”,然后: 你的终端左侧窗口,开始像打字机一样,流式输出 AI 生成的 Go 代码。 右侧窗口,实时显示出代码的编译状态、单元测试的进度条和结果。 当代码完成时,它自动运行起来,并告诉你:“服务已在 localhost:8080 启动,请测试。” 这个场景,就是我们开发者梦想中的“编码伙伴”,也是一个宏大但并非遥不可及的目标。 要实现这个目标,除了调用 AI 的 API,最关键、也是我们最容易忽视的一环是——如何构建这样一个复杂的、多窗口的、可实时交互的终端界面? 答案,就是 TUI (Terminal User Interface) 开发。 你的下一个 Go 程序,值得拥有一张“脸” 我们很多 Go 开发者,都把技能点加在了后端性能、并发模型上,这当然没错。但我们常常忽略了程序的“脸面”——它的交互体验。 传统的 CLI 工具,就像一个只会用专业术语说话的机器人,强大但冷漠。而一个现代的 [...]
本文永久链接 – https://tonybai.com/2025/08/07/fork-go-module 大家好,我是Tony Bai。 今天,我想和你聊一个几乎每个 Go 开发者都经历过的场景,一种我们圈内人“只可意会,不可言传”的痛苦。我称之为 Go 模块的“分叉之痛” (The Forking Pain)。 故事通常是这样开始的:你在一个项目中,依赖了一个第三方库。某天,你发现这个库里有一个 Bug,不大不小,但确实影响了你的业务。幸运的是,你深入代码后,发现自己完全有能力修复它,可能只需要改动三五行代码。 你的脑海中浮现出一条清晰、理想的路径: 在 GitHub 上 Fork 这个项目。 在你的 Fork 中修改代码,修复 Bug。 在自己的主项目中验证修复效果。 向上游(Upstream)提交一个干净、优雅的 Pull Request。 然而,当你满怀信心地开始第一步时,现实的残酷才刚刚拉开序幕。 为了让你 Fork 的项目能在本地独立编译通过,你必须将 go.mod 文件中的 module 指令,从 module github.com/upstream/foo 改为 module github.com/bigwhite/foo。而这一改动,就像推倒了第一块多米诺骨牌,一场“全局替换”的噩梦正式降临。 你不得不祭出 sed、grep 或是 IDE 的全局搜索替换功能,将代码库中成百上千个对原仓库的内部引用路径,从 import “github.com/upstream/foo/pkg”,一个个地替换成 “github.com/bigwhite/foo/pkg”。 最终,一个原本 3 行代码的优雅修复,变成了一个包含 300 行导入路径变更的、极其嘈杂的 [...]
本文永久链接 – https://tonybai.com/2025/08/06/blitzkrieg-vs-attrition-in-ai-age 大家好,我是Tony Bai。 最近,我们的社交媒体时间线上,充斥着各种令人惊叹的 AI 效率神话。一些出海独立开发者,凭借 AI 的强大能力,在极短时间内“闪电般”地产出数个产品,上演着“一人成军”的传奇。 这景象,在令人惊叹之余,也难免给我们这些在大型项目和复杂系统中深耕的工程师,带来一丝焦虑:世界变化这么快,我们传统的开发模式和节奏,是否已经落伍了? 今天,我想和你深入探讨这背后的本质。我们需要清醒地认识到,这其实是两种目标、路径、评价体系都截然不同的开发模式。我称之为:“闪电战”与“持久战”。 “闪电战”模式:速度优先的“代码喷射器” 首先,我们必须理解那些“效率神话”主角们的战场。这是一种典型的“闪电战”模式。 核心目标: 快速验证想法,通过大量的产品“赛马”,在广阔的市场中捕捉稍纵即逝的流量和商机。 产品生命周期: 极短,甚至可以说是“阅后即焚”。一个产品可能只有一周的生命周期。若数据不佳,便会毫不犹豫地被下线,开发者则迅速转向下一个想法。 AI 的角色: 在这个模式下,AI 是一个速度优先的“代码喷射器”。它的核心任务是在最短时间内生成能运行的代码。至于代码质量、设计一致性、可维护性、乃至长期的技术债,通通不在首要考虑之列。因为代码本身,就是一种“快速消费品”。 我们工程师的“持久战”模式:严谨可靠的“副驾驶” 现在,让我们回到自己的战场。我们绝大多数人从事的,是截然不同的“持久战”。 核心目标: 构建稳定、可靠、可长期演进的系统。我们写的代码,很可能需要在金融、医疗、基础设施等关键领域,7×24 小时不间断地运行数年。 产品生命周期: 长期,以年为单位。每一次代码提交,都是在为一座摩天大楼添砖加瓦。 AI 的角色: 在这里,AI 必须是一个严谨可靠的“副驾驶”。它生成的每一行代码,都必须经受我们最严格的审视。因为我们,作为工程师,需要对 AI 产出的质量、安全性、性能、可维护性负全部责任。在这里,代码不再是消费品,而是需要长期持有和维护的核心资产——或者,沉重的技术负债。 看清这一点,我们就能明白:用“闪电战”的效率标准来衡量“持久战”的工作,是毫无意义的。 我们的战场不同,评价标准也完全不同。因此,我们完全没有必要为那种“一人一天N个产品”的神话而感到焦虑。 我们“持久战”工程师的 AI 打法与“护栏” 那么,在我们的“持久战”中,应该如何正确地使用 AI,既享受其带来的效率提升,又保证工程质量呢?关键在于建立清晰的“护栏”。 代码审查是最后防线: AI 生成的代码,必须经过比人类编写的代码更严格的审查。审查的重点,不应仅仅停留在功能实现,更要深入到安全漏洞、性能陷阱、设计模式是否恰当等深层问题。 建立团队级“Prompt 知识库”: 鼓励团队沉淀高质量、包含完整上下文和明确规范要求的 Prompt 模板。这能保证 AI 输出的“起点”质量更高,更符合团队的架构和规范,而不是每次都从零开始“随机”生成。 AI 专攻其擅长领域: 我们可以放心地让 [...]
本文永久链接 – https://tonybai.com/2025/08/06/go-new-engine-of-old-languages 大家好,我是Tony Bai。 我先来描述一种编程语言生态,请你猜猜它是谁: 它诞生于 1995 年,旨在为当时一个叫“万维网”的新平台构建应用。起初只是个小项目,却在互联网泡沫中野蛮生长,成为史上用户最广的语言之一。它曾被“严肃”的程序员们嘲笑了几十年,但最终得到了科技巨头的加持,迎来了事业的第二春。如今,它正迈向 30 岁,而其生态中最重要的一环——它的一个超集语言的编译器,正在被 Go 语言 重写以驱动未来。 你的第一反应,很可能是 JavaScript 生态。完全正确。这个超集语言,就是 TypeScript。 但这段描述,同样完美地适用于另一个名字:PHP。它也诞生于 1995 年,同样在 Web 浪潮中崛起,同样被嘲笑,同样迎来了第二春,而现在,一个基于 Go 语言 的新项目,也正在驱动着它的未来。 这两种语言,就像是同一枚硬币的两面,共同定义了 Web 编程的客户端与服务器端。而今天,我想和你聊的,正是它们故事中那个令人意想不到的、与我们 Gopher 息息相关的交集——Go 语言的角色。 编程语言中的“丰田卡罗拉” 在深入主题之前,我们必须先理解 PHP 的生态位。一篇精彩的博文将其比作编程语言中的“丰田卡罗拉”——无聊、坚固、简单、实惠。 它或许永远不会出现在技术发布会最酷炫的 Demo 上,但它和它经典的 LAMP(Linux, Apache, MySQL, PHP)组合,让全世界数以百万计的普通开发者,能以最低的成本、最可靠的方式,解决一个最实际的问题:搭建一个能用的网站。 C++ 的创造者 Bjarne Stroustrup 有一句名言:“世界上只有两种语言:一种是被人拼命吐槽的,另一种是没人用的。” PHP 显然属于前者。它曾被嘲笑为“糟糕设计的集合体”,但它也支撑着全球 70% 以上的网站。这个数字,无论你用何种挑剔的眼光审视,都无法否认其巨大的成功和顽强的生命力。 Go:一个意想不到的“新引擎” 多年以来,PHP 和 [...]
本文永久链接 – https://tonybai.com/2025/mm/dd/the-voice-of-k8s-experts-report-2025 大家好,我是Tony Bai。 过去的一年,企业 IT 基础架构领域经历了一场剧烈的地震。震中,正是由博通(Broadcom)对 VMware 的收购所引发。这场地震带来的,不仅仅是技术栈的更迭,更是一场关于成本、信任与未来路线的“大迁徙”。 最近,我读到一份来自 Portworx 的《2025 年 Kubernetes 专家之声报告》,它用翔实的数据,为这场正在发生的大迁徙描绘了一幅清晰的路线图。报告中的数字是惊人的: 95% 的受访企业计划减少其 VMware 份额。 33% 的企业计划完全停止使用 VMware。 44% 的企业在续签 ELA(企业许可协议)时,成本增长超过了 100 万美元。 这已经不是零星的抱怨,而是一场由商业驱动的、不可逆转的技术浪潮。一个核心问题摆在了所有架构师面前:当企业决定“逃离” VMware 时,那些承载着核心业务的虚拟机(VMs),将何去何从? 答案,正日益清晰地指向一个我们既熟悉又陌生的方向:Kubernetes。 云原生的新常态:从“试验田”到“核心区” 在讨论 VM 的“新家”之前,我们必须先认清一个事实:Kubernetes 早已不是那个只运行无状态 Web 应用的“试验田”了。 报告数据显示,云原生已经成为企业构建未来的默认选择。82% 的新应用将在云原生平台上构建,更重要的是,58% 的企业已经开始在 Kubernetes 上运行他们最核心的 Tier 0/1 级别的任务关键型应用。 这意味着,Kubernetes 已经赢得了企业在性能、稳定性和数据安全方面的信任。它已经成功承载了: 69% 的数据库 67% 的实时分析系统 60% 的 [...]
本文永久链接 – https://tonybai.com/2025/08/05/go-concurrency-mental-model 大家好,我是Tony Bai。 如果你曾是 Java、C++ 或 Python 阵营的一员,你一定对 synchronized、std::mutex 或 threading.Lock 这些概念驾轻就熟。它们就像我们工具箱里熟悉的锤子,在处理并发问题时,我们总能下意识地拿起它,去“敲定”那些需要保护的共享资源。 然而,当你满怀期待地步入 Go 的世界,准备大展拳脚时,却发现社区和高手们总在谈论一个看似“绕路”的概念——通道 (Channel)。他们反复强调一句如同“咒语”般的箴言:“不要通过共享内存来通信;相反,要通过通信来共享内存。” 这时,困惑便产生了: “放着简单直接的锁不用,为什么要用看起来更复杂的 Channel?” “Channel 和 Mutex 到底该在什么场景下选择?有没有一个万能的法则?” “我用 Go 写的并发程序,为什么总感觉不地道,甚至比我用 Java 写的还容易出错?” 如果你曾有过这些疑问,那么恭喜你,你已经触及了掌握 Go 并发精髓的核心症结。问题不在于 Go 的语法有多难,而在于我们试图用旧的“心智模型”去套用一个全新的并发范式。这就像试图用拉丁语的语法去理解中文的意境,生硬的翻译只会让你离真相越来越远。 这个微专栏,就是为你——一位从其他编程语言阵营走来,希望真正掌握 Go 并发精髓的开发者——量身打造的“心智模型”转变教程。 在这里,我不会枯燥地罗列 API,也不会给你一堆零散的“最佳实践”。我的目标是带你完成一次思维的“破冰”与“重塑”。在这次由三节课组成的“转变之旅”中,我们将一起: 第一课:心智模型转变。 我们将从根源上剖析 Go 并发哲学的不同,通过一个具象的案例,亲身体验从“加锁”思维到“通道通信”思维的转变是多么酣畅淋漓。你将明白,为何 Channel 才是 Go 并发世界的一等公民。 第二课:心智模型实践。 我们会将新的心智模型应用到工业界最常见的并发模式中,并直面、破解“慢消费者”和“任务调度”这两个经典难题。 第三课:心智模型升华。 我们将探讨并发编程中最容易被忽视,也最致命的一环——Goroutine 的生命周期与工程纪律。你将学会如何避免资源泄漏,如何优雅地控制和关闭你的并发程序。 我不是来教你语法的,而是邀请你和我一起,完成一次思维的升级。我希望,当这个微专案结束时,你收获的不仅仅是几个可以复制粘贴的代码模板,而是一种全新的、看待并发问题的视角。你将能够自信地写出简洁、健壮、地道的 Go [...]
本文永久链接 – https://tonybai.com/2025/08/04/continuous-profiling-fourth-pillar 大家好,我是Tony Bai。 凌晨两点,运维平台的警报刺破了宁静。P99 延迟飙升,用户服务几近瘫痪。作为 Go 工程师,你的脑海中闪过无数可能:是数据库慢了?是下游服务超时?还是某个新上线的 goroutine 泄露了?你急忙打开监控面板,Metrics (指标) 显示 CPU 和内存平稳,Logs (日志) 没有明显异常,Traces (追踪) 只告诉你请求在服务内部耗费了大量时间,却不知所踪。这个场景,是现代软件运维中一个令人沮丧的“最后一公里”难题。 近日,可观测性领域的领导者 Datadog 在其官方技术博客中发表了一篇极具洞察力的文章,题为《Why continuous profiling is the fourth pillar of observability》,它为这个难题提供了答案。文章掷地有声地论证了,一个新兴的技术范式——持续性能分析 (Continuous Profiling)——正在补全可观测性的关键拼图,成为继 Metrics、Logs 和 Traces 之后,不可或缺的“第四大支柱”。本文将结合该文的核心论点,为 Go 开发者深度解读这场正在发生的变革。 可观测性缺口:为什么三大支柱还不够? 多年来,我们依赖三大支柱来理解复杂的分布式系统。它们是强大的工具,但各自的边界也愈发清晰: Metrics 如同系统的仪表盘,提供聚合的、宏观的健康度量。它能告诉我们“服务 CPU 使用率达到 90%”,但无法回答 “是哪段 Go 代码在消耗 CPU?” Logs 是离散的事件记录,如同飞机的黑匣子。它能记录“发生了一个错误”,但当系统因性能下降而非错误崩溃时,日志往往是沉默的。 Traces 描绘了请求的生命周期,如同 GPS 导航。它能精确定位“请求在 [...]
本文永久链接 – https://tonybai.com/2025/08/03/choose-boring-technology 大家好,我是Tony Bai。 大约十年前,Dan McKinley 的一篇经典雄文《选择无聊的技术》(Choose Boring Technology)在工程师圈子里广为流传。它的核心观点简单而深刻:一家公司的“创新代币”(innovation tokens)是有限的,应该用在刀刃上,而不是随意挥霍在那些闪亮但未经证实的新技术上。 “无聊”的技术,比如 Postgres、Python、PHP,它们的优势不在于新潮,而在于其故障模式和能力边界是众所周知的。当系统在凌晨三点崩溃时,你需要的是一个有大量 Stack Overflow 答案可以求助的领域,而不是一片你必须独自开拓的未知“无人区”。 这个原则,在过去十年里,成为了无数资深工程师的技术选型座右铭。然而,十年后的今天,随着 LLMs 和 Agentic AI 编程工具的崛起,业界仍然认为:这个原则不仅没有过时,反而比以往任何时候都更加重要,甚至更加致命。 AI 时代的“诱惑”与“危险” AI 编程助手带来了一个全新的变数。这个变数既有趣,又极其危险。 这里的“有趣”在于,现代 AI 工具(无论是 Claude 还是 Copilot)已经非常擅长为几乎任何你能想到的技术栈,生成“看起来非常专业”的代码。你给它一个 prompt,让它用最新的 JavaScript 框架、GraphQL federation 和 Kubernetes 来实现一套微服务,它会迅速给你返回一堆代码——这些代码可能遵循了所有社区惯例,命名规范无可挑剔,错误处理看起来也像模像样,甚至,它可能真的能运行。 这就是 AI 的“诱惑”。它让你感觉,掌握任何新技术都不过是弹指一挥间的事。 而“危险”也恰恰源于此。当你在一个你不熟悉的技术领域里使用 AI 时,一个致命的问题出现了: 你根本无法验证,AI 是不是在“一本正经地胡说八道”(bullshitting you)。 我亲眼见过,有工程师接受了 AI 生成的代码,而这些代码里: 使用了早已废弃的 API。 实现了严重的安全反模式。 制造了只有在生产负载下才会暴露的、极其隐蔽的性能问题。 [...]
本文永久链接 – https://tonybai.com/2025/08/02/proposal-http3 大家好,我是Tony Bai。 在社区长达数年的热切期盼之后,Go 官方终于迈出了支持 HTTP/3 的关键一步。一项编号为#70914的新提案,正式建议在 x/net/http3 中添加一个实验性的 HTTP/3 实现。这一进展建立在另一项更基础的提案 #58547(x/net/quic) 之上,该提案的实现已取得重大进展,并已从内部包移至公开的 x/net/quic。这意味着 Go 的网络栈即将迎来一次基于 UDP 的、彻底的现代化升级。本文将带您回顾 Go 社区对 HTTP/3 的漫长期待,深入解读官方 QUIC 和 HTTP/3 的实现策略,并探讨其对未来 Go 网络编程的深远影响。 一场长达五年的等待 对 HTTP/3 的支持,可以说是 Go 社区近年来呼声最高的功能之一。早在 2019 年,issue #32204 就被创建,用于追踪在标准库中支持 HTTP/3 的进展。在随后的五年里,随着 Chrome、Firefox 等主流浏览器以及 Cloudflare 等基础设施提供商纷纷拥抱 HTTP/3,社区的期待也日益高涨。 在此期间,由 Marten Seemann 维护的第三方库 quic-go 成为了 Go 生态中事实上的标准,为 [...]
本文永久链接 – https://tonybai.com/2025/08/01/proposal-purego 大家好,我是Tony Bai。 对于许多 Go 开发者来说,purego 构建标签一直是一个模糊的存在。它到底意味着“没有 Cgo”、“没有 unsafe”,还是“没有汇编”?这个问题的答案在社区中众说纷纭,甚至连标准库中的使用也不尽统一。最近,一项历时六年、编号为#23172 的提案终于尘埃落定,Go 团队正式接受 (accepted) 了关于 purego 含义的共识。本文将带大家一起回顾这场漫长而精彩的社区辩论,深入探讨其背后的技术权衡,并阐明这个小小的标签对于 Go 的跨实现(如 TinyGo)和可移植性生态的深远意义。 背景:一个模糊的约定 purego 标签的诞生,源于 Go 生态系统日益增长的多样性。除了官方的 gc 编译器,还涌现出了 GopherJS、TinyGo、gccgo 等多种 Go 实现。在这些非标准环境中,对 unsafe 包的指针操作、Cgo 的支持以及 Go 汇编的兼容性各不相同。 最初,protobuf 等库为了兼容Google App Engine 等不允许 unsafe 的环境,开始使用 safe 标签。这个概念逐渐演变为 purego,但其确切含义从未被正式定义。这导致了混乱: 有人认为 purego 意味着完全的内存安全,即禁止 unsafe 包。 有人认为它意味着纯粹的 Go 代码,即禁止 cgo [...]
本文永久链接 – https://tonybai.com/2025/07/31/periodic-table-of-system-design 大家好,我是Tony Bai。 近日,一篇名为《系统设计的元素》(Elements of System Design)的论文引发社区热议。它的目标宏大且吸睛:通过梳理上百篇横跨操作系统、数据库、分布式系统等领域的经典论文,提炼出一套通用的系统设计原则“元素周期表”。 这份“周期表”的价值,不在于提供一套死板的规则,而在于为我们提供一套共享的词汇和心智模型。它能帮助我们更清晰地思考、更精确地沟通、更深刻地理解不同系统设计背后的内在联系。 下面便是该论文的中译版,希望能给大家带去启发。 系统设计通常通过特定领域的解决方案来传授,例如数据库、操作系统或计算机体系结构,每个领域都有其自成一派的方法和术语。虽然这种多样性是一种优势,但它也可能掩盖了跨领域反复出现的共通原则。本文提出了一个从计算机系统多个领域中提炼出的系统设计原则的初步分类法。其目标是提供一套共享、简洁的词汇,以帮助学生、研究人员和实践者对系统结构和权衡进行推理,跨领域比较设计,并更清晰地沟通设计选择。 引言 投身于计算机系统领域的一大乐趣在于其纯粹的多样性,它涵盖了操作系统、数据库、计算机体系结构、分布式系统、编程语言、网络等众多分支,每个分支都有着丰富的历史。对于初学者来说,由于传统和词汇的多样性,要发现不同领域之间的联系可能颇具挑战:相同的设计原则可能会以不同的面貌出现在不同的领域中。 例如,思考一下 Jim Gray 等人关于数据库隔离级别的经典论文。它仔细阐述了并发控制机制以及在正确性和性能之间的权衡。然而,如果没有在操作系统或计算机体系结构领域接触过类似问题,这些思想可能看起来仅仅是狭隘地“关于数据库”的。实际上,相同的设计原则,“放宽一致性”,以不同的形式在各种系统中反复出现,从弱顺序内存层次结构到分布式系统中的最终一致性协议。当每个社区都使用自己的术语和范例时,初学者可能很难识别出底层的设计原则。这种碎片化增加了认知开销,因为同一个权衡必须在每个上下文中重新学习。 这是一个更广泛的模式:系统研究富含实践洞见,但在共享的概念性支架上则较为薄弱。在各个领域中,类似的挑战反复出现,如管理并发、确保一致性和适应变化,而其框架和词汇却常常不同。因此,看似毫不相关的领域之间的深层联系可能仍然相对模糊。 本文是朝着弥合这些差距迈出的一小步。借用门捷列夫的比喻,我们提出了一个反复出现的系统设计原则的“元素周期表”。其目标并非一个僵化的分类法,而是一个可用的词汇表:一种用以标注论文、讲座和设计文档中所采用的基本原则的简洁方式。其目的是揭示计算机系统中已经存在的结构,以便学生能形成更连贯的心理地图,研究人员能精确定位其贡献,而实践者能以更高的清晰度跨领域讨论设计选择。 方法论 我们通过回顾操作系统、计算机体系结构、数据库、网络、编程语言、安全以及计算机系统其他领域的 100 多篇有影响力的论文来识别这些原则。这些论文因其历史意义和持续的相关性而被选中,例如关于并发控制 和共识 的经典论文,以及关于在系统内部使用机器学习 和为云设计系统 的近期工作。 对于每篇论文,我们都问:其底层的高层设计原则是什么?在不同领域中,独立的系统常常不是在机制上趋于一致,而是在共享的设计原则上:例如,通过放宽一致性来提高性能,或通过提升抽象来增强可用性。 要被认定为一条系统设计原则,它必须满足两个条件: 抽象性 – 该原则必须独立于具体的技术或实现。 通用性 – 该原则必须在不同领域中出现(例如,数据库系统、操作系统、编程语言)。 本分析旨在梳理出许多具有持久、通用价值的原则,而非对所有原则进行编目。 设计原则表 我们整理了一套结构化的、包含 40 多个从系统文献中提炼出的通用设计原则。如下图所示,它们被组织成反映了系统设计中常见维度的不同主题组。 图例: Code = 唯一短符号, Name = 原则名称, Intent = 简短描述。 每个原则都带有一个简短的符号(例如,Co 代表可组合性,Op 代表乐观设计)以便快速参考。我们强调设计意图而非规定具体机制:这些原则阐述的是诸如“在并发下保持正确性”或“优先处理普遍情况”等目标,而不是“使用此锁定协议”或“优化此查询计划”,具体的实现则留给特定领域。 目录 [...]
本文永久链接 – https://tonybai.com/2025/07/30/six-principles-production-ai-agents 大家好,我是Tony Bai。 随着 AI Agent 技术的兴起,许多开发者都投入到构建智能体的浪潮中,但很快就会发现,让 Agent 稳定、可靠地工作远非想象中容易。它们时而产生幻觉,时而偏离轨道,时而做出一些令人费解的“愚蠢”行为。最近,来自 app.build 的 Arseni Kravchenko 分享了他们在构建生产级 AI Agent 过程中总结出的六大核心工程原则。这些原则摒弃了虚无缥缈的“提示词黑魔法”,回归到坚实的软件工程基础。对于正在或计划使用 Go 构建 AI Agent 的开发者来说,这是一份宝贵的实践指南。 原则一:投资你的系统提示 (System Prompt) 许多人对“提示词工程”持怀疑态度,认为它充满了“我奶奶快不行了,请帮帮我”之类的奇技淫巧。然而,作者指出,现代 LLM 真正需要的是直接、详细、清晰且无矛盾的上下文,而非情感操控。 对于开发者而言,你要做的就是不要耍小聪明,要把系统提示当作给 Agent 的API 文档来写。 当你为 Agent 提供一个通过 os/exec 调用的工具时,不要只告诉它工具的名字。在系统提示中清晰地说明: 工具的完整命令是什么。 每个参数的含义、类型和格式。 预期的输出格式以及如何解析它。 前置条件和错误情况。 一个详尽的系统提示是 Agent 可靠行为的基石。 原则二:拆分上下文 (Split the Context) “上下文工程”是比“提示词工程”更重要的概念。巨大的、单一的上下文不仅成本高、延迟大,还会导致模型出现“注意力衰减”,忽略掉关键信息。 作者建议大家:默认只提供最少必要知识,并通过工具让 Agent 在需要时主动获取更多上下文。 与其在初始提示中塞入整个项目的源代码,不如: [...]
本文永久链接 – https://tonybai.com/2025/07/29/slog-multihandler 大家好,我是Tony Bai。 自 log/slog 在 Go 1.21 中引入以来,一个常见的需求始终困扰着开发者:如何将日志同时发送到多个目的地,并为每个目的地设置不同的日志级别?尽管社区已涌现出 samber/slog-multi 等优秀的三方库,但关于“标准库是否应原生支持”的讨论从未停止。最近,一项编号为#65954 的提案,建议在 log/slog 中加入 MultiHandler,获得了 Go 官方的 [likely accept] 评级。本文将带您回顾该提案从被质疑到被接受的全过程,深入探讨其背后的设计权衡。 背景:一个普遍而又棘手的需求 在实际生产环境中,日志往往需要被送往多个地方: * 控制台(stdout):用于开发和调试,通常需要 DEBUG 级别的详细信息。 * 本地文件:用于归档和追溯,可能需要 INFO 级别以上的日志。 * 远端日志服务(如 ELK, Loki,VictoriaLogs等):用于聚合和告警,可能只关心 ERROR 级别的日志。 然而,log/slog 的核心设计是一个 Logger 对应一个 Handler。虽然 io.MultiWriter 可以将相同格式、相同级别的日志写入多个 io.Writer,但它无法满足不同目的地、不同级别这一核心需求。 这导致许多开发者不得不自行实现 slog.Handler 来“扇出”(fan-out)日志,或者引入第三方依赖。正如提案者 lxl-renren 和多位评论者所指出的,这是一个非常普遍的场景。 从“不需要”到“值得拥有”的转变 提案初期,Go 团队成员 jba [...]
本文永久链接 – https://tonybai.com/2025/07/28/go-fix-reborn 大家好,我是Tony Bai。 Go 语言工具链中的元老级命令 go fix 即将迎来其生命周期中最重要的转折点。一项编号为 #73605 的新提案建议移除 go fix 当前的全部功能,使其暂时成为一个空命令。这一看似“激进”的举动,实则是为一个更宏大的目标铺路:将 go fix 改造为一个基于 Go 强大的代码分析(analysis)框架的、能够批量应用安全修复的现代化工具。本文将深入解读该提案的背景、具体内容以及它对 Go 代码现代化演进的深远影响。 背景:go fix 的历史使命与现状 在 Go 语言的早期发展阶段,go fix 是一个不可或缺的工具。它帮助早期使用者应对语言和标准库快速迭代带来的兼容性问题。其内置的修复器(fixer)涵盖了从 +build 标签迁移到 context 包导入路径变更等一系列历史遗留问题。 然而,时至今日,这些修复器中的绝大多数早已完成了它们的历史使命,变得鲜为人知且几乎不再被需要。提案作者 Alan Donovan 指出,除了 buildtag(处理旧式构建标签)可能还有些用处外,其他如 cftype、egl、netipv6zone 等修复器都已过时。 一个陈旧、功能固化的 go fix 已经无法满足现代 Go 开发的需求。 提案核心:“清空”是为了更好的“填充” 该提案分为前后关联的两步,本次讨论的是第一步: 第一步(本提案 #73605):清空 go fix 提案建议,首先移除 go [...]
本文永久链接 – https://tonybai.com/2025/07/27/native-prometheus-instrumentation-over-opentelemetry 大家好,我是Tony Bai。 在云原生可观测性的世界里,OpenTelemetry (OTel) 正如日中天。它被誉为“可观测性的未来”,承诺用一个统一的标准,终结 Metrics、Traces、Logs 各自为战的混乱局面。无数的开发者和公司,都在热情地拥抱这个“一次插桩,到处发送”的美好愿景。 但就在这股几乎不可阻挡的浪潮中,一个权威的声音却发出了一个略显刺耳的警告。 这个人,就是 Prometheus 的联合创始人,Julius Volz。 在他最新的博文中,Julius 毫不客气地指出:如果你正在使用 Prometheus 作为你的核心监控系统,并且你真正关心监控的质量和体验,那么,在使用 OpenTelemetry SDK 生成 Metrics 前,请务必三思! 他认为,拥抱 OTel 这个“通用标准”的代价,可能是丢掉 Prometheus 作为一个完整监控系统的“灵魂”,并背上丑陋、低效和复杂的“技术债”。 你正在丢掉 Prometheus 的灵魂 Julius 首先尖锐地指出了一个哲学问题:Prometheus 不仅仅是一个“指标数据库”,它是一个端到端的、有自己思想的监控系统。而 OTel 的“后端无关”设计,恰恰破坏了这种端到端的自洽性。当你选择用 OTel 向 Prometheus 推送数据时,你正在放弃这些至关重要的原生特性: 失去灵魂:Target 健康监控 (up 指标) Prometheus 最核心的设计之一就是 Pull 模型 + 服务发现。这意味着 Prometheus 主动拉取指标,它清楚地知道“哪些目标应该存在”以及“它们现在是否健康”。如果一个目标拉取失败,Prometheus 会自动生成一个 up{job=”demo”} [...]
本文永久链接 – https://tonybai.com/2025/07/26/migrate-from-prometheus-to-victoriametrics 大家好,我是Tony Bai。 在云原生可观测性的领域,Prometheus 无疑是王者。凭借其简洁的模型、强大的 PromQL 和活跃的社区,Prometheus 几乎定义了现代监控的行业标准。许多顶尖技术公司,包括 PingCAP,都将其作为核心产品的监控与告警解决方案。 然而,Prometheus 的架构缺陷使其在大规模监控中显得力不从心。我们经常看到像 Pinterest 这样的大型企业面临的真实挑战。当一个 TiDB 集群规模达到 700+ 节点,每秒处理 700K+ QPS 时,曾经可靠的“王者”开始露出疲态。用于故障诊断的核心工具 TiDB Clinic 在回放和分析这些大规模集群的指标时,Prometheus 开始频繁崩溃。 这迫使整个行业直面一个残酷的现实:在极限规模下,Prometheus 不再是最佳选择。最近,PingCAP 官方发布的一篇博文 详细记录了他们如何应对这一挑战,并最终选择用 VictoriaMetrics 完成“换心手术”的全过程。 压垮 Prometheus 的“五宗罪” 为了支持 Pinterest 这样的大客户,pingcap工程团队为 Prometheus 分配了“怪兽级”的硬件资源:一台拥有 96 核心 CPU 和 768GB RAM 的 i4i.24xlarge 实例。许多人曾天真地以为,只要资源给够,一切问题都能解决。 但事实并非如此。在高基数、高吞吐量的指标冲击下,Prometheus 暴露出了几个致命的、与资源无关的瓶颈: Out of Memory (OOM) [...]
您可以订阅此RSS以获取更多信息