python异步任务队列、消息队列

提起gevent,就不得不提起greenlet。按照官方解释greenlet是轻量级的并行编程,而gevent呢,就是利用greenlet实现的基于协程的python的网络library。

官方文档:http://www.gevent.org/contents.html

说说协程,进程和线程大家平时了解的都比较多,而协程算是一种轻量级进程,但又不能叫进程,因为操作系统并不知道它的存在。什么意思呢,就是说,协程像是一种在程序级别来模拟系统级别的进程,由于是单进程,并且少了上下文切换,于是相对来说系统消耗很少,而且网上的各种测试也表明,协程确实拥有惊人的速度。并且在实现过程中,协程可以用以前同步思路的写法,而运行起来确是异步的,也确实很有意思。话说有一种说法就是说进化历程是多进程->多线程->异步->协程,暂且不论说的对不对,单从诸多赞誉来看,协程还是有必要理解一下的。

在学习的过程中,结果却发现了不少其他的相关技术:

异步任务队列:

celery http://www.oschina.net/p/celery

celery(芹菜)是一个异步任务队列/基于分布式消息传递的作业队列。它侧重于实时操作,但对调度支持也很好。

celery用于生产系统每天处理数以百万计的任务。

celery是用Python编写的,但该协议可以在任何语言实现。它也可以与其他语言通过webhooks实现。

建议的消息代理RabbitMQ的,但提供有限支持Redis, Beanstalk, MongoDB, CouchDB, ,和数据库(使用SQLAlchemy的或Django的 ORM) 。

celery是易于集成Django, Pylons and Flask,使用 django-celery, celery-pylons and Flask-Celery 附加包即可。

Gearman http://www.oschina.net/p/gearman

一个分发任务的程序框架,可以用在各种场合,与Hadoop相比,Gearman更偏向于任务分发功能。它的 任务分布非常 简单,简单得可以只需要用脚本即可完成。Gearman最初用于LiveJournal的图片resize功能,由于图片resize需要消耗大量计算资 源,因此需要调度到后端多台服务器执行,完成任务之后返回前端再呈现到界面。

消息队列(MessageQueue):

rabbitMQ http://www.rabbitmq.com/getstarted.html

beanstalk http://www.oschina.net/p/beanstalkd

关于某些概念性问题,limodou给出解答如下:

gevent是基于greenlet实现的异步框架。它提供了对io, socket, time等的一些异步的实现,如gevent.sleep(),用greenlet还可以模拟线程式的东西。同时提供monkey_patch机制,可以对python标准库的一些阻塞方法替換为非阻塞,这样原来的程序基本不用修改即支持异步。同时基于gevent还有象gevent-websocket, gevent-socketio等库用来处理web socket和sockio.js的支持。gevent也提供wsgi server的支持。这一点和tornado,不过tornado还可以进行web应用开发,而gevent只用来提供运行的server。 MessageQueue 只是一个消息队列,用于消息的分发。往往异步处理之间协同会考虑使用队列来传递消息。所以单纯的消息队列没什么作用,要和应用处理相结合。

jie chen:

我这边gevent用的比较多, gevent可以当作一个线程库来理解,但有几点要注意一下。 Python自带的线程库是由Python自己来作线程切换(如果我没记错是每运行100行+字节码就进行一次切换),所以它对每个线程的调度时间比较公平。 而gevent只有在当前协程遇到gevent支持的IO阻塞时,才会切换到其它协程运行。所以每个协程的调度时间一般来说不是很公平。 (这种非抢占式的调度策略也有好处,像Python的List,tuple, dict这些复合类型以及其它一些外部操作(sqlite操作)等等,你可以直接在多个协程里当作共享资源使用而不用加锁(那当然你确保在操作这些类型时没有gevent支持的IO阻塞)。 缺点除了上面所说的不公平外,还有就是你要知道所有gevent支持的IO阻塞。) 如果你的应用场景里,gevent支持的IO阻塞比较多的话,可以考虑用gevent, 当然,你对gevent足够了解的话,你也可以根据它的API扩展它所支持的IO阻塞。

有一个使用 django+celery+RabbitMQ 实现异步执行的例子:http://www.oschina.net/question/25940_24780

引用部分内容介绍应用场合:

言归正传,先介绍一下这篇文章的应用场景吧。我们知道大型网站的性能非常重要,然而有时不得不做一些相当耗时的操作。 比如SNS网站的“新鲜事儿”系统,我发帖之后,会给所有关注我的人推送一条通知。乍一看没什么难的,发帖之后找出关注我的人, 然后生成相应的消息记录就行了。但问题是,100个人关注我,就要执行100条INSERT查询,更要命的是,Web服务器是同步的, 这100条查询执行完成之前,用户是看不到结果的。 怎么办呢,这时就轮到消息队列上场了。发帖之后只需给队列发送一条消息, 告诉队列“我发帖子了”,然后把发帖的结果返回给用户。 这时另一个叫做worker的进程会取出这条消息并执行那100条INSERT查询。这样,推送通知的操作在后台异步执行, 用户就能立即看到发帖结果。更精彩的是,可以运行多个worker实现分布式,多繁重的任务都不在话下了。

“我其实还一直有个疑问: celery 跑 job 是可以,但是它是不是一定要通过 broker 的形式?有时候别人只是想跑个 job,而不是发异步消息跑一个 worker” worker是指进程,broker是指容器(即你的任务存放在哪,比如mongodb、redis),那job是不是工作列表?(消息队列) 有一篇文章写出了流程(全文,写的很好。http://www.dongwm.com/archives/shi-yong-celeryzhi-liao-jie-celery/):

  1. 使用django-celery或者直接操作数据库(settings.py里面指定)添加任务,设置的相关属性(包含定时任务的间隔)存入数据库.
  2. celerybeat通过djcelery.schedulers.DatabaseScheduler获取django内你设置的任务周期 性的检查(默认5s),发现需要执行某任务讲其丢入你设置的broker(我这里是rabbitmq),他会更具settings.py的设置放到对应的 队列
  3. 在你启动了celery worker(以前是celeryd)的服务器上,根据worker也会定期(默认5s)去broker里面查找需要它执行的队列里面是否有任务
  4. 当发现队列有要执行的任务,worker将它取出来执行,执行完的结果通过celerycam(默认30s,所以这个进程也要启动)写入django设置的数据库,更新了这个任务的状态.比如花费的时间

我自已内部就直接使用redis的list,它支持阻塞方式读取,和队列功能一样。 —limodou

mongodb、redis居然还能用来作消息队列? 涨知识了!

http://www.oschina.net/question/12_29127