序列文章:

Task 的实现在 Celery 中你会发现有两处,一处位于 celery/app/task.py,这是第一个;第二个位于 celery/task/base.py 中,这是第二个。他们之间是有关系的,你可以认为第一个是对外暴露的接口,而第二个是具体的实现!所以,我们由简入繁,先来看看对外的接口:

其实这就是个我们声明 Task 的对象,例如我们使用这么一段代码:

我们可以看看 add 对象是啥:

In [1]: add
Out[1]: <@task: worker.add of tasks:0x10c9b06d0>

你会发现其实他就是我们的一个 Task 对象,所以你就可以观察一下我们平时使用这个 add 的形式在里面是如何实现的了,例如我们最常使用的可能就两种方式了,分别是:

In [2]: add.delay()
In [3]: add.apply_async()

其他你看一下源码就会发现他们的实现是一样的,就像这样:

我们现在很清楚,调用 apply_async 是将我们的 Task 提交到 MQ 中,然后获得一个 celery.result.AsyncResult 对象,那么具体都做了哪些工作,还是需要进一步查看的。apply_async 的参数有很多,所以我们需要先给他归个类,这样就好看多了,概括着看,可以分为这么几类:

  • AMQP 类:connection、queue、exchange、routing_key、producer、publisher、headers
  • MQ 策略类:countdown、eta、expires、retry、retry_policy、priority、
  • 管理类:shadow、serializer、compression、add_to_parent
  • 其他:args、kwargs、link、link_error、

这样一看就感觉一目了然多了,AMQP 类的我们就不关注了,毕竟都看了这么多了,应该大家都熟悉了。这里的主要关注点还是在 MQ 策略类和管理类上,着重在 MQ 策略类上,因为管理类的功能稍微比较简单一些。

async 发送消息

apply_async 中,我们可以看到有两处执行逻辑,第一处是:

这里是直接调用 apply,然后这里的条件 task_always_eager 是什么意思我们还没见过,可以看一下文档:

ok,了解,其实就是说这是个同步的接口,那么我们就可以对应着看到下面这处应该是异步的实现咯:

既然如此,我们一个个得来看。

同步发送消息实现

同步执行消息的一层函数比较简单,只是简单的构建了一个 tracer,然后就从 tracer 调用中拿到调用结果,我们看上去会比较舒服:

但是,这个 tracer 的内容就复杂啦,但是这个 build_tracer 的构建函数不需要太过关注,所以我们需要关注的是 build_tracer 返回的这个 tracer 函数,但是这个函数的内容很多,为了简约一下,所以给大家抽象了一番。同步调用过程中,可以分为几部分功能,分别是:

  • 信号处理:执行前/后/成功这几个时刻需要释放一些信号给感兴趣的成员
  • 失败处理:对于没有执行的情况需要进行细分处理,例如:reject/ignore/retry/exception 等
  • 依赖处理:因为 Celery 支持一些简单的依赖,所以执行完成之后需要执行被依赖的 tasks
  • 执行逻辑:这个就是正常的函数调用咯
  • 其他:例如统计执行时间,出入栈之类的

我们就看下任务的执行逻辑是怎么样的,在代码里面是很简单的一个函数调用,其实就是看 Task 对象有没有实现 __call__ 方法,如果没有就使用 run 方法:

那 task 的 __call__ 实现也不是太复杂,其实最后调用的也是 run 方法,所以到最后都还是 run 方法的责任啦,但是,这里的基类是不实现 run 方法的,所以这个实现就落实到具体的实现类中了,那么你以为 run 方法会在 celery/app/base.py 中实现?我之前也是这么想的,但是,后来我发现不是的,这个实现其实就是我们在代码里面使用 @app.task 装饰的函数,其实就是讲我们自定义的函数封装成 run,这样调用 run 不就执行的我们的函数了吗?有意思吧,这个封装的方式我们后面再说,也就是说同步的方式我们就到此吧,也差不多了。

异步发送消息实现

看完同步的我们再来看看异步的,在说异步的之前,我们先思考一下,异步的应该是怎样?之前看的时候我猜想异步不就是把 Task 对象塞进 MQ 中么,就应该是这么简单,但是,看完之后发现还是 too young 了,因为从同步中我们就可以看出,还是有很多功课要完成的,不管怎样,一起来再看一遍。

