我应该坚持还是放弃Python来处理并发性?


31

我有一个10K LOC写在项目的Django用相当便宜的芹菜RabbitMQ的用于在需要异步和后台作业),并已得出结论,该系统的部分将来自于被改写受益的东西比Django的其它更好的并发。原因包括:

  • 信号处理和可变对象。特别是当一个信号触发另一个信号时,当实例更改或消失时,使用ORM在Django中处理它们可能会令人惊讶。我想使用某种消息传递方法,其中传递的数据在处理程序中不会更改(如果我正确的话,Clojure的写时复制方法看起来不错)。
  • 系统的某些部分不是基于Web的,因此需要更好的支持来同时执行任务。例如,系统读取NFC标签,当读取一个NFC标签时,LED点亮几秒钟(Celery任务),播放声音(其他Celery任务),并查询数据库(其他任务)。这是作为Django管理命令实现的,但是Django及其ORM本质上是同步的并且共享内存是有限的(我们正在考虑增加更多的NFC读取器,我认为Django + Celery方法不再可行,我希望看到更好的消息传递功能)。

与使用诸如ErlangClojure这样的语言相比,使用TwistedTornado之类的利弊是什么?我对实际的利益和损害感兴趣。

您如何得出结论,系统的某些部分使用另一种语言会更好?您是否遇到性能问题?这些问题有多严重?如果可以更快,那么是否必须更快?

示例1: Django在HTTP请求之外工作:

  1. 读取NFC标签。
  2. 已查询数据库(可能还有LDAP),并且我们希望在数据可用时执行某些操作(红灯或绿灯,播放声音)。这会使用Django ORM进行阻止,但是只要有Celery工作人员可用,就没有关系。更多工作站可能是个问题。

示例2:使用Django信号进行“消息传递”:

  1. 一个post_delete事件被处理,其他的对象可能是因为这个被修改或删除。
  2. 最后,应将通知发送给用户。在这里,如果传递给通知处理程序的参数是已删除或将要删除的对象的副本并保证在处理程序中不发生更改,那将是很好的。(当然,可以简单地通过不将ORM管理的对象传递给处理程序来手动完成。)

我认为,如果您进一步解释得出结论的原因,将会得到更好的答案
Winston Ewert

5
在有人说语言选择问题是题外话之前,我先说一下我认为这很好,因为这是一个有特定要求的实际问题。我希望它能进行一些详细的比较。
亚当李尔

扭曲是并发的反面!它是一个由事件驱动的单线程服务器,如果您需要真正的并发性,它将无处可走。

Answers:


35

开场白

您如何得出结论,系统的某些部分使用另一种语言会更好?您是否遇到性能问题?这些问题有多严重?如果可以更快,那么是否必须更快?

单线程异步

有几个问题和其他Web资源已经在解决单线程异步与多线程并发的优缺点。有趣的是,当I / O是主要瓶颈时,Node.js的单线程异步模型是如何执行的,并且有很多请求可以同时处理。

Twisted,Tornado和其他异步模型充分利用了单线程。由于大量的Web编程具有大量的I / O(网络,数据库等),因此等待远程调用所花费的时间明显增加。那是可以花在做其他事情上的时间,例如启动其他数据库调用,呈现页面和生成数据。该单线程的利用率非常高。

一个单线程异步的最大好处是,它使用多少内存更少。在多线程执行中,每个线程都需要一定数量的保留内存。随着线程数量的增加,仅存在线程所需的内存量也会增加。由于内存是有限的,因此这意味着可以一次创建的线程数是有限的。


对于Web服务器,假装每个请求都有自己的线程。假设每个线程需要1MB的内存,并且Web服务器具有2GB的RAM。该Web服务器将能够在没有足够的内存来处理更多时间之前的任何时间点(大约)处理2000个请求。

如果您的负载明显高于此负载,则请求将花费很长时间(在等待较旧的请求完成时),或者您将不得不向群集中放置更多服务器以扩大可能的并发请求数。


多线程并发

