为什么django不是异步(2023年最新整理)

是这样的,想请讲解下,为什么django不是异步(2023年最新整理)
最新回答
我咋那么萌捏

2024-11-30 11:47:46

导读:很多朋友问到关于为什么django不是异步的相关问题,本文首席CTO笔记就来为大家做个详细解答,供大家参考,希望对大家有所帮助!一起来看看吧!

Django中怎么使用django-celery完成异步任务

许多Django应用需要执行异步任务,以便不耽误httprequest的执行.我们也可以选择许多方法来完成异步任务,使用Celery是一个比较好的选择,因为Celery

有着大量的社区支持,能够完美的扩展,和Django结合的也很好.Celery不仅能在Django中使用,还能在其他地方被大量的使用.因此一旦学会使用Celery,我

们可以很方便的在其他项目中使用它.

1.Celery版本

本篇博文主要针对Celery3.0.x.早期版本的Celery可能有细微的差别.

2.Celery介绍

Celery的主要用处是执行异步任务,可以选择延期或定时执行功能.为什么需要执行异步任务呢?

第一,假设用户正发起一个request,并等待request完成后返回.在这一request后面的view功能中,我们可能需要执行一段花费很长时间的程序任务,这一时间

可能远远大于用户能忍受的范围.当这一任务并不需要立刻执行时,我们便可以使用Celery在后台执行,而不影响用户浏览网页.当有任务需要访问远程服务器完

成时,我们往往都无法确定需要花费的时间.

第二则是定期执行某些任务.比如每小时需要检查一下天气预报,然后将数据储存到数据库中.我们可以编写这一任务,然后让Celery每小时执行一次.这样我们

的web应用便能获取最新的天气预报信息.

我们这里所讲的任务task,就是一个Python功能(function).定期执行一个任务可以被认为是延时执行该功能.我们可以使用Celery延迟5分钟调用function

task1,并传入参数(1,2,3).或者我们也可以每天午夜运行该function.

我们偏向于将Celery放入项目中,便于task访问统一数据库和Django设置.

当task准备运行时,Celery会将其放入列队queue中.queue中储存着可以运行的task的list.我们可以使用多个queue,但为了简单,这里我们只使用一个.

将任务task放入queue就像加入todolist一样.为了使task运行,我们还需要在其他线程中运行的苦工worker.worker实时观察着代运行的task,并逐一运行这

些task.你可以使用多个worker,通常他们位于不同服务器上.同样为了简单起见,我们这只是用一个worker.

我们稍后会讨论queue,worker和另外一个十分重要的进程,接下来我们来动动手:

3.安装Celery

我们可以使用pip在vietualenv中安装:

pipinstalldjango-celery

4.Django设置

我们暂时使用djangorunserver来启动celery.而Celery代理人(broker),我们使用Djangodatabasebrokerimplementation.现在我们只需要知道Celery

需要broker,使用django自身便可以充当broker.(但在部署时,我们最好使用更稳定和高效的broker,例如Redis.)

在settings.py中:

importdjcelery

djcelery.setup_loader()

BROKER_URL='django://'

...

INSTALLED_APPS=(

...

'djcelery',

'kombu.transport.django',

...

)

第一二项是必须的,第三项则告诉Celery使用Django项目作为broker.

在INSTALLED_APPS中添加的djcelery是必须的.kombu.transport.django则是基于Django的broker

最后创建Celery所需的数据表,如果使用South作为数据迁移工具,则运行:

pythonmanage.pymigrate

否则运行:(Django1.6或Django1.7都可以)

pythonmanage.pysyncdb

5.创建一个task

正如前面所说的,一个task就是一个Pyhtonfunction.但Celery需要知道这一function是task,因此我们可以使用celery自带的装饰器decorator:@task.在

djangoapp目录中创建taske.py:

fromceleryimporttask

@task()

defadd(x,y):

returnx+y

当settings.py中的djcelery.setup_loader()运行时,Celery便会查看所有INSTALLED_APPS中app目录中的tasks.py文件,找到标记为task的function,并

将它们注册为celerytask.

将function标注为task并不会妨碍他们的正常执行.你还是可以像平时那样调用它:z=add(1,2).

6.执行task

让我们以一个简单的例子作为开始.例如我们希望在用户发出request后异步执行该task,马上返回response,从而不阻塞该request,使用户有一个流畅的访问