从前边我们说有同步和异步两种形式那里我们可以发现同步和异步的除了功能不一样之外,还有调用的对象也不一样,同步的是调用 Task 自己的方法,也就是说消息被 Task 自己消化了;而异步的确实使用的 Celery 对象的方法,也就是说还得依赖于 Celery 这个 Boss 来实现。这是为啥呢?很明显嘛,Task 自身没有关于 MQ 的任何消息,而只有一个绑定的 Celery 对象,所以从抽象层面就只能交给 Celery 了,而 Celery 却包含了所有你需要的信息,是可以完成这个任务的。

所以,异步的消息到了 Celery 是这么被发出去的:

这里出现了一个我们还没怎么接触过的 amqp,但是没关系,随着等下的了解,我们会认识到它的,这里的几个关键步骤都是通过 amqp 来完成的,所以我们应该着重看看他们

异步消息体的创建

在 Celery 中,异步消息体是通过 create_task_message 来创建的,我们可以发现,这里是传了一大堆参数进去,但是,无妨,对于这些参数,我们大部分都在前面见过了,不怵,主要还是需要关注一下内部都为消息体做了什么工作:

这里可以发现两件事情

  1. 消息体的预处理都是在这里完成的,例如检验和转换参数格式
  2. 构建消息就用了四个属性:headerspropertiesbodysent_event

这里其实就是所有的构建消息体的代码了,为什么呢,因为 task_message 是一个 nametuple:

异步消息的发送

异步消息的发送这里不是直接就调用的一个函数,而是动态得创建了一个 sender ,然后才调用这个 sender 发送的(没搞懂为啥,为了扩展?)。而创建 sender 的逻辑倒是比较简单,所以忽略了,直接来看真正的 send 操作是如何完成的,其实之前提过了,这里真正的 send 操作就像之前我们看同步的执行逻辑一样尿性,又臭又长,真的,又臭又长,而且作者自己都加注释承认了,他的理由是为了性能!

同样得,为了方便我们的理解,我还是采用抽丝剥茧的方式来给大家介绍一下,首先,我习惯性得分个类:

  • MQ 的各项功能:routing_key/exchange/delivery_mode/retry
  • 任务执行的前后处理:发送前/发送后
  • 真正的发送逻辑
  • 其他

其实重头戏应该在 MQ 的参数确定上,因为只要这些参数都确定了,消息的发送只是一个 producer.publish 就解决的事情,所以我们花些精力来看看 MQ 的参数都是怎么决定出来的:

  • queue_name
    1. 调用 task.delay 的时候传的,没传并且也没传 exchange 那就是 default
    2. 不会出现传了 exchange 但是不传 queue
  • routing_key
    1. 调用 task.delay 的时候传的,没传就看 exchange 有没有,没有就是 queue 的值了
    2. 如果参数传了 exchange,那么就是配置中的默认 routing_key
  • exchange:
    1. 调用 task.delay 的时候传的,没传但是 exchange_type 类型是 direct,那么就是 ""
    2. 如果类型不是 direct,那么 queue 有 exchange 就用,没有就使用默认的
  • delivery_mode
    1. 调用 task.delay 的时候传的,没传就看 queue 里面有没有,有就用
    2. 没有就使用默认的
  • retry:
    1. 调用 task.delay 的时候传了就用,没传就用默认的

等这些参数确认完之后,就使用这些参数发送了!

然后这样子就将消息发出去了,等待 Worker 的接收,而 worker 的接受逻辑我们之前已经看到了,其实还是注册的 Consumer 的 on_message

附加

在前面我们说如何构建异步消息体的时候,对于消息体只是简单的用几个 ... 忽略过,但是,对于整体理解来说,我们不应该忽略他们的实质内容,所以在最后我把他们都罗列出来,前后的会用到的。而且你会发现有点意思的是,对于我们的一个异步调用,task 名和 id 都是放在 headers 里头的,而参数什么的却是放在 body 里面,在我自己实现的异步 MQ 里面,这些都是放在 body 里面的,这点我倒是不太欣赏 Celery 的。

headers

properties

body

send_event

