Celery fanout signals with `send_task`

Viewed 128

I'm trying to establish communication between different processes running celery. I successfully sent tasks from one process to others using app.send_task on a celery instance. I am struggling now to broadcast tasks through a fanout rabbitmq exchange to all other instances (basically a publish-subscribe pattern for celery).

It must be related to the routing across exchanges and queues but I simply can't make it work.

This is the master emitter which broadcasts a task named signal through the default exchange of type fanout:

from celery import Celery
from kombu import Exchange, Queue

app = Celery('emitter',
             broker='pyamqp://test@localhost//',
             backend='db+sqlite:///results.db')


default_queue_name = 'default'
default_exchange_name = 'default'
default_routing_key = 'default'

default_exchange = Exchange(default_exchange_name, type='fanout')
default_queue = Queue(
    default_queue_name,
    default_exchange,
    routing_key=default_routing_key)

app.conf.task_queues = (
    default_queue,
)

app.conf.task_default_queue = default_queue_name
app.conf.task_default_exchange = default_exchange_name
app.conf.task_default_routing_key = default_routing_key


if __name__ == '__main__':
    app.send_task(name='signal', exchange='default')

To my understanding the routing, queue and exchange setup on the other app needs to be identical. Thus, this is a very similarly looking piece of code but defining a task that gets called:

from celery import Celery
from kombu import Exchange, Queue


app = Celery('CLIENT_A',
             broker='pyamqp://test@localhost//',
             backend='db+sqlite:///results.db')


default_queue_name = 'default'
default_exchange_name = 'default'
default_routing_key = 'default'

default_exchange = Exchange(default_exchange_name, type='fanout')
default_queue = Queue(
    default_queue_name,
    default_exchange,
    routing_key=default_routing_key)

app.conf.task_queues = (
    default_queue,
)

app.conf.task_default_queue = default_queue_name
app.conf.task_default_exchange = default_exchange_name
app.conf.task_default_routing_key = default_routing_key


@app.task(name='signal')
def signal():
    print('client_a signal')
    return 'signal'

The second client will look exactly the same as the first except for the name and the print message:

# [...]

app = Celery('CLIENT_B', ...

# [...] identical to the part above

@app.task(name='signal')
def signal():
    print('client_b signal')

I'm starting both client workers with different node names (otherwise celery will complain):

celery -A client_a worker -n node_a
celery -A client_b worker -n node_b

If I then call the emitter (first piece of code) I see the signals being triggered alternately by client_a and client_b but never both as I would like them to be triggered.

The rabbitmq management platform looks as expected with the default exchange defined as fanout and the routing looks alright.

I'm not sure if I'm on the completely wrong track here but that's what I imagined should be possible with correct routing.

0 Answers
Related