最近需要考虑如何在django环境中跑定时任务. 这个在 stackoverflow也有对应的 讨论, 方法也有不少, 这边简单尝试和总结下.
假设我们现在的定期任务就是睡眠 n秒, 然后往数据库里面写一条记录, 记录这个任务的起始以及结束时间, 并且我们 不关心该任务的返回结果. 项目名称为 mmtest, 应用名称为 mma_cron(说实话我也不知道自己怎么取这样的名称….), 数据库使用 sqlite, model代码如下:
最朴素的方法
考虑一个最最朴素的实现: 起一个简单的daemon程序, 每隔一定时间进行任务处理; 或者是写一个简单的程序, 利用 cron来定期执行该脚本. 一个好处是足够简单. 但很多时候写这样的程序需要花不少时间.
简单的改进
自己重新写一个程序有时候代价还是挺大的, 尤其是业务逻辑十分复杂的时候, 所以可以考虑复用已有的代码. 如果考虑 cron定期执行的策略, 我们可以在 django中实现对应逻辑, 方法也有几个:
- 实现一个api, 定期请求
- 扩展manage.py
方法一没什么好说的, 但是需要考虑访问控制; 方法二的话也很简单, 可以参考 自定义commands. 无论用哪种方式, 我们可以先实现对应的任务处理逻辑:
针对方法一, 实现对应的view:
添加对应的route:
我们手动访问试一下: curl 127.0.0.1:8000/mma_cron/task/ddddd, 可以看到在经过一段时间后数据库中有结果了. 可行~
针对方法二, 扩展 manage.py, 实现对应的command:
再手动试一下: python manage.py yy, 发现也有对应的结果, 可行~
至于定期可以通过设定cron来实现.
插件
上面的实现比较简便, 也很有效. django的插件也有对应的实现, 这里着重介绍一下 django-cron. 它类似使用了上面提到的方法二, 在此之上增加了 任务执行检查, 日志记录等功能. 我们也来看一下;)
安装的话推荐直接把源码下的 django_cron(相当于一个django的应用)目录放到当前工程目录. 使用可以参考 这里. 接下来需要执行下它的迁移操作(migration):
该操作是创建对应的数据库, 之后执行定时任务时会把 时间和 相关结果保存到数据库中. 接下来我们需要在 mma_cron中定义一个对应的定时任务:
可以看到里面定义了该定时任务的 处理间隔与相应动作. 最后我们还需要在 settings.py注册一下我们的定时任务:
大功告成, 接下来我们只需在执行 python manage.py runcrons即可跑我们的任务了. 运行时 django_cron会根据任务的执行时间与数据库中记录的时间做对比, 如果没到对应时间会直接退出. 每次执行完都会在 django_cron_cronjoblog这张表中记录对应的时间以及返回的结果(原来python不是默认把最后一个语句的结果当成返回值的, 好吧). 同样的, 为了让其定时跑, 我们也需要在crontab中添加对应的命令.
值得一提的是如果注册了多个任务, 这些任务默认是串行执行的(考虑到安全性). 如果想要并行执行需要改一些设置, 详见文档.
Celery
上述几种方法都比较简单, 但是我们还有其他的方式;) 比如很多人都提到的
Celery. 给我的感觉它很像 gearman和 sidekiq, 类似一个任务分发和处理框架. 利用它的 Periodic Tasks, 可以实现定期发布任务让worker取执行任务. 我们也来简单看看.
简单尝试
我们先不管django, 先感性的了解下Celery. Celery的 任务分发和 结果存储需要依赖外部组件, 可选的有 RabbitMQ, Redis, 数据库等等. 简单起见, 这里选择使用 redis( RabbitMQ部署起来还是稍微复杂了点).
首先根据官方的 教程, 创建个分发 加法和 乘法任务的系统. 先来看看 worker的代码:
再来看看 celery的设置代码:
我们先启动 redis, 然后通过执行 celery -A proj worker -l info来启动一个 worker. 接下来我们手动来创建任务, 然后丢给worker:
这样就创建一个任务, 我们在celery的控制台(worker进程)可以看到这样的输出:
很直观~ 我们同时也可以看看它在redis里面是什么个样子:
现在我们是手动来创建任务, 我们可以启动一个定时生产任务的生产者 celery -A proj beat -l info. 它会定期创建我们在 proj/celery.py代码中指定的 CELERYBEAT_SCHEDULE中的任务.
结合django
理解了celery, 再结合django就相对比较简单了. 推荐阅读一下官方的 文档. 结合我们的SimpleTask的例子, 先设定下celery的任务:
接下来我们需要实现一下我们的worker:
不需要其他的工作, 我们就完成了我们的目的. 接下来就是分别启动 worker和 beat了. 这也实现了我们的定时任务的目的(就我们这个例子有点大材小用了).
选择
就我个人观点, 我比较倾向于改进后的朴素方法. 原因就是业务比较简单, 比较轻量级.
整个过程还是学到了不少东西. 继续学习python~
原文:http://blog.aka-cool.net/blog/2015/05/14/cron-job-in-django/
0 Comments