ACID 与 BASE:两种一致性契约
先看三个真实场面: 转账扣了钱但没到账,用户投诉——排查发现是「扣款成功、入账失败」,中间那一步没人负责回滚; 「秒杀超卖」在单库上从不发生,一拆成三个库就冒出来了; 同一个商品页面,用户刷新两次看到两个价格,过几秒又统一了——谁都没错,只是「读到了一个还没同步完的中间态」。 这三件事的共同点:它们都不是「代码写错了」,而是「一致性的契约没签清楚」。同一份数据在几个地方各存了一份,那么「写完之后别人什么时候能看到」这件事,必须有人负责——而 ACID 和 BASE,就是两种完全不同签法的契约。 本文不从「ACID 是哪五个单词」讲起——那部分背一下就行。这里讲的是这两种契约各自承诺了什么、为此付出了什么代价,以及在真实系统里它们各自被用在哪个位置。 一、它从哪来ACID 这个词是 1983 年造出来的。 1983 年,德国学者 Theo Härder 和 Andreas Reuter 在论文《Principles of Transaction-Oriented Database Recovery》里,把当时已经积累起来的一堆「事务应该满足什么」的共识,归纳成了一个缩写。但真正...
停等与滑动窗口:让管道不空转
先看三个真实场面: 一个文件传到一半突然变慢,抓包发现发送方每发一个包就停下来等 ACK——不是网络差,是窗口开得太小; 一个跨机房接口,明明两边都是千兆网,实测吞吐只有几 Mbps,排查半天发现瓶颈是RTT 太大而窗口没跟着调; 面试时被问「TCP 怎么保证可靠传输」,答了「超时重传」,对方追问「那如果每一段都要等确认,网络岂不是白买了一半」——答不上来。 这三个场面指向同一条规律:只要一条链路上「只有一份数据在飞」,那么链路的实际吞吐就由 RTT 决定,而不是由带宽决定。而滑走这条限制的方法,叫滑动窗口。 一、它从哪来这一切要从自动重传请求(ARQ, Automatic Repeat reQuest)说起:发送方发出数据后,如果超时没收到确认,就重发。最早的方案叫停等(Stop-and-Wait)——发一帧,等一个 ACK,收到再发下一帧。 它的实现极其简单,所以被早期链路协议广泛使用:IBM 的 BSC(二进制同步通信)、1977 年的 XMODEM(那个年代拨号上网传文件用的协议)都是停等式的。实现的简单,换来的是效率的灾难——后面会用具体数字说明。 滑动窗口(Sli...
引用计数与垃圾回收:谁来负责释放
先看三个真实场面: 一段 C++ 代码用 shared_ptr 管对象,逻辑上没问题,但两个对象互相持有智能指针,内存一路涨到 OOM; 一个 Java 服务,平时 P99 是 20ms,每次 Full GC 时飙到 800ms,业务方以为是网络抖动; 一段 Python 代码,with open(...) 一离开作用域文件就关了,但一个自定义缓存类里的对象却要等到「某个不确定的时刻」才被清掉。 这三个场面看起来分别是「C++ 智能指针用错」「JVM 调优」「Python 语法」,但它们指向的是同一个问题:一块内存什么时候可以被释放,以及由谁来决定? 两种主流的回答方式——引用计数和追踪式垃圾回收——给出了两套完全不同的答案。 本文讲的不是「哪种 GC 算法更好」,而是这两种机制各自的成本结构:为什么有的语言选了引用计数、有的选了追踪式 GC,以及现代语言为什么大多是「混合体」。 一、它从哪来很有意思的是,这两种方案诞生在同一年:1960 年。 引用计数(Reference Counting):1960 年,George E. Collins 在《A Method for ...
缓存一致性:多核与多副本的同一个问题
先看三个真实场面: 一段多线程代码,把两个线程各改一个计数器,逻辑上互不相干,实测却比单线程还慢——把两个变量之间塞几个没用到的字节进去,反而快了好几倍; volatile 明明只是为了禁止编译器优化,为什么在多核上还能保证可见性; 一个热点商品缓存,两个业务共用了同一个 key,一个业务高频刷新,另一个业务的命中率莫名其妙掉到底。 第三个场面看起来是「Redis 用错了」,第一个看起来是「CPU 微架构」,中间那个看起来是「Java 内存模型」。但这三件事是同一个问题:同一份数据存在多份拷贝,写了一份之后,谁负责让其它拷贝知道? 本文讲的这条原则就是回答这个问题的——它不叫「Redis 缓存一致性」,它的本名是 cache coherence(缓存一致性),是几十年前多核处理器为了共享内存而发明的一整套协议。 一、它从哪来要让多个核共享内存,缓存就必须解决一个矛盾:缓存的意义是「让每个核都有一份副本,各自快速访问」,但副本一多,「谁的是最新的」就成了问题。 这个问题在 1960 年代末就已经出现(1967 年 Maurice Wilkes 提出 cache 的概念时,就在论...
鸽巢原理:从哈希冲突到抽屉
先看三个在工程里反复出现的场面: 用「随机生成的 8 位十六进制串」当订单号,跑了一段时间开始出现主键冲突——随机不是不重复; Java 的 HashMap 默认容量 16,理想情况能放 16 个,可它在放入第 13 个元素时就扩容了; 数据库给某个字段加了唯一索引,上线后偶发插入失败,排查半天发现是「业务上默认唯一的那个编号,其实并不唯一」。 这三件事背后是同一条规律:只要你的「格子」比「东西」少,就必然有格子装两件东西。它不依赖随机性、不依赖实现细节、也不需要概率——是纯粹的计数结论。这条规律就是鸽巢原理。 本文不从「抽屉原理的定义」讲起——那在离散数学课上讲过。这里讲的是它作为工程上一条不可绕开的约束:它怎么决定了哈希表的内存开销、怎么变成了 HashDoS 的攻击面、以及为什么「看起来随机」的系统照样会撞。 一、它从哪来这条原理的正式提出者是 Peter Gustav Lejeune Dirichlet(狄利克雷),时间是 1834 年。他在研究用有理数逼近无理数的问题时,需要一个「必然存在」的结论——不是「大概率存在」,而是「一定存在」。他在论文里把它命名为 Sch...
墨菲定律:会出错的总会出错
「墨菲定律」大概是所有工程格言里被引用最多、被认真对待最少的一条。 它的日常用法是这样的:「今天演示肯定出问题,墨菲定律嘛。」——说完就过去了,像一个用来消解焦虑的段子。然后演示果然出了点小问题,大家哈哈一笑,散会。 但如果只把它当段子,就错过了它真正有用的那一半。因为这句话在工程语境里其实是一个关于概率的断言,而概率可以被计算。一旦你能计算它,它就从「自我实现的悲观预言」变成一条可以量化、可以设防的设计约束。 本文讲的是工程意义上的墨菲定律:它最初说了什么、为什么它会成立、它给出的那个具体判据("有几个方式做一件事")、以及它怎么变成了可靠性工程里一整套具体做法。 一、它从哪来这句话的出处没有想象中古老。 1949 年,美国空军在加州 Muroc 干湖(后来改名叫爱德华兹空军基地)做一项叫 MX981 的项目,测试人类对突然减速的耐受极限——通俗说,就是"人能被刹车刹到什么程度"。参与测试的工程师里有一位叫 Edward A. Murphy Jr.(爱德华·墨菲),当时是莱特航空发展中心的试飞工程师。 故事的版本有好几个,最广为流传的一版...
大 O 复杂度:增长率的语言,不只是面试题
先看三个真实反复发生的场面: 一段跑得飞快的代码,上线后数据量涨了 100 倍,直接超时——代码一个字没改; 一个"优化"把平均耗时从 3 毫秒降到 1 毫秒,但 P99 反而变差了——因为新的实现有更差的最坏情况; 面试里能默写"快排平均 O(n log n)、最坏 O(n²)",但在实际项目里从没算过自己写的查询嵌套是几阶。 这三件事的共同点:大 O 不是用来背结论的,它是用来回答"规模变化时,代价怎么变"这个问题的语言。 会背结论和会用这门语言,是两件事。 本文不再从"排序算法对比表"讲起——那部分在 CS 课程里已经讲过。这里讲的是复杂度作为一门工程语言:它到底说了什么、没说(也说不了)什么、以及它怎么变成实际系统里的性能判断和容量决策。 一、它从哪来大 O 记号的数学源头是 1894 年,德国数论学家 Paul Bachmann 在《解析数论》里首次引入 O 记号,用来描述函数增长的上界;1909 年 Edmund Landau 系统化地使用它,所以它在欧洲常被称为 Landau 记号。...
布鲁克斯定律:给延期项目加人只会更晚
1975 年,一本讲操作系统的书里,有一章不是讲技术,而是讲人。它的名字后来成了软件工程里最常被引用、也最常被无视的一句话: Adding manpower to a late software project makes it later.向已经延期的软件项目增加人力,只会让它更晚。 这句话之所以反直觉,是因为它违背后勤部式直觉——活多了就加人,这有什么问题? 于是几乎每一个延期的项目都会重演一遍它:加人、更晚、再加人、再更晚,直到某天有人想起这本书。 这就是布鲁克斯定律(Brooks's Law)。它讲的是沟通成本随人数增长的非线性——一条在分布式系统、组织设计、乃至微服务拆分里反复出现的规律。 一、它从哪来Frederick P. Brooks Jr.(弗雷德里克·布鲁克斯,1931–2022) 是 IBM 在 1960 年代最著名的操作系统项目 OS/360 的项目经理——那是一个投入了上千人年、最终交付但严重超期超预算的项目。 1975 年,他把这段经历写成了 《The Mythical Man-Month》(人月神话),全书的核心洞察都围绕一个词...
帕金森定律:工作会膨胀到占满时间
先看两个看上去毫不相干的场景: 你在 SELECT * 后面加了一个 LIMIT 10,页面的响应时间没有变好——因为前端已经在等那个 2 秒的接口了,接口慢一点快一点,用户感受到的都是 2 秒。 团队里三个人的工作量,两个人的团队也能扛——不是因为那三个人在摸鱼,而是因为工作会自己长出来:检查更多、文档更全、会议更密、评审更细。 这两个场景背后是同一条规律:一件事所消耗的资源,会膨胀到把可用资源全部占满——不管那件事实际需要多少。 这就是帕金森定律(Parkinson's Law)。它常被当成一句吐槽官僚主义的俏皮话,但在工程上,它是资源分配与性能优化里最能解释"为什么优化没效果"的那条规律。 一、它从哪来1955 年 11 月 19 日,英国历史学家、海军史学者 Cyril Northcote Parkinson(西里尔·诺斯古德·帕金森) 在《经济学人》(The Economist)上发表了一篇讽刺短文《Parkinson's Law》,开篇第一句就是那句名言: Work expands so as to fill the time...
梅特卡夫定律:网络价值与 n² 的迷思
在讲这条定律之前,先看一个所有做系统的人都遇到过的场景: 你设计了一个服务,一开始很轻松。连接数从 10 涨到 100 涨到 1000,成本几乎不涨——直到某个时刻,它突然开始崩,而且不是线性地崩,是断崖式地崩。加机器、调参数都只能往后拖一点。 这里藏着一个容易被误读的数字:n²。梅特卡夫定律说网络的价值正比于 n²,这句话被用来给一切"连接更多用户"的商业故事做背书。但它其实同时在讲另一件事,而这件事对工程师更重要:任何"个人 × 个人"的互动关系,规模翻倍,交互总量就变成四倍。 价值是这样涨的,成本也是。 一、它从哪来梅特卡夫定律(Metcalfe's Law) 得名于 Robert Metcalfe(罗伯特·梅特卡夫)——以太网的发明者之一、3Com 的创始人。 关键年份是 1980 年。这一年他提出用一条简单的关系来说服客户买网卡: 网络的价值与联网用户数的平方成正比(V ∝ n²)。 这个论断的推导极其朴素:如果网络里有 n 个人,那么可能的两两连接数是 n(n-1)/2,约为 n²/2。每当接入一个新用户,他带来的潜...
JIT:让程序在奔跑中变快
同一个函数,同一台机器,同一份代码,只改了启动参数——吞吐差了 13 倍: 12345运行模式 数值热循环 次/秒------------------------------------------------默认(JIT 全开) 198,656--no-opt(禁用优化编译器) 28,936 0.14x--jitless(纯解释器) 15,281 0.08x 更耐人寻味的是逐次调用的耗时——第 21 次还是 82.8 µs,第 22 次就掉到 5.0 µs,中间没有任何代码变化,只是编译器在程序运行的过程中"决定"给它生成一份更快的机器码。 这就是 JIT(Just-In-Time compilation,即时编译)最反直觉的地方:它把编译从"运行之前"挪到了"运行之中",用运行时的真实信息换更激进的优化。 一、它从哪来JIT 的直系祖先是动态语言的实现者——因为只有他们非这么做不可。 1960 ...
契约式设计:把应该写成必须
几乎每个接口文档里都有这样几行字: amount 必须是正整数。调用前请确保余额充足。 这几行字通常出现在三种地方:注释、文档站、以及没有人读的 PR 描述里。它们的共同命运是——写下那一刻是对的,三个月后就不是了。代码改了,注释没改;调用方以为有人在检查,被调方以为调用方会守规矩。 契约式设计(Design by Contract, DbC)就是对这行字的回答:把"应该"写成"必须",并且让机器来检查。 一、它从哪来1985 年前后,Bertrand Meyer 在设计 Eiffel 语言时提出了 Design by Contract,并在 1992 年的 IEEE Computer 论文《Applying "Design by Contract"》里系统阐述。 它的概念骨架只有三件东西: 名称 Eiffel 关键字 含义 谁负责保证 前置条件 precondition require 调用这个方法之前必须为真的条件 调用方 后置条件 postcondition ensure 方法返回时必须为真的条...
快速失败:让 Bug 早点现形
三个你大概都见过、但很少连在一起看的现象: 一个金额字段解析失败,于是 except: return 0.0 —— 脏数据变成了 0,报表照样出,没人知道; 数据库列是动态类型,'N/A' 就这么写进了 REAL 列,几个月后 SUM(amount) 已经少算了; 生产环境用 -O 启动 Python,所有 assert 校验被静默剥掉,而没人记得这件事。 它们的共同点不是"出错了",而是错误没有被消灭,只是被推迟了——推迟到了一个完全没有上下文的地方。 一、它从哪来1985 年,Tandem Computers 的 Jim Gray 在《Why Do Computers Stop and What Can Be Done About It?》里给出了一对概念: fail-fast module:要么产生正确结果,要么立刻发出失败信号——绝不产生"看起来正确"的结果。fail-stop processor:出错就停,而不是带着错误状态继续跑。 这是容错计算的地基。逻辑很朴素:冗余要能接管,前提是故障可被检测。一个&q...
最终一致性:不一致是常态,收敛才是目标
三个副本,写了 300 次,然后什么都不做: 123r1 本地 counter = 103r2 本地 counter = 99r3 本地 counter = 98 三个数不一样。如果你以为"最终一致"的意思是"放着不管,过一会儿它们自己就一样了",那这三个数字就是反例——晾 1000 轮也不会变。 最终一致性被误解得最厉害的地方就在这个"最终"上。它不是"时间会治愈一切",而是一份有条件的承诺:在停止写入、并且修复机制跑完之后,所有副本会收敛到同一个值。 注意后半句——修复机制必须存在。 一、它从哪来最终一致性的谱系,比大多数人以为的要长。 1975 年,Johnson 与 Thomas 在《The Maintenance of Duplicate Databases》(RFC 677)里提出用时间戳来解决副本冲突——这是 Last-Write-Wins 的雏形,也是"让副本自己收敛"这一思路最早的落地。 1987 年,Xerox PARC 的 Demers 等人 发表《Epi...
鲁棒性原则(Postel):接收要宽松,发送要严格
同一个 HTTP 请求,链路上两个解析器给出了两个答案: 前端(取第一个 Content-Length)认为消息体是 6 字节,解析完一个请求,剩下的当垃圾丢掉; 后端(取最后一个 Content-Length)认为消息体是 13 字节,把多出来的 7 字节当成了下一个请求的开头。 两边都没报错,都觉得自己很宽容,都"成功"处理了请求。而攻击者要的就是这 7 个字节的差值。 这就是鲁棒性原则(Robustness Principle)——"接收要宽松,发送要严格"——最经典的一张脸,也是它从互联网地基变成安全漏洞清单的完整弧线。 一、它从哪来1980 年,Jon Postel 在 TCP 的早期规范 RFC 760 里写下这句现在被引用了半个世纪的话;1981 年的 RFC 793(TCP) 把它正式列为一条通用原则: TCP implementations will follow a general principle of robustness:be conservative in what you do, be liberal ...
写前日志 WAL:先记账、后落库
00:00:03.412 你点了"支付",页面弹出"付款成功"。 00:00:03.415 三毫秒后,这台数据库服务器的电源被拔了。 重启之后,这笔钱还在不在? 如果答案是"看运气",那你的系统大概率还没读懂一条被用了四十多年的原则——写前日志(Write-Ahead Logging,WAL)。它解释了一堆看起来互相矛盾的现象:为什么 MySQL 敢承诺"事务已提交就绝不回退",而 Redis 回了 OK 却仍可能丢数据?为什么 SQLite 一开 WAL 模式,目录里就冒出 -wal、-shm 两个文件?为什么 PostgreSQL 的备份要归档一个叫 pg_wal/ 的目录?为什么 Kafka 干脆把"日志"当成了唯一的数据结构? 先看三个真实存在的东西:MySQL 的 ib_logfile0、PostgreSQL 的 pg_wal/、etcd 的 member/wal/。它们是三个不同系统的命根子,却长着同一副骨架。 一、它从哪来WAL 不是谁灵光一现的设计,它是从扇区的物理...
康威定律:架构复制沟通结构
面试里有个问题特别刁:"你们的微服务是按什么切的?" 答"按业务能力"的人很多,但追问一句就露馅了——为什么订单服务刚好是这五个人在维护?为什么"用户中心"和"账户中心"是两个人分别负责,而不是一个人管全? 再看两个更具体的现象:两个团队共用一个数据库,加一个字段要等两周;一个模块三个人都在改,谁都不敢重构,因为它没有主人。 这些都不是技术问题。它们是同一条规律在组织层面的投影——康威定律。它最扎人的地方在于:你设计的架构,其实早就被你和同事的沟通方式决定了。 一、它从哪来1967 年,Melvin Conway 写下一篇短文 《How Do Committees Invent?》,1968 年 4 月发表在 Datamation 杂志上。核心结论只有一句(原文): Organizations which design systems … are constrained to produce designs which are copies of the communication structur...
最小权限原则:安全的第一道闸
三个一眼就该报警的现象: 容器默认以 root 跑,挂载着宿主机目录,还带着 CAP_SYS_ADMIN; 一个"只负责查订单"的服务,它的数据库账号有 ALL PRIVILEGES ON *.*; 运维的 sudoers 里写着 ops ALL=(ALL) ALL——看起来是配置了权限系统,实际上等于没有。 它们背后是安全领域里少数"不用花钱、不用加机器、不用改架构"就能大幅降低损失的原则:最小权限(Principle of Least Privilege)。它最值钱的地方在于——它防的不只是攻击者,更是任何单一环节的错误。 一、它从哪来1975 年,Jerome Saltzer 与 Michael Schroeder 在 Proceedings of the IEEE 上发表 《The Protection of Information in Computer Systems》——安全工程史上被引用最多的论文之一。论文提出了 8 条保护机制设计原则,第四条就是 Least privilege,大意是: 每个程序和每个用户,都应当使...
最少惊讶原则:接口设计的隐形标准
来几个不用查文档就能"猜错"的问题: [1, 10, 2].sort() 的结果是什么?(不是 [1, 2, 10]) Python 里 data.sort() 返回什么?(不是排好序的列表,是 None) git checkout 到底干什么?(切分支、丢改动、取文件、建分支——四件事) 一个接口返回 HTTP/1.1 200 OK,body 里写着 {"code": 500},算什么?(算事故) 这些行为都不是 bug,每一个都能找到理由。但它们都要让使用者停下手里的事,去记住一条"反直觉的规则"。这就是最少惊讶原则(Principle of Least Astonishment, POLA)要说的事——它管的不是功能对不对,而是接口是否可信。 一、它从哪来POLA 没有唯一的提出者,它是从人机交互(HCI)到编程语言、API 设计里慢慢固化成共识的一条准则。它的措辞很朴素:系统的行为,应该与使用者基于既有经验所形成的预期一致。 在工程界留下脚印的几个节点: 人机交互传统的系统论述:界面设计...
背压:慢消费者的保护机制
线上有个很常见的死法:上游一慢,下游先崩。 下游没做错什么,它只是老老实实收下所有请求,放进队列,然后慢慢处理。结果队列越来越长,内存越来越满,延迟越来越高,最后 OutOfMemoryError 或者一次超时雪崩——监控图上,崩溃前那段"吞吐还挺好看"的时间,往往就是队列在替它挨打。 再对比两个现象:Kafka 的消费者处理不过来时,Broker 一点事没有,只是 lag 在涨;而写一个死循环 send() 的客户端,对着一个不读的服务端狂发数据,它自己会被卡住,对端安然无恙。 它们背后是同一件事:背压(back pressure)——把下游的"我接不动了"传回上游。 一、它从哪来"背压"这个词是从流体力学借来的:管道下游堵住,压力会反推到上游。计算机里最早的工业级实现不是某个框架,而是网络协议本身: 1981 年,RFC 793(TCP) 就用滑动窗口做了流量控制:接收方通过 rwnd(receive window)告诉发送方"我还能收多少",窗口归零时发送方必须停下等。这是背压最朴素也最可靠的...
CAP 定理:分布式的取舍三角
面试官抛出一个经典问题:设计一个跨机房的订单系统,一致性和可用性你选哪个?你答"我都要"。对方笑而不语。 再问:为什么 ZooKeeper 一选主,整个集群就拒绝写入,而 Eureka 明明发现一半节点失联了,还照样对外返回服务列表?为什么 Redis Cluster 默认一个槽位不可用就整集群报 CLUSTERDOWN?为什么 MySQL 主从复制可以在超时后"降级"成异步? 这四个问题背后是同一个东西——CAP 定理。它大概是分布式领域被引用最多、也被误读最多的一条原则。这一篇把它从猜想讲到定理,再讲到十二年后作者本人的纠偏,最后落到真实的选型现场。 一、它从哪来CAP 不是一开始就叫"定理",它最初是个猜想。 2000 年 7 月,UC Berkeley 的 Eric Brewer 在 ACM 的 PODC 会议(Principles of Distributed Computing)上做主题演讲,提出一个判断:一个分布式系统无法同时满足**一致性(Consistency)、可用性(Availability)、分...
空间换时间:哈希、缓存与索引的共同母题
面试官问:一张十万行的表,怎么让某条查询从秒级变毫秒级?你答"加索引"。再问:Python 里为什么 dict 查一个键比 list 从头扫快那么多?你答"哈希"。又问:为什么同样的热数据,要再往 Redis 里放一份?你答"缓存"。 三个答案听着是三个领域,骨子里却是同一句话——多花一点空间,少花一点时间。哈希表、数据库索引、缓存层、动态规划的记忆化……它们都是同一个母题的不同分身。这一篇就把这条"无处不在却总被当成本能"的原则拆开看。 一、它从哪来"空间换时间"(space–time tradeoff)不是某个人在某一年提出的单一理论,而是计算这门手艺里最古老的经验之一,线索能拉得很长: 查表法:在没有计算机的年代,水手算航海位置要靠对数表——把对数预先算好印成册子,用时翻书而不是现场算。这是最朴素的空间换时间:一本书的纸张空间,换掉每次航行里的重复计算。 哈希表:1953 年,IBM 的 Hans Peter Luhn 在内部备忘录里提出用"散列"把键映...
幂等性:让重试变得安全
深夜线上商城报了个工单:用户付款时手机卡了五秒,他以为没付上,又点了一次"确认支付"。结果呢?如果后端没有幂等保护,这一笔订单会被扣两次款——用户炸了,客服忙了一整晚,最后还得走退款流程。问题不在用户手快,也不在网络抖动,而在于:重试这件事,在分布式系统里根本躲不掉。 网络会超时、进程会重启、消息会重复投递,任何一个环节出问题,客户端都会"再试一次"。所以真正的问题从来不是"要不要重试",而是:重试发生时,系统能不能保证副作用只发生一次? 这就引出了今天的主角——幂等性。 一、它从哪来"幂等"(idempotent)这个词最早来自抽象代数:对某个二元运算,若元素满足 x · x = x,就称它是幂等元。19 世纪数学家本杰明·皮尔斯(Benjamin Peirce)在 1870 年首先使用了这个概念。 它被引进计算机世界,则是分布式系统发展的必然。网络不可靠是铁律(1984 年端到端原则论文里就论证过"中间网络只做尽力而为"),于是超时重试成为所有可靠通信的标配——但&q...
端到端原则:TCP 为什么把可靠性放在两端
先问一个"熟到没人问"的问题:你在浏览器里下载一个 1 GB 的文件,TCP 保证它一个字节都不差地到达。可这 1 GB 数据要穿过几十台路由器、交换机,中间任何一台设备都没有帮你校验文件内容——它们只低头转发,丢了包也不管。 那这份"不差一个字节"的保证,到底是谁给的?答案出人意料:是发给你文件的那台服务器和你这台电脑,两端的操作系统。 一、它从哪来1984 年,MIT 的 Jerome Saltzer、David Reed 与 David Clark 发表了一篇论文《End-to-End Arguments in System Design》(系统设计中的端到端论证),提出了后来被称作**端到端原则(End-to-End Principle)**的思想。 背景是一场大争论:当时的通信网络分成两派。一派认为网络应该"聪明"——由中间节点(交换机、路由器)负责可靠性、纠错、乱序重组,终端只负责收发;另一派认为终端才应该负责这些,网络只做"尽力而为"的转发。ARPANET(互联网前身)早期走的是&quo...
二八定律(Pareto):热点总是集中
线上出了个诡异的事故:一台网关机器 CPU 跑满,报警刷屏,可负责的同事查了半天,发现流量并没有暴涨——总量跟平时差不多。最后抓包一看,全网 1000 多个接口里,有 3 个接口扛走了 82% 的请求,而其中 1 个接口自己就占了一半。平时没人细看,因为总量不大;可一旦那几个热点接口抖动,整台机器就跟着抖。 这不是偶然,而是一条反复出现的规律:少数的东西,总是贡献了大部分的结果。 一、它从哪来1896 年前后,意大利经济学家 维尔弗雷多·帕累托(Vilfredo Pareto) 在研究财富与土地分配时发现:意大利大约 20% 的人口拥有约 80% 的土地。他把这个"少数占大头"的观察推广到更多领域,后人便称其为帕累托法则(Pareto Principle),俗称二八定律。 真正把它带进工程与管理世界的,是质量大师约瑟夫·朱兰(Joseph Juran)。1951 年他在《质量控制手册》中提出"关键少数与琐碎多数"(vital few and trivial many)的思想:质量缺陷并非均匀分布,少数几种缺陷类型造成了大部分损失——所以改进...

