CGI 是【每来一次请求就起一个新程序】的做法,它最大的问题不在能不能用,而在把最贵的那一步放在了每次请求里。
2-5 说动态页面要【起一个程序】,但没说起程序这件事本身要花多少代价。这一条讲的就是这个代价,以及为什么后来大家换掉了它。它是本章的一个转折点:前面讲的都是【看到什么】,从这里开始讲【后台怎么把它做出来】。
一、CGI 的做法
CGI 是一套很直白的规矩:服务器把请求的内容变成一组环境变量,启动一个程序,程序把结果从标准输出送回来,服务器再转发给浏览器。
它好懂、好写、语言无关——这正是它当年能流行起来的原因。
问题出在它的执行方式上:每来一次请求,就起一个新进程,干完就退出。
二、动手 Demo:把点火的成本算出来
parts = [("读取脚本文件", 1), ("启动语言解释器", 30), ("建立数据库连接", 20)]
for name, ms in parts:
print("%-16s 约 %2d 毫秒" % (name, ms))
total = sum(ms for _, ms in parts)
print("合计 约 %d 毫秒" % total)
print("每秒 100 次请求:光启动就要 %d 毫秒 -> 需要 %.0f 个 CPU 核心"
% (100 * 30, 100 * 30 / 1000.0))
实跑输出:
--------------------------------------------------------------------
D9 程序的启动:CGI
--------------------------------------------------------------------
静态页面是服务器【读文件】,动态页面要服务器【起程序】。
CGI 是干这件事最早的一套规矩,做法很直白:
服务器把请求变成环境变量,启动一个程序;
程序把生成的结果从标准输出吐回来,服务器再转发给浏览器。
它的问题不在能不能用,在【每来一次请求就起一个新进程】:
----------------------------------------------------------------------------------------
环节 开销 说明
----------------------------------------------------------------------------------------
读取脚本文件 约 1 毫秒 从磁盘把代码读进内存
启动语言解释器 约 30 毫秒 Python 这类要先起虚拟机
建立数据库连接 约 20 毫秒 每次连接都要握手
合计 约 51 毫秒 每次请求都要重来一遍
----------------------------------------------------------------------------------------
51 毫秒里,真正在处理业务的大概只有几毫秒,其余都花在【点火】上。
把它放大到每秒 100 条请求来看:
每秒要起 100 个进程,光启动就是 100 x 30 = 3000 毫秒。
意思是:需要 3 个 CPU 核心什么都不干,专门负责启动进程。
FastCGI 换了个做法:进程先起好、一直待着,来一条请求接一条。
----------------------------------------------------------------------------------------
做法 每次请求的启动成本 一台机器的吞吐
----------------------------------------------------------------------------------------
CGI 约 30 毫秒 每秒数百次
FastCGI 约 0.5 毫秒(复用) 每秒数千次以上
----------------------------------------------------------------------------------------
同样一台机器,吞吐差出一个数量级。这就是 CGI 从主流退场的原因——
不是因为不好用,是因为它把最贵的那一步放在了每次请求里。
⚠️ 2026 现状:CGI 没有死,但它基本只活在三个地方——
遗留系统(还能跑就先不动)、低流量站点(简单胜过高效)、
路由器与摄像头这类嵌入式设备(资源少,够用就行)。
Apache 2.4 至今仍带着 mod_cgi 模块。
今天接替它的,是一整套按语言分家的接口:
----------------------------------------------------------------------------------------
接口 属于哪个语言 为什么快
----------------------------------------------------------------------------------------
FastCGI 语言无关 进程常驻,请求复用
WSGI Python 应用对象常驻,只调一次函数
ASGI Python 同上,还支持异步与长连接
Servlet Java 由容器管理线程池
mod_php PHP 解释器直接嵌在服务器里
----------------------------------------------------------------------------------------
这些名字看着多,说的其实是同一件事:
别为每条请求都重新点一次火。
读这个输出:51 毫秒里真正在处理业务的大概只有几毫秒,其余都花在启动解释器和建连接上。再往下看最后一行——每秒一百次请求,就要拿三个 CPU 核心专门负责启动进程。这个成本不是【有点浪费】,而是把机器的大部分力气用在了重复的准备工作上。
三、后来怎么改的
FastCGI 的思路很简单:别每次重启,让进程一直待着。请求来了就接一条,处理完不退出。这样一来,启动成本从【每次 30 毫秒】降到【几乎为零】。
同样一台机器,吞吐能差出一个数量级——不是因为程序写得更好了,是因为最贵的那一步被挪走了。
今天接替它的是一整套按语言分家的接口:FastCGI(语言无关)、WSGI 与 ASGI(Python)、Servlet(Java)、mod_php(PHP 直接嵌进服务器)。名字看着多,说的其实是同一件事:别为每条请求都重新点一次火。
四、⚠️ 2026 现状
CGI 没有死,但它基本只活在三个地方:遗留系统(还能跑就先不动)、低流量站点(简单胜过高效)、路由器与摄像头这类嵌入式设备(资源少,够用就行)。Apache 2.4 至今仍带着 mod_cgi 模块。
顺带说一句安全上的老账:CGI 的经典漏洞是把用户输入当成命令去执行。这套规矩把【程序】和【输入】混在一起传递,本身就给命令注入留了口子——这也是它被更结构化的接口取代的原因之一。
30 秒实验
- 打开你常用的一个网站,想象它每来一个访问者就要重新启动一遍程序,估一下它每天要启动多少次。
- 再想想这个过程里,哪一部分是每次都一样的准备工作。
- 最后回答一个问题:如果这些准备工作能只做一次,省下来的是时间还是钱?
常见误区
- 错误:CGI 是过时的技术,不用了解。——纠正:它是理解【后台怎么跑起来】这套逻辑的起点。今天用的 FastCGI、WSGI、Servlet,全都是在回答 CGI 提出的同一个问题——只是把答案换成了更省的做法。不了解它,就不知道现在这套做法在省什么。
- 错误:慢一点没关系,反正是后台。——纠正:后台慢会直接变成用户的等待。而且这个慢是按访问次数累加的——一万次访问就是多花一万次启动时间。后台的每一次浪费,都会被访问量放大。
- 错误:只要换成更快的语言就行了。——纠正:问题不在语言快不快,在【每请求起一次进程】这个做法。换成任何语言,只要还是每次重启,那 30 毫秒的启动开销都跑不掉。先改做法,再谈换语言。
- 错误:CGI 只是性能问题。——纠正:它还留下一批经典的安全教训。把用户输入直接交给一个能被外部触发的程序,稍不注意就变成命令注入——这条教训今天仍然有效,只是换了地方出现。
自测三问(点开对答案)
1. CGI 最根本的问题是什么? —— **把最贵的那一步放在了每次请求里**。启动解释器、建数据库连接这些准备工作,每条请求都要重来一遍。**它本身没有做错事,只是把一个应该只做一次的动作,做成了每次都做。** 2. FastCGI 改了什么? —— **让进程常驻**。进程先起好、一直待着,请求来了接一条、处理完不退出。这样启动成本只在最开始付一次。**同样一台机器,吞吐因此差出一个数量级**——改的是结构,不是代码优化。 3. 为什么说这个问题和 2-1 讲的【改得起】是一条线? —— 因为**动态页面要能快速迭代,就不能让启动开销拖住每一次请求**。后台从 CGI 换到常驻进程,本质上和低代码、前后端分离在解决同一件事:**把重复的准备工作从【每次】变成【一次】**,让【改】这件事变得更便宜。- ☐ 下一节(2-10 将客户端与服务器分离:脚本语言)
相关笔记
- 静态页面与动态页面 —— 上一节:动态页面要多走的那几步
- 静态Web网站、动态Web网站、CGI —— 编程课里的 CGI
- 虚拟化与周边设备 —— 服务器这一侧的效率账
- 🕸️ Web技术原理 · 索引