在系列中的第二篇我们已经看过了 Celery 中的执行引擎是如何执行任务的,并且在第三篇中也介绍了任务的对象,但是,目前我们看到的都是被动的任务执行,也就是说目前执行的任务都是第三方调用发送过来的。可能你会有点奇怪,难道除了第三方调用发送,还有其他的调用发送方?是的,Celery 自身也会发送任务,在本文中,你将看到 Celery 如何利用自身的定时机制运行我们设置得定时任务,并且交给 Worker 执行。

定时任务的定义

在开始讲解源码之前,我们不妨先看下我们平常都是怎么定义定时任务的,还是以我们习惯的 Demo 为例:

定义就是这么简单,这么随意,但是,想要执行却是需要我们运行一个定时器,也就是在命令行中启动 Beater,正常情况下你这么做就可以了:

然后你就会看到一个个的定时任务被发送到 MQ 中,然后被 worker 消化。

定时任务的启动

上面只是举了个如何使用的例子,但是,在 Celery 内部是如何处理这些任务才是我们需要关心的真正的点。回想一下在我们第一篇中讲 Worker 的启动流程的文章,有一个很重要的 BootStep 我们还没有讲到,那就是 Worker 的 Beat,但是我在那里排的优先级却是 2,确实如此,我也是在讲完了所有 1 的优先级之后再讲它的,所以它可以说重要,也可以说不重要。

既然都说开了,那么就不停下了,直接看看 Beat 的实现,Beat 的实现可以说是非常简单,我们一眼就可以看完:

核心还是在 create 中咯,然后关键还是看 Line 199,这里又牵扯到 celery.beat.EmbeddedService,那我们基本上就可以确定在这了。

敲黑板了,注意看这里,Line 648 就决定了是使用 线程 还是 进程 来运行 Beat 服务,但是我们应该清楚,无论是使用 线程 还是 进程,思路都是相差不远的,我们可以先找一个来看看。到这里,其实定时任务的启动工作就算是完成了,因为后面就是以独立的线程/进程执行了,主线程已经可以回去了。

定时任务的执行

其实无论是用 Thread 还是 Process,这里都是构造的 Service 对象,然后 start 的,那么这个 Service 对象具体是啥,其实也是在这个文件里面,但是我们不急着看它。在看它之前我先给大家描述一下这个文件里面几个关键的类的关系,方便大家了解:

这里就出现了 4 个类,它们之间的关系还是比较明显的,中枢部分就是 Scheduler,然后 Service 是驱动部分,最后的承载实体就是 SchedulerEntry 了,明白这层关系之后,我们再来看看 Service 是如何驱动的:

这里的灵魂一句就是 Line 557 中的这句循环了,我们知道这段代码是运行在独立的线程/进程中的,所以这里是个死循环,而循环的条件就是条件变量 shutdown 被设置了。这里不断得尝试做一件事情,这件事情就是调用 schedulertick 函数,并且根据它返回的值等待片刻,然后继续执行,所以,关于这个 tick 里面有什么东西,很值得我们关注,从上面的 UML 图中,我们可以看到 tick 是在 Scheduler 中,所以直接可以找到它:

这段代码乍一看可能会很复杂,但是实质上很简单,其中 H 是一个最小堆,它的作用就是承载了所有我们设置得定时任务,而最小堆的特性就是堆顶的元素是最小的,在这里就是 event 这个变量,那么你可能会问排序的依据是啥,排序的依据就是 Line 274 的关键词 next_time_to_run,celery 会先计算每个定时任务下一次执行的时间戳 - 当前时间戳,然后根据这个时间差值进行排序,毫无疑问,差值最小的就是下一次需要执行的任务了。

同样在 Line 274 这里还做了一个判断,那就是差值最小的那个任务现在应不应该执行 is_due,如果应该执行,那么 Line 276 - Line 285 就是执行的逻辑了,这里需要注意的一点就是 Line 277 还对出堆的元素进行了判断,以防不是我们刚才要执行的元素,这里我猜测的原因是这个 H 并不是线程安全的,在我们执行定时任务的时候,还可能有其他线程/进程在修改它,所以需要进行一个判断。

还有一个值得我们关注的点就是 Line 279 中的提交定时任务,这个也可以说是我的此行的目的,但是,我们已经有了普通异步任务的经验,相信这里不会让我们太吃惊。

正如所期待的,这里只是想将 SchedulerEntry 转换为 Task,然后至于 Task 怎么提交的异步任务,相信看过 第三篇文章的同学已经不陌生了,可以 pass 了。

那么到这里我们也算是将定时任务的执行看完了。

定时任务的持久化

虽然定时任务的执行我们是看完了,但是,定时任务还有一个很重要的地方我们还没有看,那就是持久化。在 Celery 中,定时任务的执行并不会因为我们重启了 Celery 而失效,反而在重启 Celery 之后,Celery 会根据上一次关闭之前的执行状态,重新计算新的执行周期,而这里计算的前提就是能够获取旧的执行信息,而在 Scheduler 中,这些信息都是默认保存在文件中的。

Celery 默认的存储是通过 Python 默认的 shelve 库实现的,shelve 是一个类似于字典对象的数据库,我们可以通过调用 sync 命令在磁盘和内存中同步数据。当然,你也可以自定义存储的位置,但是目前来看这个 store 存储适合 PersistentScheduler 绑定的,所以我个人更建议通过自定义 Scheduler 来实现,我曾经在 Github 开源了一个基于 Redis 的实现,感兴趣的同学可以看一下,地址是:celery redis beat

