早年我并不知道Python写的Web应用是怎么部署的,总觉得像PHP、ASP一样,仅仅提供一个语言级别的执行模块,直接嵌入Web服务器运行,甚至于直接对外提供带扩展名的URL都是自然而然的事情。
前一阵学习了Python,总是如别人一样,不自觉的和PHP进行对比。随着学习的逐步深入,更发现PHP的发展受限于其Web出身,恐将来难成正经的通用开发语言。
先说说PHP从历史到今天分别是怎么运行的:
- CGI SAPI:由Web服务器的请求处理进程fork+exec这个CGI,用环境变量单向传递Headers给应用程序,应用程序从stdin读request body,其stdout会被Web服务器作为response headers和body输出给客户端。Web服务器和PHP CGI的关系是父子进程间通信,当应用程序的响应速度较慢时(忙于计算、外部I/O等),会阻塞对应的Web服务器进程/线程;而且因为CGI进程的输入之一是环境变量,导致其只能一次性使用,需要承担频繁fork()的开销
- Apache2Handler等Web服务器模块类SAPI:Apache2调用Handler,并向其传递server_context;Handler利用server_context读到Header、body等内容之后构造起PHP脚本的context,也就是$_SERVER之类,然后开始执行PHP脚本。而脚本执行的输出也直接被Web服务器收集。PHP和Web服务器在同一个进程内执行,处于完全从属的地位。Apache2Handler方式本来可以通过多线程等方式让Apache不受PHP阻塞,但由于PHP社区的坚持,目前常见的Apache2+PHP配置都是prefork MPM的,以至于性能相当一般
- FastCGI SAPI:原理是把每次fork处理一个请求,变成一个不退出的循环,每循环一轮就处理一个请求;多进程listen在同一个socket上,实现并发。PHP-FPM是后来的一个民间补丁项目,提供了更好的进程管理和附加功能,处理完License问题之后,FPM合并入PHP主线。Web服务器和PHP的关系是网络通信的关系,可以分离到不同服务器上部署。Web服务器可以使用多线程或者I/O复用技术来处理别的请求,性能会>比前二者好很多。但FPM的实现仍然不够好。
PHP的各种扩展模块,大都是偏重于数据库、字符串处理等应用层面的功能,充分体现了其作为Web开发利器的特点;而对于I/O、POSIX基础功能,则关注较少。
Web开发之外的领域,用PHP写独立运行的服务器,并不是主流的用法。其依赖的pcntl、pthread等模块也是较晚加进来的,而且也没啥人用。用
- PEAR Net_Server(无人维护) 通用服务器框架
- PEAR HTTP2 协议解析库
- PEAR HTTP_Server(无人维护) HTTP服务器框架
- PECL pecl_HTTP(需要编译) HTTP服务器框架,我没编译出来……
等几个模块,可以很容易的创作一个独立的HTTP静态文件或SOA服务;但如果想在其中运行一般的Web PHP脚本,就需要用PHP本身实现一个SAPI:
- 输入方面是很容易仿制的,除了需要构造Predefined/Reserved Variables(感谢作者,$_SERVER等只是普通数组,只须填充内容即可)再写一下上传文件自动存盘的处理就差不多了
- 输出方面就比较麻烦,因为“写”是动作,是对外部环境施加影响的。考虑到PHP深深的“假设stdout”情结,须使用output buffer机制收集输出内容,副作用是chunked输出的程序会丧失分段效果,变成缓冲输出;header()函数需要用pecl-apd扩展中的override_function()函数,但此扩展却无法兼容当前的ZendAPI,无法编译成功
传统上,协议解析是SAPI的工作,PHP的语言引擎部分从属于SAPI,以至于没积累出什么好用的协议解析库;而PHP书写的Web应用向来都是直接调用SAPI提供的函数,积习难改,以至于想用PHP本身来写Web服务器就须要去实现一个SAPI,但又会遭遇到这个语言本身的Web烙印太深、假设太多,以及不够“动态”的问题。
传统上,网络通信向来也不须由PHP来处理,以至于也没积累出什么好用的通信框架库。
最终,PHP成了一个Web only的开发语言。真可谓成也Web、败也Web。
一些新兴的PECL扩展提供了高性能I/O框架,比如swoole,但因为上述SAPI的问题,目前尚未出现像Python那样用PHP本身写的PHP Web服务器。
相比之下,不用写<%的Python语言,其发展道路就更加general purpose一些:
- 标准库中有os、multiprocess、threading、select等模块,一看就是面向系统编程而非仅仅针对Web开发
- 标准库中的SocketServer及其MixIn classes、HTTPServer等可用于写服务器;CGI模块可用于解析HTTP协议请求
- 标准库中的wsgiref是纯Python的。也就是说,用Python语言写一个WSGI Server很容易且是提倡的做法
- yield语法加入语言比较早,可以实现协程
在Web应用方面,Python的WSGI接口已经基本统一江湖。WSGI不是通信协议,而是语言级别的调用约定(关于这个函数后面带几个什么样的参数的约定),而WSGI可以用纯Python实现,因此不会遇见用PHP实现SAPI的尴尬。由于Python没有像PHP那样深深的Web烙印(甚至早年在Web上的部署并不太方便)加上标准库中很早就带有各种基础模块,导向结果就是出现了很多优秀的高性能框架和服务器软件。现在流行的组合是用 gunicorn WSGI Server运行WSGI应用程序,管理多个worker充分利用多处理器,用gevent消除I/O等待时的浪费;对外提供HTTP服务,前面套一个nginx提供静态文件和访问控制、rewrite等功能。
点赞。不学编码的运维工程师不是好胖子……