Celery-Canvas: Designing Work-flows

Signature

在调用指南中学习了如何使用任务延迟方法来调用任务,着通常是用户所需要操作的结果。但是,有时可能希望将任务调用的签名传递给另一个进程或作为另一个函数的参数。

signature()包装单个任务调用的参数,关键字参数和执行选项,以便可以将其传递给函数,甚至进行序列化并通过网络发送。

这个部分怎么做到的,好奇怪。

  • 你可以为一个add任务创建一个签名,如下所示:
from celery import signature
from proj.tasks import add

if __name__ == '__main__':
    res = signature('tasks.add', args=(2, 2), kwargs={'debug': True}, countdown=10)

    print(res.args)
    print(res.kwargs)
    print(res.options)

执行结果

$ python client_16.py
(2, 2)
{'debug': True}
{'countdown': 10}

它支持延迟,apply_async等的“调用API”,包括直接调用(call)

调用签名将在当前进程中内联执行任务:

from celery import signature
from proj.tasks import add


if __name__ == '__main__':
    res1 = add(2, 2)
    res2 = add.s(2, 2)()

    print(res1)
    print(res2)

执行结果:

$ python client_canvas.py
4
4

delay方法是apply_async使用的捷径方式,采用star-arguments方式:

from celery import signature
from proj.tasks import add

if __name__ == '__main__':
    result = add.delay(2, 2)
    print(result.get(timeout = 1))

执行结果

$ python client_canvas.py
4

apply_async采用与app.Task.apply_async()方法相同的参数:

from celery import signature
from proj.tasks import add

if __name__ == '__main__':
    #add.apply_async(args, kwargs, **options)
    #add.signature(args, kwargs, **options).apply_async()

    res1 = add.apply_async((2, 2), countdown = 1)
    res2 = add.signature((2, 2), countdown = 1).apply_async()

    print(res1.get())
    print(res2.get())

执行结果:

$ python client_canvas.py
4
4

无法使用s()定义调用选项,但是链集调用可以解决此类问题:

这个地方是什么意思呢?

cat client_canvas.py
from celery import signature
from proj.tasks import add

if __name__ == '__main__':
    res = add.s(2, 2).set(countdown = 1)
    print(res)

执行结果

$ python client_canvas.py
proj.tasks.add(2, 2)

Partials

使用signature,可以在worker中执行任务:

from proj.tasks import add

if __name__ == '__main__':
    res1 = add.s(2, 2).delay()
    res2 = add.s(2, 2).apply_async(countdown = 1)

    print(res1.get())
    print(res2.get())

执行结果

$ python client_partials.py
4
4

或者可以直接在当前的进程中直接调用它:

from proj.tasks import add

if __name__ == '__main__':
    res3 = add.s(2, 2)()
    print(res3)

这里的直接调用,应当指的是没有进入到异步处理的过程,相当于applying.

执行结果

$ python client_partials.py
4

指定其他args, kwarg或者apply_async/delay选项可以创建局部变量:

  • 添加的所有参数将在签名中的args之前添加:
 from proj.tasks import add

if __name__ == '__main__':
    partial = add.s(2)
    res1 = partial.delay(4)
    res2 = partial.apply_async((4, ))

    print(res1.get())
    print(res2.get())

执行结果:

$ python client_partials.py
6
6

添加的所有关键字参数将与签名中的kwargs合并,新关键字参数优先:

>>> s = add.s(2, 2)
>>> s.delay(debug=True)                    # -> add(2, 2, debug=True)
>>> s.apply_async(kwargs={'debug': True})  # same

这里面的debug没有办法直接导入。

添加的所有选项将与签名中的选项合并,新选项优先:

from proj.tasks import add

if __name__ == '__main__':
    s = add.signature((2, 2), countdown = 10)
    res = s.apply_async(countdown = 1)

    print(res.get())

执行结果

$ python client_partials.py
4

还可以克隆签名来创建detivatives:

from proj.tasks import add

if __name__ == '__main__':
    s = add.s(2, 2)
    res = s.clone(args = (4, ), kwargs = {'debug': True})
    print(res)

