前言 当使用Django执行脚本的时候,经常遇到一种情况:跳转到某个url,结果是先在后台执行一个时间较长的脚本,然后才能打开这个url页面,这样用户体验就很不好。
比如说像这样的views.py
配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @csrf_exempt def run_command (request) : command = "ansible all -i /root/.ssh/hosts -m shell -a 'echo 'worinixianren' >> /tmp/xianren.txt'" if request.method == 'POST' : id = request.POST.getlist("ecs" ) num = [] num.append(len(id)) name = [] db = pymysql.connect("阿里云数据库" ,"数据库账号" ,"数据库密码" ,"databases名" ) cursor = db.cursor() with open('/root/.ssh/hosts' ,'w' ) as f: for i in id: sql = 'select * from createyaml_ecs where name = "' + i + '";' cursor.execute(sql) ip = cursor.fetchall()[0 ][3 ] cursor.execute(sql) name.append(cursor.fetchall()[0 ][1 ]) f.write(ip+" ansible_ssh_user=root" +"\n" ) db.close() child = subprocess.Popen(command,stdout=subprocess.PIPE, stderr=subprocess.PIPE,shell=True ) stdout, stderr = child.communicate() return render(request,'run_command.html' ,{'data' :num[0 ],'name' :name}) else : return render(request,'homepage.html' )
像上面这段代码,要看到run_command.html
页面就要先把整个ansible部署的脚本全跑完,如果是几百台机器批量操作的脚本,那就要等到海枯石烂水倒流。那遇到这样的情况怎么解决呢?根据不同的请求,有不同的对策:
单纯的后台跑一个脚本,那么就可以使用Celery
;
在后台跑脚本的同时,还需要不断的向后台发送请求(比如微信上的茶叶妹聊天机器人),那么就要使用Channels
;
Celery
原理部分和配置定时任务就不多说了,文末的参考资料里有网站,这里主要说的是如何配置Celery
。
环境交代 存储后端:阿里云redis(需要支持evalsha
命令,如果不支持,去控制台升级小版本
即可) Python:3.6.5
Django:2.1.1
django-celery:3.2.2
,安装方法:pip install django-celery
celery-with-redis:3.0
,安装方法pip install celery-with-redis
celery:3.1.26.post2
具体配置 首先配置setting.py
,全文最后添加这样几句话:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #celery配置信息 #celery中间人 redis://:redis密码@redis服务所在的ip地址:端口/数据库号,我用的是254号 #channels配置redis也是这样配置,如果没有密码,就可以把':redis密码@'省略 BROKER_URL = 'redis://:redis密码@阿里云redis地址:6379/254' #celery结果返回,可用于跟踪结果 CELERY_RESULT_BACKEND = 'redis://:redis密码@阿里云redis地址:6379/254' #celery内容等消息的格式设置 CELERY_ACCEPT_CONTENT = ['application/json' ,] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' #celery时区设置,使用settings中TIME_ZONE同样的时区 CELERY_TIMEZONE = TIME_ZONE
在setting.py
同级的文件夹里创建celery.py
,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from __future__ import absolute_import, unicode_literals from celery import Celeryfrom django.conf import settingsimport os project_name = os.path.split(os.path.abspath('.' ))[-1 ] project_settings = '%s.settings' % project_name os.environ.setdefault('DJANGO_SETTINGS_MODULE' , project_settings) app = Celery(project_name) app.config_from_object('django.conf:settings' ) app.autodiscover_tasks(lambda : settings.INSTALLED_APPS)
还是在同样的文件夹里,编辑__init__.py
:
1 2 3 4 5 from __future__ import absolute_import, unicode_literals from .celery import app as celery_app
然后在app(具体应用的文件夹里),创建一个叫tasks.py
,这里面就是需要在后台执行的具体脚本:
1 2 3 4 5 6 7 8 9 10 11 from celery.decorators import taskimport subprocess@task #在原有的方法上加上celery装饰器task def run_ansible () : command = "ansible all -i /root/.ssh/hosts -m shell -a 'echo 'worinixianren' >> /tmp/xianren.txt'" child = subprocess.Popen(command,stdout=subprocess.PIPE, stderr=subprocess.PIPE,shell=True ) stdout, stderr = child.communicate() print ("success!!!" )
保存退出之后,修改原有的views.py
,把原来涉及脚本的字段删除,改成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @csrf_exempt def run_command (request) : if request.method == 'POST' : id = request.POST.getlist("ecs" ) num = [] num.append(len(id)) name = [] db = pymysql.connect("阿里云数据库" ,"数据库账号" ,"数据库密码" ,"databases名" ) cursor = db.cursor() with open('/root/.ssh/hosts' ,'w' ) as f: for i in id: sql = 'select * from createyaml_ecs where name = "' + i + '";' cursor.execute(sql) ip = cursor.fetchall()[0 ][3 ] cursor.execute(sql) name.append(cursor.fetchall()[0 ][1 ]) f.write(ip+" ansible_ssh_user=root" +"\n" ) db.close() run_ansible.delay() return render(request,'run_command.html' ,{'data' :num[0 ],'name' :name}) else : return render(request,'homepage.html' )
返回到manage.py
所在的目录,先正常启动django,然后再/usr/local/python3/bin/celery -A project名称 worker -l info
启动celery,如图:akb48
看到tasks.py
已经成功被celery使用了,然后在页面上去执行原本的命令,就会看到celery页面有刷新:akb48
此时再去redis里查看一下存储的效果:akb48
可见tasks执行的状态已经被保存到了redis里。但是上面我们是在前台页面启动celery,如果想把celery作为一个后台守护进程,那么命令语句如下:
1 /usr/local/python3/bin/celery multi start worker -A project名称 -l info
效果如图:akb48
停止或重启将上面的start
换为stop
或restart
即可。
补充 如果tasks.py
内容变化了,需要重启celery才能生效。
如果在启动celery的时候,日志有写UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments! warnings.warn('Using settings.DEBUG leads to a memory leak, never '
,那么就在settings.py
里把DEBUG = True
改成DEBUG = False
即可。
查看redis有几个库的命令:config get databases
。
参考资料 http://yshblog.com/blog/163 (对照代码做一遍就更有体会了)https://www.cnblogs.com/wdliu/p/9517535.html (原理以及如何配置定时任务)https://www.cnblogs.com/wdliu/p/9530219.html http://docs.celeryproject.org/en/latest/getting-started/brokers/redis.html