阅读更多

2顶
0踩

编程语言
用 Python 做到每秒处理上百万次 HTTP 请求,可能吗?也许不能,但直到最近,这已成为现实。

很多公司都在为了提升程序的执行性能和降低服务器的运营成本,而放弃 Python 去选择其它编程语言,其实这样做并不是必须,因为 Python 完全可以胜任这些任务。

Python 社区最近做了大量关于性能的优化。CPython 3.6 重写了新的字典从而全面提升解析器的执行性能。由于引入更快的调用规则和字典查询缓存,CPython 3.7 甚至还要更快。

我们可以用 PyPy 的 Just-in-Time 来编译复杂的科学计算任务,NumPy 的测试套件也优化了和 C 扩展的兼容性,同时 PyPy 还计划于今年晚些时候做到和 Python 3.5 保持一致。

这些振奋人心的变化激励着我想要有所创新,Python 所擅长的领域众多,我选择了其中一个:Web 和 MicroServices 开发。

了解 Japronto!
Japronto 是一个全新的,为微服务量身打造的微框架。实现它的主要目标包含够快、可扩展和轻量化。的确它快的吓人,甚至远比 NodeJS 和 Go 还要快的多的多。要感谢 asyncio,让我可以同时编写同步和异步代码。

Python 的微框架(蓝色)、NodeJS 和 Go (绿色) 和 Japronto (紫色)

勘误表:用户 @heppu 提到,如果谨慎点用 Go 的 stdlib HTTP 服务器可以写出比上图的 Go 快 12% 的代码。另外 fasthttp 也是一个非常棒的 Go 服务器,同样的测试中它的性能几乎只比 Japronto 低 18%。真是太棒了!更多细节查可以看 https://github.com/squeaky-pl/japronto/pull/12https://github.com/squeaky-pl/japronto/pull/14

我们可以看到其实 Meinheld WSGI 服务器已经和 NodeJS 和 Go 的性能差不多了。尽管它用的是阻塞式设计,但还是要比前面那四个要快的多,前面四个用的是异步的 Python 解决方案。所以,不要轻易相信别人那些关于异步系统总是比同步系统更快的说法,虽然都是并发处理的问题,但事实远不如想象的那么简单。

虽然我只是用 “Hello World” 来完成上面这个关于微框架的测试,但它清晰的展现了各种服务器框架的处理能力。

这些测试是在一台亚马逊 AWS EC2 的 c4.2xlarge 实例上完成的,它有 8 VCPUs,数据中心选在圣保罗区域,共享主机、HVM 虚拟化、普通磁盘。操作系统是 Ubuntu 16.04.1 LTS (Xenial Xerus),内核为 Linux 4.4.0–53-generic x86_64。操作系统显示的 CPU 是 Xeon® E5–2666 v3 @ 2.90GHz。Python 我用的版本是 3.6,刚从源码编译来的。

公平起见,所有程序,包括 Go,都只运行在单个处理器内核上。测试工具为 wrk,参数是 1 个线程,100 个链接和每个链接 24 个请求(累计并发 2400 次请求)。

HTTP 流水线(图片来自 Wikipedia)

HTTP 流水线在这里起着决定性的因素,因为 Japronto 用它来做执行并发请求的优化。

大多数服务器把来自客户端的流水线和非流水线请求都一视同仁,用同样的方法处理,并没有做针对性的优化。(实际上 Sanic 和 Meinheld 也是默默的把流水线请求当做非流水线来处理,这违反了 HTTP 1.1 协议)

简单来说,通过流水线技术,客户端不用等到服务器端返回,就可以在同一条 TCP 链接上继续发送后续的请求。为了保障通讯的完整性,服务器端会按照请求的顺序逐个把结果返回给客户端。

细节优化过程
当一堆小的 GET 请求被客户端以流水线打包发送过来,服务器端很可能只需要一次系统调用,读取一个 TCP 数据包就能拿到全部的请求。

系统调用,以及在内核空间到用户空间之间移动数据,相比起在进程内部移动数据,成本要高的多。这就是为什么不到万不得已,要尽可能少做系统调用的次数。

当 Japronto 收到数据并成功解析出请求序列时,它会尝试尽可能快的把这些请求执行完成,并以正确的顺序合并所有结果,然后只执行一次系统调用发送数据给客户端。实际上因为有 scatter/gather IO 这样的系统调用,合并的工作并不需要自己去完成,只不过 Japronto 暂时还没有用到这些功能。

然而事情并不总是那么完美,有时候请求需要耗费很长时间去处理,等待完成的过程增加了不必要的延迟。

当我们做优化时,有必要考虑系统调用的成本和请求的预期完成时间。

经过优化 Japronto 拿到了 1,214,440 RPS 的成绩

除了利用客户端流水线请求,和优化调用,还有一些其它可用的技术。

Japronto 几乎都是用 C 写的。包含解析器、协议、链接管理、路由、请求、应答等对象都是用 C 扩展写的。

Japronto 力图做到 Python 的懒加载,比如,协议头的字典只有在被试图请求到时才会被创建,另外一系列的对象也只有在第一次使用时才会被创建。