过程.那么,我们可以使用.delay,例如在在views.py的一个view中:

frommyapp.tasksimportadd

...

add.delay(2,2)

...

Celery会将task加入到queue中,并马上返回.而在一旁待命的worker看到该task后,便会按照设定执行它,并将他从queue中移除.而worker则会执行以下代

码:

importmyapp.tasks.add

myapp.tasks.add(2,2)

7.关于import

这里需要注意的是,在impprttask时,需要保持一致.因为在执行djcelery.setup_loader()时,task是以INSTALLED_APPS中的app名,

加.tasks.function_name注册的,如果我们由于pythonpath不同而使用不同的引用方式时(例如在tasks.py中使用frommyproject.myapp.tasksimport

add形式),Celery将无法得知这是同一task,因此可能会引起奇怪的bug.

8.测试

a.启动worker

正如之前说到的,我们需要worker来执行task.以下是在开发环境中的如何启动worker:

首先启动terminal,如同开发django项目一样,激活virtualenv,切换到django项目目录.然后启动django自带web服务器:pythonmanage.pyrunserver.

然后启动worker:

pythonmanage.pyceleryworker--loglevel=info

此时,worker将会在该terminal中运行,并显示输出结果.

b.启动task

打开新的terminal,激活virtualenv,并切换到django项目目录:

$pythonmanage.pyshell

frommyapp.tasksimportadd

add.delay(2,2)

此时,你可以在worker窗口中看到worker执行该task:

[2014-10-0708:47:08,076:INFO/MainProcess]Gottaskfrombroker:myapp.tasks.add[e080e047-b2a2-43a7-af74-d7d9d98b02fc]

[2014-10-0708:47:08,299:INFO/MainProcess]Taskmyapp.tasks.add[e080e047-b2a2-43a7-af74-d7d9d98b02fc]succeededin0.183349132538s:4

9.另一个例子

下面我们来看一个更为真实的例子,在views.py和tasks.py中:

#views.py

frommyapp.tasksimportdo_something_with_form_data

defview(request):

form=SomeForm(request.POST)

ifform.is_valid():

data=form.cleaned_data

#Scheduleatasktoprocessthedatalater

do_something_with_form_data.delay(data)

returnrender_to_response(...)

#tasks.py

@task

defdo_something_with_form_data(data):

call_slow_web_service(data['user'],data['text'],...)

10.调试

由于Celery的运行需要启动多个部件,我们可能会漏掉一两个.所以我们建议:

使用最简单的设置

使用pythondebug和logging功能显示当前的进程

11.Eager模式

如果在settings.py设置:

CELERY_ALWAYS_EAGER=True

那么Celery便以eager模式运行,则task便不需要加delay运行:

#若启用eager模式,则以下两行代码相同

add.delay(2,2)

add(2,2)

12.查看queue

因为我们使用了django作为broker,queue储存在django的数据库中.这就意味着我们可以通过djangoadmin查看该queue:

#admin.py

fromdjango.contribimportadmin

fromkombu.transport.djangoimportmodelsaskombu_models

admin.site.register(kombu_models.Message)

13.检查结果

每次运行异步task后,Celery都会返回AsyncResult对象作为结果.你可以将其保存,然后在将来查看该task是否运行成功和返回结果:

#views.py

result=add.delay(2,2)

...

ifresult.ready():

print"Taskhasrun"

ifresult.successful():

print"Resultwas:%s"%result.result

else:

ifisinstance(result.result,Exception):

print"Taskfailedduetoraisinganexception"

raiseresult.result

else:

print"Taskfailedwithoutraisingexception"

else:

print"Taskhasnotyetrun"

14.定期任务

还有一种Celery的常用模式便是执行定期任务.执行定期任务时,Celery会通过celerybeat进程来完成.Celerybeat会保持运行,一旦到了某一定期任务需要执

行时,Celerybeat便将其加入到queue中.不像worker进程,Celerybeat只有需要一个即可.

启动Celerybeat:

pythonmanage.pycelerybeat

使Celery运行定期任务的方式有很多种,我们先看第一种,将定期任务储存在django数据库中.即使是在django和celery都运行的状态,这一方式也可以让我们

方便的修改定期任务.我们只需要设置settings.py中的一项便能开启这一方式:

#settings.py

CELERYBEAT_SCHEDULER='djcelery.schedulers.DatabaseScheduler'

django的异步请求非阻塞是怎么实现的

你应该是使用了Django自己的开发服务器跑的例子,在Django关于manage.py的文档中写道:

--nothreading

Thedevelopmentserverismultithreadedbydefault.Usethe--nothreadingoptiontodisabletheuseofthreadinginthedevelopmentserver.

也就是说,默认情况下你使用./manage.pyrunserver会开启多个线程对HTTP请求进行伺服,所以第二个请求进来时虽然第一个请求仍在sleep,但已经新开了一个线程进行响应处理,看起来像是“非阻塞”的工作模式,其实质是多线程而非单线程,想禁用这一行为也已经给出了答案,加上--nothreading参数:./manage.pyrunserver--nothreading即可。

我为什么从python转向go

坦白的讲,在一帮python用户面前讲为什么放弃python转而用go其实是一件压力蛮大的事情,语言之争就跟vim和emacs之争一样,是

一个永恒的无解话题,稍微不注意就可能导致粉丝强烈地反击。所以我只会从我们项目实际情况出发,来讲讲为什么我最终选择了go。

为什么放弃python

首先,我其实得说说为什么我们会选择python。在我加入企业快盘团队之前,整个项目包括更早的金山快盘都是采用python进行开发的。至于为

什么这么选择,当时的架构师葱头告诉我,主要是因为python上手简单,开发迅速。对于团队里面大部分完全没服务端开发经验的同学来说,python真

的是一个很好的选择。

python的简单高效,我是深有体会的。当时私有云项目也就几个程序员,但是我们要服务多家大型企业,进行定制化的开发,多亏了python,我们才能快速出活。后来企业快盘挂掉之后,我们启动轻办公项目,自然也使用python进行了原始版本的构建。

python虽然很强大,但我们在使用的时候也碰到了一些问题,主要由如下几个方面:

动态语言

python是一门动态强类型语言。但是,仍然可能出现int+string这样的运行时错误,因为对于一个变量,在写代码的时候,我们有时候很容易就忘记这个变量到底是啥类型的了。

在python里面,可以允许同名函数的出现,后一个函数会覆盖前一个函数,有一次我们系统一个很严重的错误就是因为这个导致的。

上面说到的这些,静态语言在编译的时候就能帮我们检测出来,而不需要等到运行时出问题才知道。虽然我们有很完善的测试用例,但总有case遗漏的情况。所以每次出现运行时错误,我心里都想着如果能在编译的时候就发现该多好。

性能

其实这个一直是很多人吐槽python的地方,但python有它适合干的事情,硬是要用python进行一些高性能模块的开发,那也有点难为它了。

python的GIL导致无法真正的多线程,大家可能会说我用多进程不就完了。但如果一些计算需要涉及到多进程交互,进程之间的通讯开销也是不得不考虑的。

无状态的分布式处理使用多进程很方便,譬如处理http请求,我们就是在nginx后面挂载了200多个djangoserver来处理http的,但这么多个进程自然导致整体机器负载偏高。

但即使我们使用了多个django进程来处理http请求,对于一些超大量请求,python仍然处理不过来。所以我们使用openresty,将高频次的http请求使用lua来实现。可这样又导致使用两种开发语言,而且一些逻辑还得写两份不同的代码。

同步网络模型

django的网络是同步阻塞的,也就是说,如果我们需要访问外部的一个服务,在等待结果返回这段时间,django不能处理任何其他的逻辑(当然,多线程的除外)。如果访问外部服务需要很长时间,那就意味着我们的整个服务几乎在很长一段时间完全不可用。

为了解决这个问题,我们只能不断的多开django进程,同时需要保证所有服务都能快速的处理响应,但想想这其实是一件很不靠谱的事情。

异步网络模型

tornado的网络模型是异步的,这意味着它不会出现django那样因为外部服务不可用导致这个服务无法响应的问题。话说,比起django,我可是非常喜欢tornado的,小巧简单,以前还写过几篇深入剖析tornado的文章了。

虽然tornado是异步的,但是python的mysql库都不支持异步,这也就意味着如果我们在tornado里面访问数据库,我们仍然可能面临因为数据库问题造成的整个服务不可用。

