Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。

Quora的使命就是分享和增加全世界的知识,并且为了达到这个使命,我们不断地推出改进来让Quora对于我们的读者和作者来说更快。在上一篇,缩短绘制时间,我们讨论了对于客户端性能的最新优化,本文,我们将通过异步编程框架来讨论服务器端性能的新发展。

为了同时优化页面加载和用户操作的性能,我们积极使用缓存,以确保持续快速访问常用数据。在Quora中,对于存储在较慢的存储系统如MySQL中的数据,我们使用memcached作为主缓存层。例如,当我们呈现那些给答案投票者的名单时,需要从存储层取回用户ID到用户名称的映射。每个请求直接访问MySQL的话,数据库将很快超载,所以使用memcached存储这个映射来替代。尽管为每一个用户ID分配一个单独的memcached请求会比查询MySQL更快,但是通过一个单一的批量的多请求取回所有数据会更快。

因为网络通常是memcached请求最昂贵的部分(在我们的环境中占总时间的80%以上),适当的批量缓存请求对于保持Quora快速是很重要的。然而,如果开发者必须手动指定所有数据如何从memcached批量检索,它将是单调易错的。因此,我们已经开发了一个抽象概念叫Asynq,它使开发者很容易编写批量的缓存请求,如今它已经开源。

Priming

在我们开发Asynq之前,使用一个叫做priming的方法来给memcached发送批量请求。每次开发者编写访问数据的函数时,也要编写一个单独的priming函数来指定该函数可以访问的所有数据。例如,假设有一个检索给定用户ID列表并返回用户标识列表的函数,如下:

相应的priming函数会看起来如下:

调用get_names的代码将负责先调用prime_get_names,它将使用多请求从memecached获取所有必要的数据。然后,该数据将被存储在服务器的本地缓存中,所以当真正的函数(本例是get_names)运行时,它不会调用任何网络请求!本质上,每个prime函数表示的都是一个函数的依赖项,或者说它依赖的memecached键。

当我们需要调用一个缓存函数来决定从memcached中获得什么额外数据时,priming会变得更复杂。考虑如下模板函数:

在本例中,prime_get_upvoter_names 需要知道upvoter_uids,为了那些uids调用prime_name_of_user ,但由于upvoters_of_answer 被存储了,它也必须被启动。因此,相应的prime函数将明显更加复杂:

通过恰当地启用我们所有的模型调用, 我们看到惊人的速度改进,因此,我们使用静态分析工具来实施规则,即Quora上所有需要呈现的数据首先要被primed。然而,这些高速增长带来了明显的开发费用,因为实质上开发者需要编写(并调用)所有的模版函数两次。随着我们代码库的增长,priming变得冗余、难理解且易出错。

Asynq

为了解决priming带来的复杂性,我们创建一个叫做Asynq的框架,它在底层采用类似的方法,但是改进了API,把缓存请求集成到模板代码本身。Asynq中,所有需要数据访问的函数通过调度程序运行,调度程序记录跟踪它们的依赖项。当一个函数需要通过调用其它函数来获取数据时,它不再控制调度程序,而是指示需要取回的数据。然后调度程序停止执行该函数直到它解决了这个函数所有的依赖项。

Asynq中,get_names 函数早期看起来如下:

不需要额外的priming 函数——所有代码都包含在模版函数本身。因此,开发者不仅不再需要编写一个完全独立的priming函数,他们也不需要记忆每次模版函数被调用时调用priming函数。之前更复杂的get_upvoter_names在Asynq中也更简单了:

可以把Async 函数理解成创建一个依赖关系图:在它的第一个yield中,get_upvoter_names依赖于upvoters_of_answer的完成。类似地,upvoters_of_answer可能有它自己的依赖项。Asynq调度程序分解该依赖关系图执行async函数,直到所有目前执行的函数从memcached中获取数据时阻塞。然后调度程序使用一个单独的多请求从memcached取回数据,并继续执行直到async函数完成。

假定我们有一个异步函数称为model_call(),它有三个依赖项,每个依赖项都会读取多个memcached键值。直接实现将会使用3个多请求(或者6个单一获取),每个依赖项函数一个,而异步调度程序分解的依赖图看起来如下所示:

我们异步编程的方法不同于Python中其它的异步库如asyncio、Twisted、gevent和Tornado。这些库侧重于异步I/O,而Asynq却侧重于高效的批处理。例如,一个典型缓存使用memcached和asyncio的实现将分别解决缓存依赖项,因此每个memcached请求都会对memcached产生一个单独的请求。在Asynq中,依赖项将会成批进入一个单独的memcached多请求,这可以减少I/O阻塞的总时间。另外,Asynq允许函数被同步或异步调用(通过增加的.async属性),而asyncio需要所有的async def函数被asyncio.get_event_loop()显式调度。使用Asynq的批处理,我们花费很少时间阻塞在I/O,因此采用其它异步I/O库对于我们来说不是优先选择。

与priming相比,Asynq提供一个更通用的、简明的、有原则的方式来支持批处理。因为逻辑仅需要被实现一次,Asynq明显比priming花费更少的开发费用。 减少priming的重复逻辑也提升了性能,正如我们所见,服务器端的速度取胜,由于我们迁移更多代码库从priming到Asynq。

迁移和学习

开发出第一个版本Asynq后,通过迁移代码库的几个小部分从priming到Asynq,我们着手验证我们的设计和实现。在这样做的过程中,我们发现并修复了各种问题:Python2.7中,生成器不能返回值,所以以上代码片段实际上在Python2.7中是无效的。在Asynq的第一个版本中,异步函数产生的最后一个值将会被解析为函数的返回值。然而,这意味着yield关键字意义的超载,这使得代码很难阅读。作为一个替代解决方案,我们从生成器中返回值——PEP 380中有介绍——从Python 3到Python 2.7,并且在代码库中必要的地方,我们目前正在使用Python 2.7的一个补丁版本。(Asynq也支持Python 2.7的未打补丁版本,通过使用一个result函数,该函数抛出一个异常,该异常被解析为返回值。)

最初,使用@async()装饰器使一个函数变为异步函数完全改变了它的接口——所有的调用者不得不使用一个特殊语法来调用异步函数。这个决策使得priming和Asynq在我们的代码库中更难共存,由于所有的开发者需要意识到这种差异。为了修复这个问题,我们更新@async()装饰器,给所有的异步函数增加了一个新的.async属性,这样直接调用一个装饰器函数将仍旧返回结果。

起初测试异步函数具有挑战性。在单元测试中,我们广泛使用Python的mock模块,但是很难模拟异步函数,因为这样做需要特殊处理.async的属性并返回值。作为这个问题的解决方案,我们创建一个专用的模拟函数,asynq.mock.patch,它自动负责模拟,这使得模拟异步函数更轻松。

实现上述改进后(和其它很多改进),我们决定将我们的代码库完全从priming迁移到Asynq。让这两种抽象概念同时存在于我们的代码库中不利于开发速度,因为工程师需要在两种API中根据他们正在编辑哪种模型进行上下文切换。我们利用现有的静态分析工具自动进行迁移,因此工程师只需要去核实脚本的输出并做一点细小的改变,而不是手动迁移代码。

在开始大规模迁移整个代码库之前,不同团队的工程师完成一些较小的“演习”,作为细化我们自动迁移工具的手段,并建立精确的范围估计。完成几个演练后,我们由大约30个工程师(即我们工程师团队的50%)进行了一次有协调性的迁移,在此期间仅用了4天时间,我们迁移了Quora代码库的15,000多行priming代码。

如今,我们的整个代码库仅使用Asynq,并且服务器端开发速度更快且更不易出错。

开源Asynq(和朋友们)

现在可以在GitHub和PyPI上获得Asynq。你可以阅读源码或者通过pip install asynq安装Asynq。和Asynq一起,我们也将QCore(Asynq的唯一依赖项)开源,它是一个助手集,用于整个Quora代码库,包括一个装饰器框架,一个枚举实现,测试助手,和一个事件实现。Asynq和QCore同时兼容Python2.7和Python3,关于Asynq的更详细文档可以在GitHub仓库查看。

未来,我们将继续致力于使Quora对我们的用户来说更快,使新特性对于我们的工程师来说更容易进行开发。我们目前正在招聘Platform工程师来开发像Asynq的核心框架和抽象概念,所以看看我们的职业页面,如果你想和我们一起分享和增加全世界的知识!英文原文:https://engineering.quora.com/Asynchronous-Programming-in-Python?srid=hST?utm_source=mybridge&utm_medium=email&utm_campaign=read_more

译者:蒲公英