Japronto 使用超牛逼的 picohttpparser C 库来解析状态、协议头以及分片的 HTTP 消息体。Picohttpparser 是直接调用现代 CPU 集成的 SSE4.2 扩展文本处理指令去快速匹配 HTTP 标记的边界(那些 10 年前的老 x86_64 CPU 都有这玩意儿)。I/O 用到了超棒的 uvloop,它是一个 libuv 的封装,在最底层,它是调用 epoll 来提供异步读写通知。

Picohttpparser 依赖 SSE4.2 和 CMPESTRI x86_64 的特性做解析


Python 是有垃圾收集功能的语言,为避免不必要的增加垃圾收集器的压力,在设计高性能系统时一定要多加注意。Japronto 的内部被设计的尝试避免循环引用和尽可能少的分配、释放内存,它会预先申请一块区域来存放对象各种,同时尝试在后续请求中重用那些没有被继续引用的 Python 的对象,而不是将那些对象直接扔掉。

这些预先申请的内存的大小被固定为 4KB 的倍数。内部结构会非常小心和频繁的使用这些连续的内存区域,以减少缓存失效的可能性。

Japronto 会尽可能避免不必要的缓存间复制,只在正确的位置执行操作。比如,在处理路由时,先做 URL 解码再进行路由匹配。

开源贡献者们,我需要你们的帮助
我已经连续不断的开发 Japronto 超过三个月,不光在每一个工作日,周末也无休。除了每天的工作外,我把所有时间精力都投入到这个项目上了。

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

Japronto 已经可靠的实现了下面这些功能:
  • 实现 HTTP 1.x 并且支持分片上传
  • 完整支持 HTTP 流水线
  • 可配置是否让链接 Keep-alive
  • 支持同步和异步视图
  • 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。
  • 大小: 53.9 KB
  • 大小: 134.3 KB
  • 大小: 14.7 KB
  • 大小: 6.6 KB
  • 大小: 24.6 KB