所以这个问题就变成了如何自定义 Scheduler,我根据自己的经验,总结了以下步骤:

  1. 继承 Scheduler 类,实现构造函数
  2. 实现 Schedulertick()should_sync()_do_sync()close() 等方法
  3. 启动的时候指定 Scheduler 类的包路径即可

总结

在本篇文章中,我们以 Beat 为触发点,讲解了 Celery 关于定时任务的定义、启动、执行和持久化。通过本篇文章的介绍,应该可以自己定义或者修改出更好得定时调度器了,同时我们也知道保存在当前目录下的定时文件有什么用了。

Celery 源码解析四: 定时任务的实现的更多相关文章

  1. Celery 源码解析三: Task 对象的实现

    Task 的实现在 Celery 中你会发现有两处,一处位于 celery/app/task.py,这是第一个:第二个位于 celery/task/base.py 中,这是第二个.他们之间是有关系的, ...

  2. Celery 源码解析五: 远程控制管理

    今天要聊的话题可能被大家关注得不过,但是对于 Celery 来说确实很有用的功能,曾经我在工作中遇到这类情况,就是我们将所有的任务都放在同一个队列里面,然后有一天突然某个同学的代码写得不对,导致大量的 ...

  3. Celery 源码解析六:Events 的实现

    在 Celery 中,除了远程控制之外,还有一个元素可以让我们对分布式中的任务的状态有所掌控,而且从实际意义上来说,这个元素对 Celery 更为重要,这就是在本文中将要说到的 Event. 在 Ce ...

  4. Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

    Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的?   如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...

  5. Sentinel源码解析四(流控策略和流控效果)

    引言 在分析Sentinel的上一篇文章中,我们知道了它是基于滑动窗口做的流量统计,那么在当我们能够根据流量统计算法拿到流量的实时数据后,下一步要做的事情自然就是基于这些数据做流控.在介绍Sentin ...

  6. Dubbo 源码解析四 —— 负载均衡LoadBalance

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...

  7. iOS即时通讯之CocoaAsyncSocket源码解析四

    原文 前言: 本文为CocoaAsyncSocket源码系列中第二篇:Read篇,将重点涉及该框架是如何利用缓冲区对数据进行读取.以及各种情况下的数据包处理,其中还包括普通的.和基于TLS的不同读取操 ...

  8. React的React.createContext()源码解析(四)

    一.产生context原因 从父组件直接传值到孙子组件,而不必一层一层的通过props进行传值,相比较以前的那种传值更加的方便.简介. 二.context的两种实现方式 1.老版本(React16.x ...

  9. Celery 源码解析七:Worker 之间的交互

    前面对于 Celery 的分布式处理已经做了一些介绍,例如第五章的 远程控制 和第六章的 Event机制,但是,我认为这些分布式都比较简单,并没有体现出多实例之间的协同作用,所以,今天就来点更加复杂的 ...

随机推荐

  1. nodejs+express+mongoose无法获取数据库数据问题解决

    通过mongoose与mongodb进行操作.而mongoose是通过model来创建mongodb中对应的collection的,这样你通过如下的代码: mongoose.model('User', ...

  2. Angular JS中的路由

      前  言            本章节将为大家介绍 AngularJS 路由.AngularJS 路由允许我们通过不同的 URL 访问不同的内容.通过 AngularJS 可以实现多视图的单页We ...

  3. 读书笔记之宿舍共享wifi

    若有某方面侵权,请邮件1047697114@qq.com,一个工作日即可处理,谢谢   目录一.简单安装虚拟机二.简单设置,开热点! 我没试过那些wifi软件之类的,以下是个人测试的过程 一.简单安装 ...

  4. C# 7 局部函数剖析

    局部函数是C# 7中的一个新功能,允许在一个函数中定义另一个函数. 何时使用局部函数? 局部函数的主要功能与匿名方法非常相似:在某些情况下,创建一个命名函数在读者的认知负担方面代价太大.有时,函数本身 ...

  5. spring cloud+dotnet core搭建微服务架构:Api网关(三)

    前言 国庆假期,一直没有时间更新. 根据群里面的同学的提问,强烈推荐大家先熟悉下spring cloud.文章下面有纯洁大神的spring cloud系列. 上一章最后说了,因为服务是不对外暴露的,所 ...

  6. php soap实现WebService接口

    nusoap是php写的一个功能文件,下载地址:http://pan.baidu.com/s/1i3mUQJr 一.不使用wsdl服务端 server.php <?php //包函nusoap. ...

  7. CentOS7 Redis安装

    Redis介绍 1.安装Redis 官方下载地址:http://download.redis.io 使用Linux下载:wget http://download.redis.io/redis-stab ...

  8. iOS的异步绘制--YYAsyncLayer源码分析

    iOS的异步渲染 最近看了YYAsyncLayer在这里总结一下.YYAsyncLayer是整个YYKit异步渲染的基础.整个项目的Github地址在这里.你可以先下载了一睹为快,也可以跟着我一步一步 ...

  9. JQuery自定义插件详解之Banner图滚动插件

      前  言 JRedu JQuery是什么相信已经不需要详细介绍了.作为时下最火的JS库之一,JQuery将其"Write Less,Do More!"的口号发挥的极致.而帮助J ...

  10. Apache配置腾讯云SSL证书指引

    一.安装Apache 1) 使用yum安装Apache # yum install httpd 2) 修改测试页面 # vim /var/www/html/index.heml PS:修改为测试内容, ...