用 Python 实现每秒百万级请求

简介: 本文讲的是用 Python 实现每秒百万级请求,Python 社区近来针对性能做了很多优化。CPython 3.6 新的字典实现方式提升了解释器的总体性能。得益于更快的调用约定和字典查询缓存,CPython 3.7 会更快。
本文讲的是用 Python 实现每秒百万级请求,

用 Python 可以每秒发出百万个请求吗?这个问题终于有了肯定的回答。

许多公司抛弃 Python 拥抱其他语言就为了提高性能节约服务器成本。但是没必要啊。Python 也可以胜任。

Python 社区近来针对性能做了很多优化。CPython 3.6 新的字典实现方式提升了解释器的总体性能。得益于更快的调用约定和字典查询缓存,CPython 3.7 会更快。

对于计算密集型工作,可以利用 PyPy 的即时编译。Numpy 的测试组件亦可一试,其对 C 拓展的兼容性已有全面提升。预计今年晚些时候 PyPy 会兼容 Python 3.5。

所有这些杰出的贡献鼓舞我在 Python 应用最广泛的领域 —— web 和微服务 —— 开拓创新。

欢迎来到 Japronto 的世界!

Japronto 是为你的需求量身打造的全新微服务框架。其主要目标就是快,可拓展并且轻量。通过 asyncio 使得同时同步异步编程变成可能。而且 Japronto 出人意料的,甚至快过 NodeJS 和 Go。

Python 微框架(蓝色),黑暗力量(绿色)和 Japronto(紫色)

勘误:@heppu 指出, Go 标准库 HTTP 服务端如果写得更小心些可以获得比图表所示要高 12% 的速度提升。此外还有个出色的 Go 语言 fasthttp 服务端实现,据说在这个基准测试中只比 Japronto 慢 18%。赞!详见 https://github.com/squeaky-pl/japronto/pull/12 和 https://github.com/squeaky-pl/japronto/pull/14

我们同时注意到 Meinheld WSGI 服务端几乎跟 NodeJS 和 Go 一样快。尽管其内部采用阻塞设计,它和前面四个 Python 异步方案比起来性能也不差。所以不要轻信任何人所说的异步系统一定更快的言论。异步几乎一定意味着更高的并发,但是往往并发过了头了。

我用 “Hello world!” 做了这个小基准测试,足够说明一些服务器框架解决方案的系统开销。

这些测试是在 AWS São Paulo 区的 c4.2xlarge 实例上进行的,配置是 8 VCPU,默认配置的共享带宽,HVM 虚拟化和弹性存储。操作系统是 Ubuntu 16.04.1 LTS (Xenial Xerus),内核是 Linux 4.4.0–53-generic x86_64,CPU 是 Xeon® E5–2666 v3,主频是 2.90GHz。Python 版本是最近从源码编译的 Python 3.6。

公平起见,所有评比对象(包括 Go)都运行在单工作进程上。服务端则使用单线程的 wrk 做负载测试,100 个连接,每个连接 24 个(管线化的)同步请求,累积起来相当于 2400 个请求。

HTTP 管线化(图片版权维基百科)

HTTP 管线化至关重要,因为这是 Japronto 在执行请求时考虑到的优化手段之一。

大多数服务器执行管线化客户端请求的方式并不会和处理非管线化客户端请求有什么不同。也不会尝试进行优化。(事实上 Sanic 和 Meinheld 会静默丢弃管线化客户端发来的请求,这不符合 HTTP 1.1 协议。)

简而言之,管线化技术让客户端不必等待响应就可以复用同一 TCP 连接发送后续的请求。为保证通信的完整性,服务端发送响应的顺序要和收到的请求的顺序一致。

关于优化的有趣细节

客户端在合并许多小 GET 请求的时候,这些请求很有可能会发到服务端的同一个 TCP 包中去(这要归功于纳格算法),随后被一次系统调用读取

进行一次系统调用并将数据从内核空间移动到用户空间比在进程空间内移动内存要更加昂贵。这就是为什么只执行必需的系统调用非常重要(但是也不能过少)。

Japronto 收到数据并成功地解析了几个请求后,就会尝试尽快搞定所有的请求,并将响应以正确的顺序组合在一起,然后用一次系统调用写回。实际上内核在组合响应时亦可发挥作用,这要归功于 scatter/gather IO 系统调用,不过 Japronto 还没有利用它。

不过要注意管线化并不总是可行,因为个别请求可能会耗费过长时间,等待它们会毫无必要地增加延迟。

在调整试探方法时请务必小心,既要考虑到系统调用的成本也要考虑到预估的完成请求的耗时。

Japronto 可以发出每秒请求数 (RPS) 中位数达 1,214,440 的分组连续数据,该数字使用内插法,取第五十百分位数算出。

除了延迟对管线化客户端的写操作外,Japronto 还用到了其他技术。

Japronto 几乎完全用 C 语言实现。解析器,协议,连接收割机(connection reaper),路由以及请求和响应对象都是 C 语言拓展。

除非明确要求,Japronto 会尽量推迟创建其内部结构对应的 Python 对象。比如除非在 view 中明确要求,Japronto 不会创建头部字典。所有的符号边界都已标记,但是标准化请求头的键名并创建字符串对象这种事只有在第一次被访问时才会做。

Japronto 使用出色的 picohttpparser 库来解析状态行,头部以及分块的 HTTP 消息体。Picohttpparser 直接调用有(几乎所有十年来的 x86_64 CPU 都有的) SSE4.2 拓展的现代 CPU 的文字处理指令,来迅速匹配 HTTP 符号边界。I/O 交由超级赞的 uvloop 处理,uvloop 本身就是 libuv 的包装器。在最底层这就是 epoll 系统调用的桥梁,提供了异步的读写就绪通知。