其实异步模型最大的问题在于代码逻辑的割裂,因为是事件触发的,所以我们都是通过callback进行相关处理,于是代码里面就经常出现干一件事情,传一个callback,然后callback里面又传callback的情况,这样的结果就是整个代码逻辑非常混乱。

python没有原生的协程支持,虽然可以通过gevent,greenlet这种的上patch方式来支持协程,但毕竟更改了python源码。另

外,python的yield也可以进行简单的协程模拟,但毕竟不能跨堆栈,局限性很大,不知道3.x的版本有没有改进。

开发运维部署

当我第一次使用python开发项目,我是没成功安装上项目需要的包的,光安装成功mysql库就弄了很久。后来,是一位同事将他整个python目录打包给我用,我才能正常的将项目跑起来。话说,现在有了docker,是多么让人幸福的一件事情。

而部署python服务的时候,我们需要在服务器上面安装一堆的包,光是这一点就让人很麻烦,虽然可以通过puppet,salt这些自动化工具解决部署问题,但相比而言,静态编译语言只用扔一个二进制文件,可就方便太多了。

代码失控

python非常灵活简单,写c几十行代码才能搞定的功能,python一行代码没准就能解决。但是太简单,反而导致很多同学无法对代码进行深层次的思

考,对整个架构进行细致的考量。来了一个需求,啪啪啪,键盘敲完开速实现,结果就是代码越来越混乱,最终导致了整个项目代码失控。

虽然这也有我们自身的原因,譬如没好的代码review机制,没有好的项目规范,但个人感觉,如果一个程序员没经过良好的编码训练,用python很容易就写出烂的代码,因为太自由了。

当然,我这里并不是说用python无法进行大型项目的开发,豆瓣,dropbox都是很好的例子,只是在我们项目中,我们的python代码失控了。

上面提到的都是我们在实际项目中使用python遇到的问题,虽然最终都解决了,但是让我愈发的觉得,随着项目复杂度的增大,流量性能压力的增大,python并不是一个很好的选择。

为什么选择go

说完了python,现在来说说为什么我们选择go。其实除了python,我们也有其他的选择,java,php,lua(openresty),但最终我们选择了go。

虽然java和php都是最好的编程语言(大家都这么争的),但我更倾向一门更简单的语言。而openresty,虽然性能强悍,但lua仍然是动

态语言,也会碰到前面说的动态语言一些问题。最后,前金山许式伟用的go,前快盘架构师葱头也用的go,所以我们很自然地选择了go。

go并不是完美,一堆值得我们吐槽的地方。

error,好吧,如果有语言洁癖的同学可能真的受不了go的语法,尤其是约定的最后一个返回值是error。项目里面经常会充斥这样的代码:

if_,err:=w.Write(data1);err!=nil{

returunerr

}

if_,err:=w.Write(data2);err!=nil{

returunerr

}

难怪有个梗是对于一个需求,java的程序员在写配置的时候,go程序员已经写了大部分代码,但是当java的程序员写完的时候,go程序员还在写err!=nil。

这方面,errors-are-values倒是推荐了一个不错的解决方案。

包管理,go的包管理太弱了,只有一个go

get,也就是如果不小心更新了一个外部库,很有可能就导致现有的代码编译不过了。虽然已经有很多开源方案,譬如godep以及现在才出来的gb等,但毕

竟不是官方的。貌似google也是通过vendor机制来管理第三方库的。希望go1.5或者之后的版本能好好处理下这个问题。

GC,java的GC发展20年了,go才这么点时间,gc铁定不完善。所以我们仍然不能随心所欲的写代码,不然在大请求量下面gc可能会卡顿整个服务。所以有时候,该用对象池,内存池的一定要用,虽然代码丑了点,但好歹性能上去了。

泛型,虽然go有inteface,但泛型的缺失会让我们在实现一个功能的时候写大量的重复代码,譬如int32和int64类型的sort,我们

得为分别写两套代码,好冗余。go1.4之后有了go

generate的支持,但这种的仍然需要自己根据go的AST库来手动写相关的parser,难度也挺大的。虽然也有很多开源的generate实现,

但毕竟不是官方的。

当然还有很多值得吐槽的地方,就不一一列举了,但是go仍旧有它的优势。

静态语言,强类型。静态编译能帮我们检查出来大量的错误,go的强类型甚至变态