主题
第 1 章:钟摆的暴力 —— Web 演进简史
"History doesn't repeat itself, but it often rhymes."
—— Mark Twain
1. 引言:算力的引力场
计算机科学的历史,本质上是一部**算力(Compute)**在"集中"与"分布"之间不断摇摆的历史。这种摇摆不是随机的,它受制于两个物理常数的博弈:带宽(Bandwidth)与延迟(Latency)。
每隔十年,这个巨大的钟摆就会带着毁灭性的力量砸向另一端,粉碎旧的王者,通过新的霸主。
作为一名 Web 工程师,如果你看不懂这个钟摆,你就会永远疲于奔命地追逐新框架。如果你看懂了,你就会发现,Next.js 今天的"创新",不过是 1970 年代 IBM Mainframe 的一次回响。
2. 史前时代:大型机的幽灵 (1960s - 1980s)
状态:极度集中 (Centralized)
在个人电脑诞生之前,算力是昂贵的稀缺资源。IBM System/360 像神像一样供奉在恒温的机房里,而终端(Terminal)只是一个没有任何计算能力的显示器和键盘(Dumb Terminal)。
2.1 绿屏与块模式 (Block Mode)
你可能在老电影里见过黑客对着满屏绿字的屏幕敲击。那不仅仅是 UI 风格,那是IBM 3270 协议的遗产。
在那个时代,每一次按键都发送给服务器是极其昂贵的(中断处理成本太高)。所以,终端工作在块模式 (Block Mode) 下:
- 主机发送一个表单定义给终端。
- 用户在本地终端填写表单,光标在字段间跳动。此时没有任何数据发往主机。
- 用户按下
Enter键。 - 终端将整屏数据打包,一次性发送给主机。
这听起来是不是很耳熟?是的,这就是 HTML <form> 提交的原型。
2.2 提线木偶的隐喻
在这种架构下,所有的逻辑、状态、渲染都在服务器端完成。终端只负责显示字符。
- 架构模式:Host-Terminal。
- 隐喻:这就像是提线木偶。木偶(终端)的一举一动,完全由幕后的操纵者(主机)控制。
这听起来很遥远?不。看看今天的 Cloud Gaming (云游戏) 或者 ChatGPT。当你的浏览器只负责显示 Token,而所有的智能都在 OpenAI 的服务器上时,我们又回到了大型机时代。
3. 第一纪元:CS 架构与胖客户端 (1990s)
状态:极度分散 (Decentralized)
摩尔定律让个人电脑(PC)的算力爆发。英特尔和微软联手,把算力塞进了每个人的办公桌。既然客户端有了算力,为什么要浪费带宽去服务器请求界面呢?
3.1 胖客户端的崛起
Client-Server (C/S) 架构应运而生。
- 客户端 (Client):运行在 Windows 上的
.exe程序(用 VB, Delphi, C++ 编写)。负责绘制 UI、处理业务逻辑、甚至缓存数据。 - 服务端 (Server):退化为单纯的数据库(SQL Server, Oracle)。
这种架构的用户体验极佳。界面响应是毫秒级的,因为不需要网络请求就能响应鼠标点击。
3.2 部署地狱 (DLL Hell)
然而,这种架构有一个致命的阿喀琉斯之踵:分发 (Distribution)。
想象一下,你是一个银行系统的开发者,你修了一个严重的利息计算 Bug。
- 你需要重新编译
.exe。 - 你需要把这个文件刻录成光盘,或者通过龟速的局域网分发给 10,000 个柜员。
- 如果用户的电脑上缺了一个
MSVCRT.DLL,或者版本不对,程序就会崩溃。这就是臭名昭著的 DLL Hell。
在这个时代,运维成本成为了瓶颈。
4. 第二纪元:Web 1.0 与瘦客户端的复辟 (1995 - 2004)
状态:回归集中 (Centralized)
为了解决"部署地狱",Tim Berners-Lee 的发明被 Netscape 商业化了。浏览器出现了。
突然间,我们又回到了"大型机模式"。
4.1 CGI:胶水代码的时代
Web 1.0 的核心技术是 CGI (Common Gateway Interface)。它不是一种语言,而是一种协议。它规定了 Web 服务器如何把 HTTP 请求转交给外部程序(通常是 Perl 或 C 脚本)。
让我们看一段 1996 年风格的 Perl CGI 代码:
perl
#!/usr/bin/perl
use CGI;
$query = new CGI;
$name = $query->param('user_name');
print "Content-type: text/html\n\n";
print "<html><body>";
print "<h1>Hello, $name!</h1>";
print "</body></html>";这段代码揭示了 Web 1.0 的本质:
- 无状态:脚本运行完就退出了,内存释放。下一次请求一切重来。
- 字符串拼接:整个互联网就是由无数个
print语句拼接出来的字符串。 - 整页刷新:用户点击一个按钮,浏览器必须丢弃当前页面,重新下载并渲染整个新页面。
4.2 为什么这是退步?
从用户体验角度,这是巨大的倒退。相比于 VB/Delphi 写出的原生应用,Web 1.0 慢、卡、丑,每次操作都要白屏。
4.3 为什么它赢了?
因为它解决了分发问题。 更新服务器上的一个 index.pl 文件,全世界用户的软件就瞬间更新了。没有安装过程,没有 DLL 冲突。
在软件工程中,分发效率往往战胜运行效率。这就是为什么 Web 最终吞噬了桌面软件。
5. 第三纪元:Ajax 与 Web 2.0 的反叛 (2005 - 2010)
状态:向客户端倾斜
2005 年 2 月,Jesse James Garrett 发表了那篇著名的文章《Ajax: A New Approach to Web Applications》。他并没有发明新技术,他只是发现了 XMLHttpRequest 这个被埋没在 IE5 中的组件。
5.1 Google Maps 的震撼
在此之前,地图网站(如 MapQuest)是这样的:你想看右边的地图,点击"向右",页面白屏刷新,加载新图片。 Google Maps 发布时,用户发现,拖动地图时,页面竟然没有刷新!
这在当时是魔法。
5.2 幕后的魔法:XMLHttpRequest
让我们看看在 jQuery 诞生之前,原生的 Ajax 代码是多么丑陋,但这丑陋中孕育着革命:
javascript
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
// IE6 的遗产
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// 局部更新 DOM,而不是整页刷新
document.getElementById("content").innerHTML = xhr.responseText;
}
};
xhr.open("GET", "/api/data", true);
xhr.send();- 隐喻变化:网页不再是文档 (Document),而是应用 (Application)。
- 架构影响:服务器不再只吐 HTML,开始吐 XML(后来变成了 JSON)。浏览器开始承担部分渲染逻辑。
6. 第四纪元:SPA 与 胖客户端的重生 (2010 - 2020)
状态:极度分散 (Decentralized)
iPhone 发布,V8 引擎发布。客户端算力再次过剩。AngularJS (2010) 和 React (2013) 登场。
工程师们想:"既然浏览器这么快,我们为什么不把整个应用都搬到浏览器里呢?" 于是,Single Page Application (SPA) 诞生了。
6.1 历史的押韵
SPA 的架构其实就是 90 年代 C/S 架构的复辟!
- Client: 变成了运行在浏览器里的 JavaScript Bundle。
- Server: 退化为 API Server,只吐 JSON。
- Routing: 以前是服务器决定你看哪个页面,现在是客户端 JS 决定。
6.2 分布式系统的谬误
SPA 架构隐含了一个危险的假设:网络是可靠的。 但实际上,网络不仅不可靠,而且有延迟。
- 瀑布流请求 (Waterfall Requests): SPA 加载流程通常是:下载 JS -> 解析 JS -> 请求 User API -> 请求 Posts API -> 渲染。 这导致了严重的 TTI (Time to Interactive) 延迟。
- SEO 死亡:爬虫抓不到内容。
- 首屏性能 (FCP) 崩溃:用户必须下载 5MB 的 JS 文件,页面才能动。
为了解决这些问题,我们发明了 Webpack,发明了 Code Splitting,发明了 Hydration。前端工程变得比后端还复杂。
7. 第五纪元:边缘计算与同构 (2020 - 未来)
状态:量子态 (Superposition)
钟摆又荡回来了,但这次它不再是非黑即白。 Next.js, Remix, Server Components 的出现,标志着 Isomorphic (同构) 时代的到来。
7.1 React Server Components (RSC)
RSC 是这一趋势的终极体现。让我们对比一下 PHP 和 RSC:
PHP (1995):
php
<ul>
<?php foreach ($db->query('SELECT * FROM users') as $user): ?>
<li><?= $user->name ?></li>
<?php endforeach; ?>
</ul>React Server Component (2023):
jsx
// app/users/page.js
async function Users() {
const users = await db.query('SELECT * FROM users');
return (
<ul>
{users.map(user => (
<li>{user.name}</li>
))}
</ul>
);
}看出来了吗?它们几乎一模一样! 我们绕了一大圈,又回到了服务器端直接读数据库渲染 HTML 的模式。
区别在于:
- PHP:每次交互都要刷新整个页面。
- RSC:交互时,服务器只发送 diff 数据,客户端无缝更新,保留状态。
7.2 边缘计算 (The Edge)
服务器不再是远在弗吉尼亚的数据中心,而是离用户 50 公里内的 CDN 节点。 Vercel, Cloudflare Workers 让逻辑在边缘运行。
这打破了"集中 vs 分散"的二元对立。算力既是集中的(由云厂商管理),又是物理上分散的(遍布全球)。
8. 理论核心:带宽延迟积 (Bandwidth-Latency Product)
为什么钟摆会摆动?除了商业因素,背后有深刻的物理原因。
网络性能由两个因素决定:
- 带宽 (Bandwidth):水管有多粗。
- 延迟 (Latency):水管有多长。
- Web 1.0 时代:带宽极小(56k Modem),延迟极高。传输 HTML(纯文本)是唯一可行的方案。
- SPA 时代:4G/光纤普及,带宽不再是瓶颈。但延迟依然受光速限制(物理定律)。 SPA 通过预加载所有代码,消除了交互时的延迟(点击菜单不需要去服务器),但牺牲了首次加载的延迟。
- Edge 时代:我们试图通过把服务器推到用户家门口,来同时解决带宽和延迟问题。
9. 结语:架构师的视角
当你看着这个钟摆来回摆动时,你应该明白:
- 没有完美的架构,只有适合时代的架构。Web 1.0 适合带宽只有 56k 的时代;SPA 适合 4G/光纤时代。
- 分久必合,合久必分。逻辑和数据,总是在"离用户更近"(低延迟)和"离数据更近"(一致性/算力)之间拉扯。
- 不要嘲笑旧技术。JSP 和 PHP 的"服务端组件"思想,在 React Server Components 中借尸还魂。
作为架构师,你的任务不是站队,而是识别当前的物理约束(带宽、延迟、算力成本),然后把钟摆推向最有利的一端。