Picohttpparser 依赖 SSE4.2 和 CMPESTRI x86_64 intrinsic 进行解析。

Python 具有垃圾回收机制,所以设计高性能系统时要小心不要给垃圾回收器带来不必要的压力。Japronto 的内部设计尽量避免循环引用,分配/释放内存亦非常节制。这是通过预先分配一些对象到所谓的「竞技场」实现的。Japronto 还会尝试复用不再被引用的 Python 对象到将来的请求上,而不是直接丢弃掉。

所有分配的内存大小都是 4KB 的整数倍。精心排布的内部结构将频繁使用的数据放在非常接近的内存区域中,这就最小化了缓存未命中的概率。

Japronto 尝试避免不必要的跨缓冲区拷贝,许多操作都就地完成。比如它先百分比解码(注:即 URL 解码)路径,再进行路由的匹配。

开源贡献者们,我需要你的帮助

我连续开发 Japronto 已有三月 —— 通常是在周末,也有工作日。这还是因为我放下了日常的开发工作,全身心投入到了这个项目。

我想是时候和社区分享我劳动的果实了。

Japronto 已实现了许多完善的特性:

  • 实现了 HTTP 1.x ,支持分块上传
  • 完善的 HTTP 管线化支持
  • Keep-alive 连接,可配置的 reaper
  • 支持同步和异步 view
  • 基于 fork 的 Master-multiworker 模型
  • 支持变化时重载代码
  • 简化的路由

我接下来还打算研究一下 Websockets 和异步 HTTP 响应。

还有许多文档和测试的工作有待完成。如果你想伸出援手,请直接在 Twitter 上与我联系。这是 Japronto 的 GitHub 地址

同时,如果你的公司在招募热衷于压榨性能同时还会 DevOps 的 Python 开发者,请联系我。国外的公司我也会考虑。

最后的话

我这里提到的所有技术其实都不是 Python 独占的。它们可能也可以用在其他语言上,比如 Ruby、JavaScript 甚至 PHP。我也想做这一部分的开发,不过除非有人赞助这基本上不太现实。

我想感谢 Python 社区在优化性能方面持续的投入。具体就是感谢 Victor Stinner @VictorStinner,INADA Naoki @methane,Yury Selivanov @1st1 以及全体 PyPy 团队。

出于对 Python 的爱。






原文发布时间为:2017年3月03日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
4月前
|
JSON API 开发者
Python网络编程新纪元:urllib与requests库,让你的HTTP请求无所不能
【7月更文挑战第31天】互联网的发展使网络编程成为软件开发的关键部分. Python以简洁、功能强大著称, 在此领域尤显突出. `urllib`作为标准库, 自Python初期便支持URL处理, 如读取URL (`urllib.request`) 和解析 (`urllib.parse`). 尽管API底层, 但奠定了网络编程基础.
61 4
|
21天前
|
数据采集 前端开发 算法
Python Requests 的高级使用技巧:应对复杂 HTTP 请求场景
本文介绍了如何使用 Python 的 `requests` 库应对复杂的 HTTP 请求场景,包括 Spider Trap(蜘蛛陷阱)、SESSION 访问限制和请求频率限制。通过代理、CSS 类链接数控制、多账号切换和限流算法等技术手段,提高爬虫的稳定性和效率,增强在反爬虫环境中的生存能力。文中提供了详细的代码示例,帮助读者掌握这些高级用法。
Python Requests 的高级使用技巧:应对复杂 HTTP 请求场景
|
4天前
|
JSON API 数据格式
Python中获取HTTP请求响应体的详解
本文介绍了如何使用Python的`requests`和`urllib`库发送HTTP请求并处理响应体。`requests`库简化了HTTP请求过程,适合快速开发;`urllib`库则更为底层,适用于性能要求较高的场景。文章详细演示了发送GET请求、处理JSON响应等常见操作。
|
1月前
|
JSON 缓存 API
在 Python 中使用公共类处理接口请求的响应结果
在 Python 中使用公共类处理接口请求的响应结果
28 1
|
2月前
|
JSON API 数据格式
使用Python发送包含复杂JSON结构的POST请求
使用Python发送包含复杂JSON结构的POST请求
|
1月前
|
安全 网络安全 数据安全/隐私保护
HTTPS 请求中的证书验证详解(Python版)
HTTPS 请求中的证书验证详解(Python版)
102 0
|
2月前
|
JSON API 开发者
Python网络编程新纪元:urllib与requests库,让你的HTTP请求无所不能
【9月更文挑战第9天】随着互联网的发展,网络编程成为现代软件开发的关键部分。Python凭借简洁、易读及强大的特性,在该领域展现出独特魅力。本文介绍了Python标准库中的`urllib`和第三方库`requests`在处理HTTP请求方面的优势。`urllib`虽API底层但功能全面,适用于深入控制HTTP请求;而`requests`则以简洁的API和人性化设计著称,使HTTP请求变得简单高效。两者互补共存,共同推动Python网络编程进入全新纪元,无论初学者还是资深开发者都能从中受益。
51 7
Python3.x常用时间的处理方法 和urlopen处理post请求,传值data 原创
Python3.x常用时间的处理方法和urlopen处理post请求,传值data 原创
|
2月前
|
开发者 Python
Python POST 请求超时配置
Python POST 请求超时配置
|
3月前
|
JSON API 数据格式
Python网络编程:HTTP请求(requests模块)
在现代编程中,HTTP请求几乎无处不在。无论是数据抓取、API调用还是与远程服务器进行交互,HTTP请求都是不可或缺的一部分。在Python中,requests模块被广泛认为是发送HTTP请求的最简便和强大的工具之一。本文将详细介绍requests模块的功能,并通过一个综合示例展示其应用。