相反,多线程并发依赖于同时执行多个任务。这意味着,如果某个线程在等待数据库调用返回时被阻塞,则可以同时处理其他请求。线程利用率较低,但执行的线程数要大得多。

多线程代码也很难推理。存在锁定,同步和其他有趣的并发问题。单线程异步不会遇到相同的问题。

但是,多线程代码在执行CPU密集型任务时性能更高。如果没有机会让线程“让步”(例如通常会阻塞的网络调用),那么单线程模型将不会有任何并发​​性。

两者可能共存

两者之间当然有重叠;它们不是互斥的。例如,可以以非阻塞方式编写多线程代码,以更好地利用每个线程。


底线

还有许多其他问题需要考虑,但是我喜欢这样考虑两个问题:

  • 如果您的程序受I / O约束,那么单线程异步可能会很好地工作。
  • 如果您的程序受CPU限制,那么多线程系统可能是最佳选择。

在您的特定情况下,您需要确定完成哪种异步工作以及这些任务的出现频率。

  • 它们是否在每次请求时发生?如果是这样,随着请求数量的增加,内存可能会成为问题。
  • 这些任务有序吗?如果是这样,如果使用多个线程,则必须考虑同步。
  • 这些任务是否占用大量CPU?如果是这样,单线程是否能够跟上负载的需求?

没有简单的答案。您必须考虑用例是什么,并进行相应的设计。有时异步单线程模型会更好。在其他时候,需要使用多个线程来实现大规模并行处理。

其他注意事项

您还需要考虑其他问题,而不仅仅是您选择的并发模型。你知道Erlang或Clojure吗?您认为您可以使用其中一种语言编写安全的多线程代码,从而提高应用程序的性能吗?掌握其中一种语言是否会花费很长时间,并且您将来所学的语言对您有好处吗?

这两个系统之间的通信困难如何?并行维护两个独立的系统是否过于复杂?Erlang系统将如何从Django接收任务?Erlang将如何将这些结果传达回Django?性能是否足够重要,是否值得增加复杂性?


最后的想法

我一直发现Django足够快,并且被一些流量非常大的网站所使用。您可以进行一些性能优化,以增加并发请求的数量和响应时间。诚然,到目前为止,我对Celery并没有做任何事情,因此通常的性能优化可能无法解决这些异步任务可能遇到的任何问题。

当然,总有建议在此问题上投入更多的硬件。供应新服务器的成本是否比全新子系统的开发和维护成本便宜?

在这一点上,我问了太多问题,但这是我的意图。如果没有分析和进一步的细节,答案将不容易。能够分析问题归结为知道要问的问题,所以……希望我在这方面有所帮助。

我的直觉说不需要用另一种语言重写。复杂性和成本可能太大。


编辑

回应跟进

您的后续文章介绍了一些非常有趣的用例。


1. Django在HTTP请求之外工作

您的第一个示例涉及读取NFC标签,然后查询数据库。我认为用另一种语言编写该部分不会对您有用,仅因为查询数据库或LDAP服务器将受到网络I / O(并可能影响数据库性能)的约束。另一方面,并​​发请求的数量将受服务器本身的约束,因为每个管理命令将作为其自己的进程运行。因为没有将消息发送到已经运行的进程,所以设置和拆卸时间会影响性能。但是,您将能够同时发送多个请求,因为每个请求都是一个独立的过程。

对于这种情况,我发现您可以研究两种途径:

  1. 确保您的数据库能够使用连接池一次处理多个查询。(例如,Oracle要求您相应地配置Django 'OPTIONS': {'threaded':True}。)在数据库级别或Django级别可能有类似的配置选项,您可以针对自己的数据库进行调整。无论使用哪种语言编写数据库查询,都必须等待该数据返回后才能点亮LED。不过,查询代码的性能可能会有所不同,并且Django ORM的运行速度并不快(通常足够快)。
  2. 最小化设置/拆卸时间。有一个持续运行的过程,并向其发送消息。(如果我错了,请纠正我,但这是您最初的问题实际上关注的问题。)上面介绍了此过程是用Python / Django还是其他语言/框架编写的。我不喜欢这么频繁使用管理命令的想法。是否有可能不断运行一小段代码,将来自NFC读取器的消息推送到消息队列,然后Celery读取并转发给Django?即使是用Python(但不是Django!)编写的小程序,其安装和拆卸也要比启动和停止Django程序(及其所有子系统)更好。

