[web基础]动态web服务的发展三阶段
[web基础]动态web服务的发展三阶段
web开发基石,我和gpt的对话:https://chatgpt.com/share/68a86863-5a84-8001-bae9-4a99be63fe81
从CGI到Servlet到应用即服务器
Web 开发的本质演进史,本质上是:“请求如何进入程序、程序如何常驻内存、资源如何被复用” 的演进史
1、Web 服务器的历史位置
| 软件 | 出现时代 | 核心定位 | 历史角色说明 |
|---|---|---|---|
| Apache Http Server,官网:https://httpd.apache.org/ | 1995 | 通用 Web Server | CGI / FastCGI / 模块化 Web Server 的代表 |
| Nginx,官网:https://docs.nginx.com/ | 2004 | 高性能反向代理 / Web Server | 解决 C10K,更偏“请求转发与网关” |
| Tomcat,官网:https://tomcat.apache.org/download-90.cgi | 1999 | Java Servlet 容器 + Web Server | Web Server 与应用运行时合体 |
| Spring Boot | 2014 | 应用框架 + 内嵌服务器 | 应用即服务器的代表,现在go有Gin框架 |
一个重要事实:Web Server 并没有消失,而是逐渐从“业务执行者”退化为“流量入口 / 网关”。
2、动态 Web 服务的发展三阶段(总览)
第 1 阶段:CGI
Web Server → fork 进程 → 执行脚本 → stdout 返回
第 2 阶段:容器化(FastCGI / Servlet)
Web Server → 长驻进程 / 容器 → 线程池处理请求
第 3 阶段:应用即服务器
应用 = Web Server + 框架 + 运行时核心理念变化:
- 进程模型:
每请求 fork → 长驻进程 → 线程池 / 协程 - 请求分发:
PATH/脚本自解析 → 容器统一路由 → 框架级路由 - 部署方式:
脚本文件 → WAR → 单一 JAR / 二进制 - 控制权中心:
Web Server → 容器 → 应用本身
第1阶段:CGI ——动态 Web 的起点
CGI 到底是什么?
CGI(Common Gateway Interface)本质是一份约定:
Web Server 如何把 HTTP 请求交给一个“外部程序”处理!
典型流程:
HTTP 请求
↓
Web Server 解析协议
↓
fork / exec CGI 程序
↓
通过 stdin / env 传请求
↓
CGI 程序 stdout 输出 HTTP Body
CGI 的历史意义
我认为 CGI 的价值在于:
第一次把“HTTP 请求 → 程序响应”标准化
实现了极致解耦
- Web Server 不关心语言
- CGI 程序不关心 Web Server
这直接催生了:
- PHP
- Perl CGI
- Shell CGI
- Python CGI
Web 从“静态文件”迈入“动态内容”的关键一步,开启了动态网站时代!
CGI 的致命缺陷
- 每请求1次一个新进程(fork/exec)
- 无状态、无资源复用
- 数据库连接、缓存全部重建
- 并发能力极弱
一句话总结:CGI 的设计思路是对的,至少在那个年代确实好用,但进程模型在后边的高并发时代完全不可扩展。后来的演进(FastCGI、Tomcat、Gin…)都在解决这两个痛点:
1、让应用常驻内存
2、让请求处理可以复用资源(线程、连接池、缓存)
- 比如:现代 Web 框架的目标是“把应用和协议栈结合起来,避免中间多余的进程开销”!
第2阶段-part1:CGI 的过渡方案:FastCGI / Web Server Modules(承上启下)
为了解决 CGI 的性能瓶颈,业界出现了两条路线:
路线1:Web Server 模块化(Apache Modules)
通过函数调用替代 CGI 的进程调用
把语言运行时作为模块加载进 Web Server 进程内,通过函数调用替代 CGI 的进程调用【比如mod_php本质就是:PHP 解释器作为一个库,被加载进 Apache 进程,常驻内存】
- 注意:不是把你写的 PHP之类的业务代码整体编译进 Apache,编译进去的是「语言解释器 / 运行时」
例如:
- mod_php = 把 PHP 解释器当成一个共享库(.so),直接
dlopen到 Apache 进程里。 - mod_perl
- mod_php = 把 PHP 解释器当成一个共享库(.so),直接
CGI 时代(php-cgi)
Apache
└── fork
└── php-cgi 进程
└── 解释你的 PHP 脚本Apache + mod_php
Apache(单进程 / 多线程)
└── 直接调用
└── PHP 解释器函数
└── 执行你的 PHP 脚本差别的本质:
| 维度 | CGI | mod_php |
|---|---|---|
| PHP 运行位置 | 外部进程 | Apache 进程内部 |
| 调用方式 | 进程通信 | 函数调用 |
| fork 开销 | 有 | 没有 |
| 内存隔离 | 强 | 无 |
优点:比原始CGI快一点
缺点:
安全问题
- Apache 进程 = PHP 进程
- 任意 PHP bug ≈ Apache 崩溃 / RCE
- 多租户几乎不可控
资源隔离极差
- Apache MPM 与 PHP 运行模型强耦合,mod_php解释器崩溃的话:Apache 一起死
架构不清晰
- Web Server
- 应用运行时
- 业务代码
- 全部搅在一个进程里
路线2:比如FastCGI:FastCGI 可以看做上面设计的一次“回摆”
可以理解为:
FastCGI = 把 mod_php 从 Apache 进程里“拆出去”
Nginx / Apache
└── socket
└── php-fpm(PHP 运行时)
└── 执行业务代码
Web Server → FastCGI 进程池 → 处理请求
保留了长驻进程,不再 fork 新进程
使用php-fpm池
恢复了进程隔离:Web Server 通过 socket 把请求转发给 FastCGI 进程
- 牺牲一点 IPC,换来可维护性
php解释器崩溃的话:那么只有PHP-FPM自己挂掉
PHP-FPM 是 FastCGI 的集大成者
第2阶段-part2:Java的Servlet 容器 —“应用容器”正式登场
- 因为Java在国内太主流了,所以当做单独阶段中的1个典型来讲,**这一阶段的本质:**把“处理请求的程序”升级为“长期运行的应用容器”。
- 除了主流这个事情,为什么 Java Servlet 是一个分水岭?因为它系统性地解决了 CGI 的全部问题,而且是标准化方案!【标准化对工业化是很重要的!】
Tomcat 作为一个 Java Servlet 容器,其核心设计
- 常驻进程:Tomcat 本身就是一个长驻的 JVM 进程,不存在“每请求启动程序”
- 内嵌协议栈:Tomcat 自己实现了 HTTP 解析、连接池、线程池,不依赖外部 Web 服务器
- 统一生命周期管理
- 线程池复用
- 应用模块化(WAR)
- 内存共享:所有 Servlet/Filter/Handler 运行在 同一个 JVM 内存空间,可以共享对象、缓存、数据库连接池
- 事件驱动/多线程:一个进程里开很多线程,线程复用,避免 fork/exec 的开销
- 应用为主导:请求来了直接进入 Servlet 的 service() 方法 → doGet/doPost,Web Server 不再是“调用外部程序”,而是应用容器直接调度
请求路径变成:
Socket → Connector → Thread → Servlet.service()Servlet 容器的革命性变化
| 维度 | CGI | Servlet |
|---|---|---|
| 调用模型 | 进程调用 | 方法调用 |
| 边界 | 进程边界 | JVM 内 |
| 并发 | 进程级 | 线程级 |
| 资源 | 不共享 | 共享内存 |
| 控制权 | Web Server | 应用容器,比如Tomca |
总结:Tomcat 在思想上继承了 CGI(外部程序可处理请求),但在实现上摆脱了 CGI 的进程模型,用容器内的函数调用替代了进程间通信。CGI和Java Servlet的实质差别:调用模型
CGI
- Web 服务器 = 主体
- CGI 程序 = 外部进程
- 调用边界 = 进程边界(stdin/stdout)
Java的Tomcat + WAR:
- WAR 并不是“程序”,而是 “被容器加载的模块”
- Tomcat = 应用容器(Web Server + 应用运行时一体)
- WAR = 容器加载的模块(类文件/资源)
- 调用边界 = 方法调用(Servlet API,运行在同一个 JVM 内存空间)
第 3 阶段:应用即服务器(Application = Runtime)
这一阶段最重要的变化只有一句话:
Web Server 不再是主体,应用自己成为主体
典型的比如Spring Boot / Gin / FastAPI 的共同点
- 内嵌 Web Server
- 应用启动 = 监听端口
- 一个命令即可上线
- 更适合容器化 / 云原生
java -jar app.jar
./server
uvicorn main:app3、架构范式变化对比
| 模式 | 核心主体 | 部署单元 | 并发模型 | 隔离方式 |
|---|---|---|---|---|
| CGI | Web Server | 脚本 | 进程 | OS 进程隔离 |
| Servlet | 容器 | WAR | 线程池 | ClassLoader隔离 |
| 应用即服务器 | 应用 | JAR / 二进制 | 线程 / 协程 | 进程/线程/协程级隔离 |
为什么“应用即服务器”在现在变主流?
- DevOps 友好
- 更加容易开发了,方便开发者迭代,方便公司赚钱。
- 容器天然契合
- 微服务粒度更小
- 技术栈收敛
CGI 教会 Web 如何“调用程序”,Servlet 教会程序如何“长期服务请求”,而应用即服务器,则让程序本身成为 Web 的全部