执行结果:

$  python client_partials.py
proj.tasks.add(4, 2, 2, debug=True)

Immutability

partials 意味可以使用回调,任意task linked或者chord的回调都将与父任务一起应用。若想要指定一个不带任何参数的回调,可以在签名中设置为不可变。

>>> add.apply_async((2, 2), link=reset_buffers.signature(immutable=True))

这里reset_buffers包没有导入进来:(
.si()快捷方式还可以用于创建不可变的签名。

>>> add.apply_async((2, 2), link=reset_buffers.si())

签名不变时,只能设置执行选项,因此无法使用部分args/kwargs调用签名。

Note
在本教程中,有时使用前缀运算符~来签名。可能不应该在生产代码中使用它,但是在使用python shell进行实验时,是一个快捷方式。
~sig
is the same as
sig.delay().get()

CallBacks

可以使用apply_async的link参数将回调添加到任何任务中。

add.apply_async((2, 2), link=other_task.s())

仅当任务成功退出时才应用回调, 并且回调将以父任务的返回值作为参数来应用。

如前前述,添加到签名的任何参数都将放在签名本身指定的参数之前。

如果你拥有一个签名

sig = add.s(10)

然后sig.delay(result)将等同于:

add.apply_async(args = (result, 10))

现在,使用部分参数通过回调调用添加任务:

add.apply_async((2, 2), link=add.s(8))

这里的执行结果是4, 并没期待的出现12。具体的:

from proj.tasks import add

if __name__ == '__main__':
    res = add.apply_async((2, 2), link=add.s(10))
    print(res.get(timeout = 1))

执行结果:

$ python client_callback.py
4

The Primitives

Overview

  1. group
    group实际是一个签名,其中包含了并行应用的任务列表。

  2. chain
    chain可以将签名链接在一起,以便一个被另一个调用,本质上形成一个回调链。

  3. chord
    chord类似于一个组,但是带有回调。一个chord由一个header group和一个body组成,body是一个任务并且在header中所有任务完成后调用。

  4. map
    map类似于内置的映射功能。但是会创建一个临时任务,在该任务中将参数列表应用于该任务。例如,task.map([1, 2])-等价于

res = [task(1), task(2)]
  1. starmap
    除了将参数应用为*args之外,其工作原理与map完全相同。例如,add.starmap([add(2, 2), add(4, 4)])

  2. chunks
    块将一长串参数分成若干部分,例如操作

items = zip(range(1000), range(1000))		# 1000 items
add.chunks(items, 10)

将项目列表分成10个大块,从而产生100个任务(每个任务依次处理10个项目)

这些原语本身也是签名对象,因此它们可以以多种方式组合成复杂的工作流程。

Examples

simple chain

这是一个简单的链,执行第一个任务并将其返回值传递给链中的下一个任务,依次类推。

from celery import chain
from proj.tasks import add
if __name__ == '__main__':
     res = chain(add.s(2, 2), add.s(4), add.s(8))()
     print(res.get())

执行结果

$ python client_chain.py
16

也可以使用管道来编写:

from celery import chain
from proj.tasks import add

if __name__ == '__main__':
    res = (add.s(2, 2) | add.s(8) | add.s(8))()
    print(res.get())

执行结果:

$ python client_chain.py
20

Immutable signatures

签名可以是部分签名,因此可以将参数添加到现有的参数中。但是,有时或许并不需要链中上一条任务的结果。

在这种情况下,可以将签名标记为不可变,这样就不能更改参数:

from celery import signature
from proj.tasks import add
from proj import tasks

if __name__ == '__main__':
    res = add.signature((2, 2), immutable = True)()

    print(res)

执行结果

$ python client_16.py
4

还有一个.si()的快捷方式,这是创建签名的首选方式:

add.si(2, 2)

现在可以创建一系列的独立任务:

from celery import signature
from proj.tasks import add
from proj import tasks

if __name__ == '__main__':
    res = (add.si(2, 2) | add.si(4, 4) | add.si(8, 8))()
    print(res.get())
    print(res.parent.get())
    print(res.parent.parent.get())

执行结果:

$ python client_16.py
16
8
4

Simple group

group的并行执行,不理解。group内的任务,仍旧是按照先后顺序依次执行的,难道这个并行是指打包一组任务顺序执行?!! 不理解

可以轻松创建一组任务用来并行执行:

from celery import group
from proj.tasks import add

if __name__ == '__main__':
    res = group(add.s(i, i) for i in range(10))()

    print(res.get(timeout = 1))

执行结果:

$ python client_group.py
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Simple chord

chord使得能够添加当组任务中的所有任务完成后调用一个回调。通常可以这样做:

from celery import chord
from proj.tasks import add, xsum
if __name__ == '__main__':
	# 传递给xsum的参数是一个元组序列: [0, 4, 2, 8, 12, 16, 6, 10, 14, 18]
    res = chord((add.s(i, i) for i in range(10)), xsum.s())()
    print(res.get())

执行结果

python client_chord.py
90

上面的示例创建了10个并行启动的任务, 当所有任务完成时,返回值组合到一个列表中并发送到xsum任务中。

chord的主体也可以是不变的,因此该组的返回值不会传递给回调。

from celery import chord
from proj.tasks import add, xsum

if __name__ == '__main__':
    res = chord((add.s(i, i) for i in range(10)), xsum.si([1]))()

    print(res.get())

执行结果:

$ python client_chord.py
1

注意,不可变的方法中使用了.si,这将创建一个不可变的签名。这意味着传递的任何新参数都将被忽略(包括传递给先前任务的返回值)。

Blow your mind by combining

chain也可以partial:

from proj.tasks import add, mul

if __name__ == '__main__':
    c1 = (add.s(4) | mul.s(8))
    res = c1(16)

    print(res.get())

执行结果:

$ python client_comb.py
160

这意味着也可以组合链:

from proj.tasks import add, mul

if __name__ == '__main__':
    c2 = (add.s(4, 16) | mul.s(2) | (add.s(4)) | mul.s(8))

    res2 = c2()
    print(res2.get())

执行结果:

$ python client_comb.py
352

将group与另一个任务链接在一起将自动将其升级为chrod.

from proj.tasks import add, mul, xsum
from celery import group

if __name__ == '__main__':
    c3 = (group(add.s(i, i) for i in range(10)) | xsum.s())
    res = c3()
    print(res.get())

执行结果

$ python client_comb.py
90

group和chord也接受部分参数因此在chain中, 前一个任务的返回值将装发到组中的所有任务中。

from proj.tasks import add, mul, xsum
from celery import group

if __name__ == '__main__':
    res = (add.s(1, 2) | group(add.s(i) for i in range(10)))()
    print(res.get())

执行结果

$ python client_comb.py
[3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

另一种写法:

from proj.tasks import add, mul, xsum
from celery import group

if __name__ == '__main__':
    c4 = (add.s(2) | group(add.s(i) for i in range(10)))
    res = c4.delay(5)
    print(res.get())

执行结果:

python client_comb.py
[7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

如果不想将参数转发给该组,可以使该组中的签名不变。

from proj.tasks import add, mul, xsum
from celery import group

if __name__ == '__main__':
    c4 = (add.s(2) | group(add.si(i, i) for i in range(10)))
    res = c4.delay(5)
    print(res.get())

执行结果:

python client_comb.py
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Chains

任务可以连接在一起:当任务成功返回时,将调用连接的任务:

from proj.tasks import add, mul

if __name__ == '__main__':
    res = add.apply_async((2, 2), link = mul.s(16))
    print(res.get())

执行结果:

$ python client_chains.py
4

连接的任务将以其父任务的结果作为第一个参数应用。在上述结果为4的情况下,将导致mul(4, 16)。

结果将跟踪原始任务调用的所有子任务,并且可以从结果实例进行访问。

from proj.tasks import add, mul

if __name__ == '__main__':
    res = add.apply_async((2, 2), link = mul.s(16))
    
    print(res.get())
    print(res.children)
    print(res.children[0].get())

这里不执行get()就不跑。

执行结果

python client_chains.py
4
[<AsyncResult: 95d15d3f-5d6a-4bdd-98a0-c13b2faa7cd3>]
64

结果实例还具有collect()方法,该方法将结果视为图形,从而可以使你迭代结果。

from proj.tasks import add, mul

if __name__ == '__main__':
    res = add.apply_async((2, 2), link = mul.s(16))
    
    print(list(res.collect()))

执行结果:

python client_chains.py
4
[(<AsyncResult: 702ef038-d2c8-46fa-acc7-0ad60202e74d>, 4), (<AsyncResult: b896b3f8-6ed4-4245-8ddc-86271d18dd9a>, 64)]

默认情况下,如果图形未完全形成(任务之一尚未完成),collect()将引发IncompleteStream异常,但是也可以获取得到图形的中间表示形式:

from proj.tasks import add, mul

if __name__ == '__main__':
    res = add.apply_async((2, 2), link = mul.s(16))
    print(res.get())

    for result, value in res.collect(intermediate = True):
        print('result: ' + str(result))
        print('value: ' + str(value))

执行结果

$ python client_chains.py
4
result: dc679094-5ee1-47ad-8baa-ee394ab333db
value: 4
result: 4a062545-7432-4af2-802b-7a89f7ae06f0
value: 64

可以将任意多个任务链接到一起,并且签名也可以连接:

from proj.tasks import add, mul

if __name__ == '__main__':
    s = add.s(2, 2)
    s.link(mul.s(4))
    print(s.delay().get())

执行结果

python client_chains.py
4

还可以使用on_error方法添加错误回调:

from proj.tasks import add, mul, xsum, log_error
from celery import group

if __name__ == '__main__':
    res = add.s(2, 3).on_error(log_error.s()).delay()
    print(res.get())

Graphs

另外可以将result graph作为DependencyGraph使用

Groups

一个组可以并行执行多个任务

组功能采用签名列表。

from celery import group
from proj.tasks import add

if __name__ == "__main__":
    res =   group(add.s(2, 2), add.s(4, 4))()
    print(res.get())

执行结果:

python client_group.py
[4, 8]

如果调用该组,则任务将在当前进程中一个接一个地应用。并返回一个GroupResult实例,该实例可以用于跟踪结果或者告知已经准备好多少个任务,以此类推:

from celery import group
from proj.tasks import add

if __name__ == "__main__":
    res = group(add.s(i, i) for i in range(100))()
    print(res.get())

执行结果:

$ python client_group.py
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198]

group是签名对象,因此可以与其他签名结合使用。

Group Results

group任务也返回一个特殊的结果,此结果与普通任务结果一样工作,只是它在整个组中都起作用:

from celery import group
from proj.tasks import add

if __name__ == '__main__':
    job = group([add.s(2, 2), add.s(4, 4), add.s(8, 8), add.s(16, 16), add.s(32, 32)])

    res = job.apply_async()
    print(res.ready())
    print(res.successful())
    print(res.get())

执行结果:

$ python group_results.py
False
False
[4, 8, 16, 32, 64]

GroupResult获取AsyncResult实例的列表并对其进行操作,就好像这是一个单独的任务一样。

它支持一下操作:

  1. successful()
    如果所有子任务成功完成(例如,未引发异常),则返回True
  2. failed()
    如果任何子任务失败,则返回True
  3. waiting()
    如果尚未完成任何子任务,则返回True。
  4. ready()
    如果尚未完成任何子任务,则返回True
  5. completed_count()
    返回完成的子任务数
  6. revoke()
    撤销所有子任务
  7. join()
    收集所有子任务的结果,并以与调用它们相同的顺序返回它们(作为列表)

Chords

Note
使用chord的任务不可忽略返回结果。如果对chord中的任何task设置不可用禁用结果后端。则应当阅读“Important Notes”Chord当前不支持rpc存储结果后端。

chord是仅在group中所有的任务完成执行后才执行的任务。

计算表达式1 + 1 + 2 + 2 + 3 + 3 … + n + n的总和,直到一百位数。

首先,需要使用两个任务, add()和tsum()(sum()是已经完成的标准化任务):

from __future__ import absolute_import, unicode_literals, print_function
from .config import app

import os

@app.task
def add(x, y):
    return x + y

@app.task
def mul(x, y):
    return x * y

@app.task
def xsum(numbers):
    return sum(numbers)

@app.task
def tsum(numbers):
    return sum(numbers)

@app.task
def log_error(request, exc, traceback):
    with open(os.path.join('/var/errors', request.id), 'a') as fh:
        print('--\n\n{0} {1} {2}'.format(task_id, exc, traceback), file = fh)

现在可以使用chord来并行计算每个加法的步骤,然后获得结果数字的总和:

from celery import chord
from proj.tasks import add, xsum

if __name__ == '__main__':
    res = chord(add.s(i, i) for i in range(100))(xsum.s())
    print(res.get())

执行结果:

$ python chord.py
9900

???官方文档在说什么,给出测试代码,实验结果并不慢反而更快。
这显然是一个非常人为的示例,消息传递和同步的开销使其比Python的慢得多。

from celery import chord
from proj.tasks import add, xsum

import time

if __name__ == '__main__':
    start_1 = time.time()

    res = chord(add.s(i, i) for i in range(100))(xsum.s())
    print(res.get())

    end_1 = time.time()
    print(end_1 - start_1)

    start_2 = time.time()

    res = sum(i + i for i in range(100))
    print(res)

    end_2 = time.time()
    print(end_2 - start_2)

执行结果:

9900
0.749430894852								# celery chord跑的
9900
1.59740447998e-05							# for循环直接跑的

chord同步步骤的成本比较高,因此应当尽量避免使用chord。尽管如此,chord仍然是工具箱中强大的一个特性,因为chord是许多并行算法中所必须的步骤。

现在分解一下chord表达式:

from celery import chord
from proj.tasks import add, xsum

import time

if __name__ == '__main__':
    res = chord(add.s(i, i) for i in range(100))(xsum.s())
    print(res.get())

    callback = xsum.s()
    header = [add.s(i, i) for i in range(100)]
    result = chord(header)(callback)
    print(result.get())

请记住,只有在header中的所有任务都返回之后才能执行回调。header中的每个任务步骤都作为任务并行执行,可能在不同的节点上执行。然后,在header中将回调与每个任务的返回值一起应用。chord()返回的任务ID是回调的ID,因此可以等待它完成,并获取最终的返回值(但请记住,切勿让任何任务等待其他任务)

???这里切勿让任何任务等待其他任务,不理解。

Error handing

那么,如果其中一项任务引发异常怎么办?

chord回调结果将转换为失败状态,并且错误设置为ChordError异常。

>>> c = chord([add.s(4, 4), raising_task.s(), add.s(8, 8)])
>>> result = c()
>>> result.get()
Traceback (most recent call last):
  File "", line 1, in <module>
  File "*/celery/result.py", line 120, in get
    interval=interval)
  File "*/celery/backends/amqp.py", line 150, in wait_for
    raise meta['result']
celery.exceptions.ChordError: Dependency 97de6f3f-ea67-4517-a21c-d867c61fcb47
    raised ValueError('something something',)

虽然回溯可能会有所不同,具体取决于所使用的结果后端,但是可以看到错误描述包括失败的任务的ID和原始异常的字符串表示形式。还可以在result.traceback中找到原始的回溯。

需要注意的是,其余任务仍将执行,因此即使中间任务失败,第三个任务(add.s(8,8))仍将执行,此外ChordError仅显示首先失败的任务:并不遵循标题中的顺序。

要在chord失败时执行操作,因此可以将errback附加到chord回调中:

from celery import chord, group
from proj.tasks import add, xsum, on_chord_error

if __name__ == '__main__':
    #res = chord([add.s(4, 4), add.s(1, 3), add.s(8, 8)])()
    #print(res.get())

    res = (group(add.s(i, i) for i in range(10)) | xsum.s().on_error(on_chord_error.s())).delay()
    print(res.get())

执行结果

$ python error_handing.py
90

Important Notes

chord中使用的任务一定不能忽略其结果。实际上,这意味着必须使用result_backend才能使用chord,此外,如果在您的配置中将task_ignore_result设置为True,则需要确保在chord内使用的各个任务都定义为ignore_result = False。这适用于Task子类和修饰的任务。

示例任务子类:

class MyTask(Task):
	ignore_result = False

装饰任务示例:

@app.task(ignore_result=False)
def another_task(project):
    do_something()

默认情况下,通过让一个周期性任务每秒轮询一次组的完成,并在准备就绪时调用签名来实现同步步骤。
实例实现:

from celery import maybe_signature

@app.task(bind=True)
def unlock_chord(self, group, callback, interval=1, max_retries=None):
    if group.ready():
        return maybe_signature(callback).delay(group.join())
    raise self.retry(countdown=interval, max_retries=max_retries)

除Redis和Memcached之外,所有结果后端都使用此方法。它们在标头中的每个任务之后增加一个计数器,然后在计数器超过集合中的任务数时应用回调。

Redis和Memcached方法是更好的解决方案,但在其他后端中不容易实现。

Note
在2.2版本之前,chord无法与Redis一起正常使用;需要至少升级到redis-server 2.2才能使用。

Note
如果在Redis结果后端使用chord,并且覆盖了task.after_return()方法。则需要确保调用super方法,否则将不应用chord回调。

def after_return(self, *args, **kwargs):
    do_something()
    super(MyTask, self).after_return(*args, **kwargs)

Map & Starmap

map和starmap是内置任务,它们将序列中的每个元素调用该任务。

与group的不同在于,仅发送一条任务消息,该操作是顺序的。

from proj.tasks import add, xsum

if __name__ == '__main__':
    res = xsum.map([range(10), range(100)])()
    print(res)

执行结果

$ python client.py
[45, 4950]

等同于:

from __future__ import absolute_import, unicode_literals, print_function
from .config import app

import os

@app.task
def add(x, y):
    return x + y

@app.task
def mul(x, y):
    return x * y

@app.task
def xsum(numbers):
    return sum(numbers)

@app.task
def temp():
    return [xsum(range(10)), xsum(range(100))]

@app.task
def tsum(numbers):
    return sum(numbers)

@app.task
def log_error(request, exc, traceback):
    with open(os.path.join('/var/errors', request.id), 'a') as fh:
        print('--\n\n{0} {1} {2}'.format(task_id, exc, traceback), file = fh)

@app.task
def on_chord_error(request, exc, traceback):
    print('Task {0!r} raised error: {1!r}'.format(request.id, exc))

client端代码:

from proj.tasks import add, xsum, temp

if __name__ == '__main__':
    res = temp.delay()
    print(res.get())

执行结果

$ python client.py
[45, 4950]

利用starmap:

from proj.tasks import add, xsum, temp

if __name__ == '__main__':
    res = add.starmap(zip(range(10), range(10)))()
    print(res)

执行结果

$ python client.py
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

等同于:

from __future__ import absolute_import, unicode_literals, print_function
from .config import app

import os

@app.task
def add(x, y):
    return x + y

@app.task
def mul(x, y):
    return x * y

@app.task
def xsum(numbers):
    return sum(numbers)

@app.task
def temp():
    return [xsum(range(10)), xsum(range(100))]

@app.task
def temp1():
    return [add(i, i) for i in range(10)]

@app.task
def tsum(numbers):
    return sum(numbers)

@app.task
def log_error(request, exc, traceback):
    with open(os.path.join('/var/errors', request.id), 'a') as fh:
        print('--\n\n{0} {1} {2}'.format(task_id, exc, traceback), file = fh)

@app.task
def on_chord_error(request, exc, traceback):
    print('Task {0!r} raised error: {1!r}'.format(request.id, exc))

客户端请求:

from proj.tasks import add, xsum, temp, temp1

if __name__ == '__main__':
    res = temp1.delay()
    print(res.get())

执行结果:

$ python client.py
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

map和starmap都是签名对象,因此它们可以用作其他签名,例如,在10秒后调用starmap:

>>> add.starmap(zip(range(10), range(10))).apply_async(countdown=10)

Chunks

Chunks可以将重复的工作分成多个部分,这样,如果有100万个对象,则可以创建10个任务,每个任务有10万个对象。

某些人可能会担心将任务分块将导致并行度降低,但是对于繁忙的集群而言,情况很少如此,实际上,这是因为避免了消息传递的开销,因此可能会大大提高性能。

要创建块签名,可以使用app.Task.chunks():

from proj.tasks import add, xsum, temp, temp1

if __name__ == '__main__':
    res = add.chunks(zip(range(100), range(100)), 10)()
    print(res.get())

执行结果:

$ python client.py
[[0, 2, 4, 6, 8, 10, 12, 14, 16, 18], [20, 22, 24, 26, 28, 30, 32, 34, 36, 38], [40, 42, 44, 46, 48, 50, 52, 54, 56, 58], [60, 62, 64, 66, 68, 70, 72, 74, 76, 78], [80, 82, 84, 86, 88, 90, 92, 94, 96, 98], [100, 102, 104, 106, 108, 110, 112, 114, 116, 118], [120, 122, 124, 126, 128, 130, 132, 134, 136, 138], [140, 142, 144, 146, 148, 150, 152, 154, 156, 158], [160, 162, 164, 166, 168, 170, 172, 174, 176, 178], [180, 182, 184, 186, 188, 190, 192, 194, 196, 198]]

在调用.apply_async时,将创建一个专用任务,以便将各个任务应用在工作程序中:

from proj.tasks import add, xsum, temp, temp1

if __name__ == '__main__':
    res = add.chunks(zip(range(100), range(100)), 10).apply_async()
    print(res.get())

执行结果:

$ python client.py
[[0, 2, 4, 6, 8, 10, 12, 14, 16, 18], [20, 22, 24, 26, 28, 30, 32, 34, 36, 38], [40, 42, 44, 46, 48, 50, 52, 54, 56, 58], [60, 62, 64, 66, 68, 70, 72, 74, 76, 78], [80, 82, 84, 86, 88, 90, 92, 94, 96, 98], [100, 102, 104, 106, 108, 110, 112, 114, 116, 118], [120, 122, 124, 126, 128, 130, 132, 134, 136, 138], [140, 142, 144, 146, 148, 150, 152, 154, 156, 158], [160, 162, 164, 166, 168, 170, 172, 174, 176, 178], [180, 182, 184, 186, 188, 190, 192, 194, 196, 198]]

同样可以将块转换为组:

from proj.tasks import add, xsum, temp, temp1
from celery import group, chunks

if __name__ == '__main__':
    res = add.chunks(zip(range(100), range(100)), 10).group()()
    print(res.get())

执行结果:

$ python client.py
[[0, 2, 4, 6, 8, 10, 12, 14, 16, 18], [20, 22, 24, 26, 28, 30, 32, 34, 36, 38], [40, 42, 44, 46, 48, 50, 52, 54, 56, 58], [60, 62, 64, 66, 68, 70, 72, 74, 76, 78], [80, 82, 84, 86, 88, 90, 92, 94, 96, 98], [100, 102, 104, 106, 108, 110, 112, 114, 116, 118], [120, 122, 124, 126, 128, 130, 132, 134, 136, 138], [140, 142, 144, 146, 148, 150, 152, 154, 156, 158], [160, 162, 164, 166, 168, 170, 172, 174, 176, 178], [180, 182, 184, 186, 188, 190, 192, 194, 196, 198]]

并且随着组的倾斜,每个任务的倒数以1为增量:

from proj.tasks import add, xsum, temp, temp1
from celery import group, chunks

if __name__ == '__main__':
    group = add.chunks(zip(range(100), range(100)), 10).group()
    res = group.skew(start = 1, stop = 10)()
    print(res.get())

没有尝试出解决办法,执行结果为空??什么鬼??

$ python client.py
[]

这意味着第一个任务的倒计时为1秒,第二个任务的倒计时为2秒,依次类推。

你可能感兴趣的:(Celery)