我不确定您要为Django使用哪种Web服务器。mod_wsgi使用Apache,您可以配置服务请求的进程数和进程中的线程数。确保调整Web服务器的相关配置,以优化可服务请求的数量。


2. Django信号的“消息传递”

您的第二个用例也相当有趣。我不确定是否有答案。如果要删除模型实例,并希望稍后对其进行操作,则可以序列化它们JSON.dumps,然后反序列化JSON.loads。稍后将不可能完全重新创建对象图(查询相关模型),因为相关字段是从数据库延迟加载的,并且该链接将不再存在。

另一种选择是以某种方式对象标记为删除,并且仅在请求/响应周期结束时(在所有信号都得到服务之后)将其删除。可能需要一个自定义信号来实现此目的,而不是依赖post_delete


1
许多FUD以及对锁定的怀疑以及Erlang所没有的其他问题,您列出的传统共享状态问题都不是专门为不共享状态而设计的语言和运行时的考虑因素。Erlang可以在很小的内存中处理成千上万的谨慎过程,内存压力也不是问题。

@Jarrod,我个人不认识Erlang,所以我会接受你在这方面的意见。否则,我提到的几乎所有其他内容都是相关的。成本,复杂性以及当前工具是否被正确利用。
乔什·史密顿


这是我非常喜欢阅读的^^史诗般的答案。+1,干得好!
Laurent Bourgault-Roy 2013年

另外,如果您有DJango模板,则可以将它们与Erlydtl一起在erlang中使用
Zachary K

8

我为一家主要的美国ISP做了一些非常复杂且高度可扩展的开发。我们使用Twisted服务器进行了一些严肃的事务处理,而让Python / Twisted扩展到受CPU限制的任何事物上,这都是一场噩梦。I / O绑定不是问题,但CPU绑定是不可能的。我们可以快速组合系统,但是如果它们受CPU约束,那么将它们扩展到数百万个并发用户将是配置和复杂性的噩梦。

我写了一篇关于它的博客文章Python / Twisted VS Erlang / OTP

TLDR;二郎赢了


4

Twisted的实际问题(我喜欢并且已经使用了大约五年):

  1. 该文档还有一些需要改进的地方,无论如何,该模型都非常复杂。我发现很难让其他Python程序员来处理Twisted代码。
  2. 由于缺乏良好的阻止API,我最终使用了阻止文件I / O和数据库访问。这确实会损害性能。
  3. 似乎没有庞大的社区和使用Twisted的健康社区。例如,Node.js的开发更加活跃,尤其是对于Web后端编程。
  4. 它仍然是Python,至少CPython并不是最快的东西。

我已经将Node.js与CoffeeScript一起使用做了一些工作,如果您对并发性能感到担忧,那可能值得飞跃。

您是否考虑过以某种安排运行Django的多个实例以在实例之间分散客户端?


1
在一般的令人不满意的Python文档待改进:/(不说这不好的,但语言是受欢迎的人会想到它是好多了)。
Rook

3
我发现Python文档(尤其是Django文档)是适合任何语言的最佳文档。但是,许多第三方库仍有一些不足之处。
乔什·史密顿

1

在考虑切换到另一种语言之前,我将建议以下内容。

  1. 使用LTTng记录诸如页面错误,上下文切换和系统调用等待之类的系统事件。
  2. 转换所有需要花费太多时间使用C库的地方,并使用您喜欢的任何设计模式(多线程,基于信号事件,回叫异步或Unix传统select)都适合在那里的I / O。

一旦应用程序具有性能优先级,我就不会在Python中使用线程。我将采用上述选项,它可以解决许多问题,例如软件重用,与Django的连接性,性能,易于开发等。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.