尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用 gevent 与 web.py 打造可并发的 WSGI 长请求应用:webpy.py 示例全解

用 gevent 与 web.py 打造可并发的 WSGI 长请求应用:webpy.py 示例全解 后端【免费下载链接】geventCoroutine-based concurrency library for Python项目地址https://gitcode.com/gh_mirrors/ge/gevent点击查看免费下载导读本文以 examples/webpy.py 为蓝本讲解如何在 gevent 生态中把第三方 Web 框架 web.py 与gevent.pywsgi.WSGIServer组合起来构建一个长请求互不阻塞的 WSGI 应用。你将掌握web.py 应用如何桥接为 WSGI 应用并交给 gevent 托管、monkey.patch_all()在其中的双重作用、time.sleep(10)为什么不会拖垮其他并发请求以及 gevent 的 WSGIServer 通过 greenlet 实现并发的底层机制。一、示例全景一个由 gevent 驱动的 web.py 应用docs/examples/webpy.rst是一篇示例文档包装页其正文通过literalinclude指令完整嵌入examples/webpy.py的源码见 docs/examples/webpy.rst属于 docs/examples/index.rst 所列出的 18 个官方示例之一。它的技术实质就是仓库examples/目录下的同名脚本——一个用 web.py 编写、由 gevent 的 WSGI 服务器托管的微型 Web 应用。整个程序只有 34 行却完整覆盖了猴子补丁 → 框架路由 → WSGI 桥接 → 长请求并发这条 gevent 集成第三方框架的典型链路。文件头部有一行特殊注释#!/usr/bin/python # gevent-test-requires-resource: webpy A web.py application powered by gevent# gevent-test-requires-resource: webpy是 gevent 测试框架的资源声明运行test__examples.py时会解析这行注释若当前环境没有安装web.py即没有启用webpy这一测试资源对应测试会被自动跳过实现细节见 src/gevent/tests/test__examples.py 的_check_resources方法与 src/gevent/testing/resources.py。运行方式非常简单只需保证已安装web.py与 geventpip install web.py gevent python webpy.py启动后控制台输出Serving on 8088...浏览器访问http://localhost:8088/即可看到Hello, world!页面页面中带有指向/long的链接。二、逐段精读34 行代码的完整拆解2.1 导入与猴子补丁from __future__ import print_function from gevent import monkey; monkey.patch_all() from gevent.pywsgi import WSGIServer import time import web # pylint:disableimport-error两行导入是整个示例的灵魂monkey.patch_all()在程序最顶端、所有标准库与第三方库导入之前执行把标准库中的 socket、time、threading、select 等模块替换为 gevent 的协作式版本。它在本示例中承担双重职责详见下文第四节一是让web.ctx所依赖的线程局部存储变成greenlet 局部存储保证请求隔离二是让 web.py 内部潜在的阻塞 I/O 操作自动变成可挂起的协作式操作。from gevent.pywsgi import WSGIServer引入 gevent 自带的纯 Python WSGI 服务器。它是 gevent 的应用层并发核心每个连接由独立 greenlet 处理这正是本示例能支撑长请求并发的根本详见第三节。2.2 URL 路由与处理器类urls (/, index, /long, long_polling) class index(object): def GET(self): return htmlHello, world!bra href/long/long/a/html class long_polling(object): # Since gevents WSGIServer executes each incoming connection in a separate greenlet # long running requests such as this one dont block one another; # and thanks to monkey.patch_all() statement at the top, thread-local storage used by web.ctx # becomes greenlet-local storage thus making requests isolated as they should be. def GET(self): print(GET /long) time.sleep(10) # possible to block the request indefinitely, without harming others return Hello, 10 seconds later这是 web.py 框架的标准写法urls元组把路径模式映射到处理类index和long_polling分别响应GET /与GET /long。long_polling.GET里的time.sleep(10)是刻意为之的演示性阻塞——它模拟一个耗时 10 秒的长轮询请求在真实场景中这段代码可能是一个长连接、一个慢数据库查询或一次对外部服务的长时间等待。源码注释清晰地说明了设计意图gevent 的 WSGIServer 让每个传入连接运行在独立的 greenlet 中因此像这样长时间运行的请求不会互相阻塞并且得益于顶部的monkey.patch_all()web.ctx 使用的线程局部存储变成了 greenlet 局部存储请求得以按预期方式隔离。这正是整个示例最值得细读的地方用time.sleep(10)这一最简单的语句演示 gevent 最核心的并发模型。在补丁后的世界中time.sleep不再是死等 10 秒而是把当前 greenlet 挂起让出 CPU 给事件循环10 秒后由 hub 调度恢复执行。2.3 启动入口web.py 到 WSGI 的桥接if __name__ __main__: application web.application(urls, globals()).wsgifunc() print(Serving on 8088...) WSGIServer((, 8088), application).serve_forever()web.application(urls, globals()).wsgifunc()是 web.py 提供的 WSGI 桥接 API它把 web.py 的路由表与处理器类转换成符合 PEP 3333 的 WSGI 可调用对象接受environ, start_response两个参数。这一步让 web.py 应用能够被任何 WSGI 服务器托管。随后把 WSGI 应用交给 geventWSGIServer((, 8088), application).serve_forever()(, 8088)表示监听所有网卡接口的 8088 端口application是上一步得到的 WSGI 应用serve_forever()启动服务并阻塞当前主greenlet直到被显式停止。从源码看serve_forever由 src/gevent/baseserver.py 定义语义是如果服务器尚未启动则启动它然后一直等待直到被停止。三、底层机制WSGIServer 为什么能并发处理长请求3.1 一连接一 greenlet 的流式服务器模型WSGIServer定义于 src/gevent/pywsgi.py其文档描述为 A WSGI server based onStreamServerthat supports HTTPS。而StreamServer在 src/gevent/server.py 中的定位是Accepts connections on a listening socket and spawns user-providedhandlefunction for each connection with 2 arguments: the client socket and the client address.在监听 socket 上接受连接并为每个连接 spawn 用户提供的handle函数。也就是说连接的处理是按连接 spawn greenlet的模型每个客户端连接都被包装为一个独立 greenlet 交给 gevent 调度器。WSGIServer的handler_class WSGIHandlersrc/gevent/pywsgi.py每个请求会创建新的WSGIHandler实例而time.sleep(10)只会挂起当前 greenlet事件循环随即调度其他 greenlet 继续执行——于是/long的 10 秒睡眠期间其他请求照常被响应。这就解释了示例注释中long running requests such as this one dont block one another的物理含义阻塞的是单个 greenlet不是整个进程。3.2 WSGIServer 的构造参数与 WSGI 环境从 src/gevent/pywsgi.py 的构造函数签名可以看到WSGIServer支持的关键参数def __init__(self, listener, applicationNone, backlogNone, spawndefault, logdefault, error_logdefault, handler_classNone, environNone, **ssl_args):listener监听地址如(, 8088)或已绑定的 socketapplicationWSGI 应用本示例中由web.application(...).wsgifunc()产出backlog监听队列长度spawngreenlet 生成策略默认default即每连接生成一个 greenletlog/error_log访问日志与错误日志对象默认写到sys.stderr传None可关闭访问日志handler_class自定义请求处理器类environ向 WSGI 环境中追加的自定义字段**ssl_args传入后即启用 HTTPS见wsgiserver_ssl.py示例。服务器为每个请求构造 WSGI 环境时会注入一组base_envsrc/gevent/pywsgi.py其中wsgi.multithread被设为False、wsgi.multiprocess为False——尽管 gevent 的并发来自 greenlet 而非线程或进程base_env源码中甚至保留了一条# XXX: Arent we really, though?的自嘲注释。理解这一点有助于正确编写依赖 WSGI 环境标志的中间件。3.3 适用范围与生产环境定位需要强调的是pywsgi.py模块文档src/gevent/pywsgi.py对自身定位有明确的重要提示This server is intended primarily for development and testing, and secondarily for other safe scenarios... The code has not been security audited, and is not intended for direct exposure to the public Internet. For production usage on the Internet, either choose a production-strength server such as gunicorn, or put a reverse proxy between gevent and the Internet.即WSGIServer主要面向开发与测试场景未经过安全审计不建议直接暴露在公网。生产部署时更稳妥的做法是使用 gunicorn搭配 gevent worker等生产级服务器或在其前方架设反向代理。这一边界在把示例改造为线上服务前务必知晓。四、请求隔离的秘密thread-local 如何变成 greenlet-local示例注释中有一个极易被忽略却极为关键的技术点web.py 框架通过web.ctx一个线程局部存储对象在请求间传递上下文数据而 gevent 的并发单元是 greenlet 而非线程——多个 greenlet 共享同一个 OS 线程如果直接使用原生的 thread-local不同请求的web.ctx就会互相污染。解决方案就是示例顶部的monkey.patch_all()gevent 的猴子补丁会把threading.local等线程局部机制替换为 greenlet 局部的等价实现这一替换发生在 gevent 的monkey包中相关补丁模块位于 src/gevent/monkey/。补丁之后每个 greenlet 一个web.ctx成立请求上下文像线程模型下一样彼此隔离——这正是示例注释所说的thread-local storage used by web.ctx becomes greenlet-local storage thus making requests isolated as they should be.这是把依赖 thread-local 的第三方框架web.py、Django、Flask 等接入 gevent 时最需要理解的一条通用规律请求隔离不再依赖 OS 线程而是由补丁后的 greenlet 局部存储来保证。五、动手验证并发性是可以摸到的示例的并发能力不需要信任任何抽象描述用两个终端即可直观验证终端 Apython webpy.py启动服务终端 B先发起curl http://localhost:8088/long服务端会打印GET /long请求将挂起约 10 秒终端 B 另开窗口或后台执行立刻访问curl http://localhost:8088/。如果一切正常第二个请求会立即返回Hello, world!页面而不是等第一个请求睡满 10 秒——10 秒后/long才返回Hello, 10 seconds later。这一现象背后就是第二节所述的greenlet 挂起让出机制。time.sleep(10)注释中的 possible to block the request indefinitely, without harming others可以无限期阻塞该请求而不影响其他请求正是在强调只要当前 greenlet 让出控制权阻塞时长对其他请求而言没有影响——这是长轮询、SSE、WebSocket 升级等场景在 gevent 下得以成立的基石。六、延伸阅读examples 目录中的同族 WSGI 示例webpy.py并非孤例仓库 examples/ 目录下还有一批同族示例可以对照阅读理解 gevent WSGI 生态的不同写法examples/wsgiserver.py手写一个最简 WSGI 应用def application(env, start_response)直接交给WSGIServer是理解 WSGI 接口本身的最佳起点examples/wsgiserver_ssl.py演示如何通过WSGIServer的**ssl_args参数启用 HTTPS仓库提供了 examples/server.crt 与 examples/server.key 测试证书examples/webproxy.py不依赖任何 Web 框架、纯手写 WSGI 应用实现的 HTTP 反向代理演示了长 URL 改写、响应头过滤DROP_HEADERS丢弃transfer-encoding与set-cookie等实用技巧。与webpy.py对比可见gevent 对 WSGI 应用的接入方式高度统一无论应用来自框架web.py还是手写函数最终都归结为WSGIServer(listener, application).serve_forever()一行。框架差异只体现在如何产出application并发能力则由 gevent 的服务器统一提供。小结webpy.py用最短的篇幅展示了 gevent 集成第三方 Web 框架的完整路径monkey.patch_all()统一线程与 greenlet 语义、web.application(...).wsgifunc()完成框架到 WSGI 的桥接、WSGIServer.serve_forever()以一连接一 greenlet的模型提供并发。其核心启示是在 gevent 的世界里长请求阻塞的永远是单个 greenlet而非整个服务要让依赖 thread-local 的框架正确工作必须让线程局部存储随猴子补丁一起变为 greenlet 局部存储。把握住这两点你就能把几乎任何 WSGI 框架安全地迁移到 gevent 之上。赞分享后端【免费下载链接】geventCoroutine-based concurrency library for Python项目地址https://gitcode.com/gh_mirrors/ge/gevent点击查看免费下载相关推荐Flask 如何用 Gunicorn 的 gevent worker 支撑大量长连接并发请求Flask 如何用 Gunicorn 的 gevent worker 支撑大量长连接并发请求 当 Flask 应用需要维持大量长时间挂起的并发连接时Gunic后端Web框架Celery gevent 并发池实战从示例应用理解 -P gevent 的安装、配置与底层实现Celery gevent 并发池实战从示例应用理解 P gevent 的安装、配置与底层实现 本文以 Celery 仓库中 examples/gevent任务调度后端消息队列gevent WSGI 服务器实战从 wsgiserver.py 示例到 pywsgi 源码解析gevent WSGI 服务器实战从 wsgiserver.py 示例到 pywsgi 源码解析 gevent 自带一个纯 Python 实现、基于协程gr后端上一篇最小生成树算法终极指南Prim与Kruskal的完整对比分析下一篇ResXResourceManager开发指南贡献代码与添加新功能详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表