为什么Zapier仍然不使用Gevent
为什么Zapier仍然不使用Gevent。在Zapier服务中,出站API请求很多——几乎每秒500次。这意味着我们要花很长的时间来等待这些API的反馈,尤其是当一个热门服务出现问题的时候。实际上,如果考虑到我们服务器的数量,累加在队列等候的时间,那么现实中每等待1秒钟,CPU一共等待了4分钟。
通常,这么大的工作量非常适合于基于事件的异步实现,例如Python的Gevent。由于我们的后端基于Python和Django,所以我们认为Gevent正好能为我所用。设想转换轮询系统为Gevent后,我们研究了可以预见的效益。以下是我们的研究发现,也说明了为什么我们不会使用Gevent.
背景
Zapier是一个相当标准的完整的Python应用程序,它的核心中,有一些微服务在工作。我们使用Celery(并行分布式框架)做异步处理,这显然是工作负荷的中流砥柱,让一个zap服务运行的每一件事都包含在一个celery任务中。
问题
为了确保有足够的带宽提供给我们的所有后台任务,我们在每台服务器上运行92个Celery。这些进程中的每一个的内存占用都超过了300MB。因此我们的硬件设施条件,给能同时运行的进程数加上了一个硬性的限制。随着规模的扩大,即使有空闲的CPU周期可以完成更多的工作,我们还是不得不增加更大内存的服务器,以满足更多的进程数目的需要。
除了内存问题,让这些进程协同工作也几乎是不可能的。它们或许正好都处于闲置状态,等待API的响应;也可能正同时执行一些CPU密集型的代码,导致服务器过载。
在理想情况下,我们利用CPU的4分钟的等待时间做一点事。这将提高固定的硬件设施下内存使用的上限,也让我们相信各个服务器不会出现超载的问题。
总之,我们的工作负荷涉及大量的I/O操作,但是我们被服务器的内存所限制。虽然有一些多余的CPU容量,但我们不能轻易利用这些容量。
Gevent简介
Gevent,简单来说,就是通过一个名为greenlet的轻量级协程,让一个Python线程并发地执行多个任务(虽然不是并行的)。
传统的线程和greenlets之间的主要区别在于,在没有明确的声明下,它们不能context-switch。利用Gevent,context-switch通常发生在一个网络请求之后,因为我们一般要等会儿结果。你可以在代码的任意位置输入gevent.sleep(0)来允许context-switch。
Greenlets通过运行在等待I/O操作完成时继续工作,有助于有效地利用发送请求和接收响应之间的时间。
对我们来说,Gevent的最主要的好处是,每个Python线程都可以做比以前更多的工作。这意味着我们不需要一味的扩充内存,有助于提高我们在役服务器的利用率。
Gevent的问题
尽管Gevent非常强大,但它也不是万能的。下面是我们使用Gevent中遇到的一些问题:
第三方库
使用Gevent的第一个问题是第三方库的支持。要使用它,任何在网络上调用的库也必须使用纯Python(因此它们可以使用monkey-patched套接字模块),或者明确地写入Gevent支持。
对我们而言,这意味着我们要转换已有的MySQL数据库链接库,改用一个不太成熟的库。虽然这不是一个致命的缺陷,但它也让我们有点儿担心。
Greenlet的安全
其次,所有代码必须充分考虑Greenlet的安全写入。有时这比让代码保证线程安全要简单点儿,但仍然需要非常大的工作量以确保没有共享资源可以访问可能存在context-switch的代码库。在Zapier服务中,我们有时强制使用第三方库的API通信(通常使用Thrift,而不是HTTP)。在这种情况下,我们不能100%确保它们的代码是Greenlet安全的。
连接池
第三,连接后端服务,例如数据库和memcached,必须与Greenlets之间共享进程,避免并发连接螺旋数量失控。处理这个问题的办法是使用连接池,它允许很多Greenlets使用有限数量的连接,只要一旦不需要这些连接,它们就立刻返回到池中。虽然这个方法是有效的,但是它显著增加了代码的复杂性,并要求开发者都知道周边某些服务的限制。
阻塞操作
最后,也是Gevent对于Zapier来说最致命的缺点是,如果不在一个合理的时间区间内让步,单个Greenlet可能阻断整个进程。我们有时候会在后端进行CPU密集型的数据转换和重复数据删除的工作。这项工作进行时,没有其他Greenlet能够运行,显著地拖延了这一进程。
虽然我们可以明确的指定Gevent产生变通解决此问题,但是这对我们而言意味着两件事:
1.我们必须确定我们的代码时Greenlet安全的;
2.我们必须处理好不断增加的代码的复杂性。
具体来说,编写关键路径的代码的开发者必须认识到他们不能依靠平时的单线程模型,让步经常也是有必要的(通过gevent.sleep(0)声明),以防止锁定队列。
我们的结果
我们成功地完成了上述的所有变化,用Gevent运行我们的后端工作了数月之后,工作效率大概增加了20%,同时也降低了内存占用。有了这个设置,我们可以切换那些CPU密集型的实例类型,并减少处理我们基本工作的负荷——这减少了我们托管费用的支出。然而,任务执行时间也有约50%的放缓,因为我们做了很多阻塞进程的操作。
最后,我们认为代码复杂性的增长和任务执行速度的放慢的负面效应不能够被内存和管理费用的节约带来的正面效应所抵消,我们决定暂时推迟向Gevent的转换。
但是在未来,我们将极有可能完成这个转换。我们已经开始慢慢将我们的核心工作负载移动到微服务。随着我们这么做,Gevent变得越来越有吸引力。可以预见,未来你的zap任务将被我们的单线程Python进程Gevent所驱动。
Python培训、Python培训班、Python培训机构,就选光环大数据!
还不够过瘾?想学习更多?点击 http://hadoop.aura.cn/python/ 进行Python学习!
大数据培训、人工智能培训、Python培训、大数据培训机构、大数据培训班、数据分析培训、大数据可视化培训,就选光环大数据!光环大数据,聘请专业的大数据领域知名讲师,确保教学的整体质量与教学水准。讲师团及时掌握时代潮流技术,将前沿技能融入教学中,确保学生所学知识顺应时代所需。通过深入浅出、通俗易懂的教学方式,指导学生更快的掌握技能知识,成就上万个高薪就业学子。 更多问题咨询,欢迎点击------>>>>在线客服!