python异步_Python中的异步编程相关推荐

  1. python 异步io_Python中的异步IO:完整的演练

    python 异步io Async IO is a concurrent programming design that has received dedicated support in Pytho ...

  2. python元编程运用_Python 中的元编程

    就像元数据是有关数据的数据一样,元编程就是编写用于操纵程序的某些程序.人们普遍认为,元程序就是生成其他程序的某些程序,但范式更加广泛.所有旨在自我读取.分析.转换或修改的程序都是元编程的范例.例如: ...

  3. less 函数_Python中的函数式编程教程,学会用一行代码搞定所有内容

    前言 在本文中,您将了解什么是函数范型,以及如何在Python中使用函数式编程.在Python中,函数式编程中的map和filter可以做与列表相同的事情.这打破了Python的禅宗规则之一,因此函数 ...

  4. python标准化_python中标准化

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! sdk 3.0 实现了统一化,各个语言版本的 sdk具备使用方法相同.接口调用方 ...

  5. python字符集_PYTHON 中的字符集

    Python中的字符编码是个老生常谈的话题,今天来梳理一下相关知识,希望给其他人些许帮助. Python2的 默认编码 是ASCII,不能识别中文字符,需要显式指定字符编码:Python3的 默认编码 ...

  6. python参数化_Python 中如何实现参数化测试的方法示例

    之前,我曾转过一个单元测试框架系列的文章,里面介绍了 unittest.nose/nose2 与 pytest 这三个最受人欢迎的 Python 测试框架. 本文想针对测试中一种很常见的测试场景,即参 ...

  7. kafka python框架_Python中如何使用Apache Avro——Apache的数据序列化系统

    了解如何创建和使用基于Apache Avro的数据,以实现更好,更有效的传输. 在这篇文章中,我将讨论Apache Avro,这是一种开源数据序列化系统,Spark,Kafka等工具正在使用该工具进行 ...

  8. python同步异步_python中Tornado的同步与异步I/O的介绍(附示例)

    本篇文章给大家带来的内容是关于python中Tornado的同步与异步I/O的介绍(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 协程是Tornado种推荐的编程方式,使用 ...

  9. cmd中的进度如何捕捉到输出内容_python 中日志异步发送到远程服务器

    在python中使用日志最常用的方式就是在控制台和文件中输出日志了,logging模块也很好的提供的相应的类,使用起来也非常方便,但是有时我们可能会有一些需求,如还需要将日志发送到远端,或者直接写入数 ...

最新文章

  1. 2009江民中国大陆地区计算机网络安全报告
  2. PHP GD库---之商详合成分享图片
  3. 利用koa实现mongodb数据库的增删改查
  4. Linux的which查找环境变量的文件
  5. 工业交换机和工业路由器的区别
  6. vue跳转到外部链接_前端实战项目:Vue.js实现外卖平台webapp,饿了么项目的翻版...
  7. 减少浏览器兼容性问题
  8. 工作了一个星期各位一定累了吧,那我们一起来表单验证一番吧!
  9. 如何自动搜出更好、更小、更快的NLP模型?
  10. Ivy Bridge处理器
  11. 利用matlab描点绘制平滑曲线
  12. creo绘图属性模板_CREO工程图模板创建
  13. 3D动作绑定_【动捕小灶】动作捕捉数据应用到动画流程详解
  14. 当中国传统文化IP与NFT撞个满怀,能擦出什么火花
  15. 周训练计划之(韦德分化训练法:胸、肩、背、腿、腹)
  16. 其他,HC6800-EM3 V30原理图
  17. c++ 输入数字 输出汉语读法(拼音)代码
  18. 主动笔驱动芯片市场现状及未来发展趋势
  19. 即将打破x86和ARM垄断地位的RISC-V,你了解吗?
  20. 典型可编程接口芯片及应用

热门文章

  1. 在SAP HANA Express Edition里创建数据库表
  2. 使用代码创建SAP BRF ruleset
  3. why metadata request for GM4 via http will be redirected to https via 307 s
  4. OData Console in C4C and Gateway Client in CRM Fiori
  5. Marketing Cloud和Cloud for Customer的客户主数据
  6. 解决minukube启动时因为未设代理导致的启动失败错误
  7. SAP WebIDE 是如何加载SAP UI5里自定义的XML view的 - JerryMaster.view.xml
  8. attachment绑相对url
  9. java计算圆锥体积_六年级:美妙数学之“球的体积计算”(0430六)
  10. 升级bigsur_升级 macOS Big Sur 后,程序监听端口报错