Celery 源码解析三: Task 对象的实现的更多相关文章

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

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

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

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

  3. Mybatis源码解析(三) —— Mapper代理类的生成

    Mybatis源码解析(三) -- Mapper代理类的生成   在本系列第一篇文章已经讲述过在Mybatis-Spring项目中,是通过 MapperFactoryBean 的 getObject( ...

  4. AspNetCore3.1_Secutiry源码解析_2_Authentication_核心对象

    系列文章目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心项目 AspNetCore3. ...

  5. ReactiveCocoa源码解析(三) Signal代码的基本实现

    上篇博客我们详细的聊了ReactiveSwift源码中的Bag容器,详情请参见<ReactiveSwift源码解析之Bag容器>.本篇博客我们就来聊一下信号量,也就是Signal的的几种状 ...

  6. ReactiveSwift源码解析(三) Signal代码的基本实现

    上篇博客我们详细的聊了ReactiveSwift源码中的Bag容器,详情请参见<ReactiveSwift源码解析之Bag容器>.本篇博客我们就来聊一下信号量,也就是Signal的的几种状 ...

  7. React的React.createRef()/forwardRef()源码解析(三)

    1.refs三种使用用法 1.字符串 1.1 dom节点上使用 获取真实的dom节点 //使用步骤: 1. <input ref="stringRef" /> 2. t ...

  8. Celery 源码解析四: 定时任务的实现

    在系列中的第二篇我们已经看过了 Celery 中的执行引擎是如何执行任务的,并且在第三篇中也介绍了任务的对象,但是,目前我们看到的都是被动的任务执行,也就是说目前执行的任务都是第三方调用发送过来的.可 ...

  9. Spring源码解析三:IOC容器的依赖注入

    一般情况下,依赖注入的过程是发生在用户第一次向容器索要Bean是触发的,而触发依赖注入的地方就是BeanFactory的getBean方法. 这里以DefaultListableBeanFactory ...

随机推荐

  1. 如何创建一个Django项目

    Django 软件框架 软件框架是由其中的各个模块组成,每个模块负责特定的功能,模块与模块之间相互协作来完成软件开发. MVC简介 MVC框架的核心思想是:解耦,让不同的代码块之间降低耦合,增强代码的 ...

  2. Python GUI - Tkinter tkMessageBox

    Python GUI - Tkinter tkMessageBox: tkMessageBox模块用于显示在您的应用程序的消息框.此模块提供了一个功能,您可以用它来显示适当的消息     tkMess ...

  3. Python系列之反射、面向对象

    一.反射 说反射之前先介绍一下__import__方法,这个和import导入模块的另一种方式 1. import commons 2. __import__('commons') 如果是多层导入: ...

  4. Weave Scope 多主机监控 - 每天5分钟玩转 Docker 容器技术(81)

    除了监控容器,Weave Scope 还可以监控 Docker Host. 点击顶部 HOSTS 菜单项,地图将显示当前 host. 与容器类似,点击该 host 图标将显示详细信息. host 当前 ...

  5. This application failed to start because it could not find or load the Qt platform plugin "windows" 的问题原因以及解决方案

    1. 问题原因非常简单,经过各种百度,都没有找到解决方案,在此做一个记录备用. 2.原因就在于,项目目录使用了中文路径,然后出现了这个问题. 3.我是在使用 syncfusion 下的HTML 转PD ...

  6. ASP.NET没有魔法——ASP.NET MVC IoC

    之前的文章介绍了MVC如何通过ControllerFactory及ControllerActivator创建Controller,而Controller又是如何通过ControllerBase这个模板 ...

  7. uva11584

    将课本上所述方法实现即可,代码如下: /* * Author: Bingo * Created Time: 2015/1/25 23:49:49 * File Name: uva11584.cpp * ...

  8. JAVA提高七:类加载器

    今天我们学习类加载器,关于类加载器其实和JVM有很大关系,在这里这篇文章只是简单的介绍下类加载器,后面学习到JVM的时候还会详细讲到类加载器,本文分为下面几个小节讲解: 一.认识类加载器 1.什么是类 ...

  9. Python3爬虫登录模拟

    使用Python爬虫登录系统之后,能够实现的操作就多了很多,下面大致介绍下如何使用Python模拟登录. 我们都知道,在前端的加密验证,只要把将加密环境还原出来,便能够很轻易地登录. 首先分析登录的步 ...

  10. [extjs(1)]MyEclipse2014安装ext4插件Spket

    1 解压好的Spket目录如下 2  建议以link方式安装Spket到MyEclipse中 找到MyEclipse的安装目录 如 3  在MyEclipse 的根目录新建一个目录extjs 当然也可 ...