来自: shaunli
2
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 在JSP页面用EL表达式调用一些函数

    在JSP页面用EL表达式调用一些函数,可以减少小脚本的出现

  • 在idea中使用jsp时无法调用out.println等方法

    在idea中使用jsp,在<%%>中使用jsp内置对象out时出现问题,发现无法使用println()这个方法了,以及其他的方法都无法使用,找到以下解决方法 选择 File->Project Structure->modules->Dependencies 点击右侧的+号,选择点击Library 选择自己本地的tomcat,然后添加确认 添加成功可以看到左侧目录中已添加tomcat,再次在jsp中使用相应的方法可以看到问题已经解决。 ...

  • JSP out对象

    概述 1、out对象即输出对象,用来控制管理输出的缓冲区(buffer)和输出流(output stream)向客户端页面输出数据。  2、out对象类型为javax.servlet.jsp.JspWriter,与HttpServletResponse接口的getWriter()方法获得PrintWriter对象对象的功能相同,并都由java.io.Writer类继承而来 3、out对象...

  • JSP内置对象out对象的功能简介说明

    JSP内置对象out对象的功能简介说明

  • Java Web JSP内置对象 out对象 request对象 response对象 重定向 Cookie session对象 application对象 exception 对象 page 对象

    1.认识JSP内置对象 内置对象是指在JSP页面中内置的不需要定义就可以在网页中总结使用的对象。 好处: 提高程序员的开发效率 内置对象特点 内置对象是自动载入的,因此它不需要直接实例化 内置对象是通过 Web 容器来实现和管理的 在所有的 JSP 页面中,直接调用内置对象都是合法的 JSP 规范中定义了九种内置对象,这里主要介绍前五种。 out 对象:负责管理对客户端的输出 request 对象:负责得到客户端的请求信息 response 对象:负责向客户端发出响应 session 对象:负责保存同一客户

  • JSP内置对象(out、request、response、session、application)

    JSP内置对象认识内置对象内置对象特点内置对象(5/9,4/9)out对象request对象response对象 认识内置对象 内置对象:在JSP页面中内置的不需要定义就能在网页中直接使用的对象 预定义的原因:因为这些内置对象有些能够存储参数,有些能够提供输出,还有些能提供其他的功能,JSP程序员一般情况下使用这些内置对象的频率比较高 内置对象特点 1,内置对象是自动载入的,不需要实例化 2,内置对象是通过Web容器来实现和管理的 3,在所有JSP页面中,直接调用内置对象都是合法的 内置对象(5/9,4/9

  • JSP常用内置对象:out、request、response

    学习目标(常用九大内置对象) out request response page session application pageContext config exception 1、什么是JSP内置对象 JSP内置对象是Web容器创建的一组对象,不需要new就可以直接使用的对象。 1.1、out对象 语法<%="文本内容1<br/>"%> #表达式输出 <% out.print("文本内容2<br/>"); %> #out对象 <%

  • jsp之out标签

    jsp之out标签

  • jsp中常用的<c:if>和<c:forEach>方法

    jsp中常用的<c:if>判断非空的方法:<c:if test="${empty obj.glyhf}">为空</c:if>  //为空显示<c:if test="${not empty obj.glyhf}">不为空</c:if>  //不为空显示<c:if>判断表单字段,重复${${}}时:这时候直

  • JSP隐式对象——out对象、pageContext对象、exception对象

    JSP隐式对象的概述 在JSP页面中,有一些对象需要频繁使用,如果每次都重新创建这些对象则会非常麻烦。为了简化Web应用程序的开发,JSP2.0规范中提供了9个隐式(内置)对象,它们是JSP默认创建的,可以直接在 JSP 页面中使用。这9个隐式对象的名称、类型和描述如表所示。 名称 类型 描述 范围 out javax.servlet.jsp.JspWriter 用于输出页面 page request javax.servlet.http.HttpServletRequest 得到用户信

  • JSP内置对象及其常用方法

    个人博客:The Blog Of WaiterXiaoYY 欢迎来互相交流学习。 前言 JSP内置对象是在JSP运行环境中已定义好的对象,可在JSP页面的脚本部分直接使用。 文章目录前言1. out对象2.request对象3.response对象4.session对象5. application对象6. exception对象7. page对象8. config对象9. pageContex...

  • 第16课:JSP隐式对象 JSP out对象 (JSP教程 JSP入门实战教程 黄菊华Java网站开发系列教程)

    JSP out对象 out 对象是一个输出流,用来向客户端输出数据,可以是各种数据类型的内容,同时,它还可以管理应用服务器上的输出缓冲区,缓冲区的默认值是 8KB,可以通过页面指令 page 来改变默认大小。 out 对象是一个继承自抽象类 javax.servlet.jsp.JspWriter 的实例,在实际应用中,out 对象会通过 JSP 容器变换为 java.io.PrintWriter 类的对象。 在使用 out 对象输出数据时,可以对数据缓冲区进行操作,及时清除缓冲区中的残余数据,为其他的输出.

  • out在jsp中使用表示什么JspWriter对象

    在 JSP 中,"out" 是 JspWriter 对象的引用。JspWriter 是一个抽象类,它封装了对 Servlet 响应的输出流的访问,提供了一组方法来把字符串输出到客户端。在 JSP 中,我们通常使用 out 对象来向客户端发送输出。例如,我们可以使用 out.println() 方法来输出字符串到客户端。 ...

  • Jsp生命周期、Jsp的使用、JSP隐式对象、EL表达式、JSTL

    jsp JSP全称Java Server Pages,是一种动态网页开发技术。它使用JSP标签在HTML网页中插入Java代码。标签通常以<%开头以%>结束 JSP 网页几乎可以被当成一个普通的 Servlet  JSP 生命周期 编译阶段:servlet容器编译servlet源文件,生成servlet类,并编译该类 初始化阶段:加载与JSP对应的servlet类,创建其实例,...

  • IDEA写jsp时无法使用out对象

    IDEA写jsp时无法使用out对象 问题 在IDEA中写jsp文件时无法使用out对象, 尝试过在项目结构中关联tomcat结果依然无效, 最后才得知是jar包的导入问题 解决过程: 在apache-tomcat-9.0.41\lib下复制这两个jar包 将以上两个jar包粘贴到WEB-INF的lib中(没有lib就手动创建) 点击红框选中处直接进入项目结构管理界面 进入Libraries添加一个Java 将WEB-INF下的两个jar包依次导入 问题解决,out对象可..

  • JspWriter与PrintWriter(转)

    1都继承自java.io.writer类 JspWriter在Jsp页面上直接用out对象输出,也可以直接用pageContex.getOut()得到out对象 PrintWriter必须通过response.getwriter()得到 2.在Jsp页面上用两种方法同时输出数据,PrintWriter中的数据会先输出 例如: 新建test.jsp页面 <%ou...

  • 07-10-17学习心得

    今天主要学习了JSP的基础知识.一.JspWriter与PrintWriter的关系:1.都是继承自java.io.Writer类.    JspWriter可以在JSP页面中直接用out对象输出.可以用pageContext.getOut();得到JspWriter对象.  PrintWrietr在JSP页面中必须用response.getWriter();方法得到其对象.二者作用域不同

  • JspWriter与PrintWriter的关系

    一.JspWriter与PrintWriter的关系:1.都是继承自java.io.Writer类.JspWriter可以在JSP页面中直接用out对象输出.可以用pageContext.getOut();得到JspWriter对象.PrintWrietr在J

  • JspWriter 与 printwriter区别

    JSP九个隐含对象JspWriter outJspWriter是抽象类而PrintWriter不是,也就是说你可以通过new操作来直接新建一个PrintWriter的对象而JspWriter不行,它必须是通过其子类来新建。 但是它们之间又是有关系的,这个关系主要是JspWriter对PrintWriter有依赖。初始化一个JspWriter对象的时候要关联ServletRe...

  • Scripting elements ( <%!, <jsp:declaration, <%=, <jsp:expression, 错误的解决方法

    <% session.setAttribute("pageContext", "it315"); %> 输出表达式\\${pageContext}的值: ${pageContext} 【web.xml】错误地在 web.xml中禁用了 jsp脚本元素 *.jsp UTF-8 *.jsp true *.jsp true

Global site tag (gtag.js) - Google Analytics