1while True:2 msg = q.get()3 if msg.id not in done:4 send_email(msg)5 done.add(msg.id)6 q.task_done()q.put(msg)queue: emailsunackedreadydone: nonereadydead lettersABCDEEABCDEE
Shop (producer) and workers (consumers): they never call each other, only the queueQueue: messages wait in order; dashed spots are copies kept until ackedTokens: messages (letters) and acks (tick)Dead-letter queue and the list of finished idsMessages hop from the shop onto the queue, slide to its front and hop to a worker. While a worker has a message, the queue keeps a dashed copy under 'unacked'; the worker's ack deletes it, and a crash turns it back into a message, so a message can arrive twice. A message that fails 3 times moves to the dead-letter queue. On a topic, every subscriber gets its own copy. How the queue keeps its copy differs: RabbitMQ holds a delivered message as unacknowledged and requeues it if the consumer's connection closes; Amazon SQS hides it for a visibility timeout and shows it again if it is not deleted in time.

Producers and consumers

  1. A producer is the part of a system that creates work. This shop takes an order and an email must go out. Instead of sending it itself, it puts a message, a note describing the job, on a queue and carries on.

When things fail

Topics and brokers

What happens

;; step 1/10 · Put it on a queue
# the shop (producer)
q.put(msg)        # message A: email order 1
# returns at once: the shop
# does not wait for the email

The code card uses Python's standard queue.Queue, which works inside one program; there task_done() plays the part of the ack. With a broker over the network the calls differ (RabbitMQ's pika client calls ch.basic_ack, SQS calls DeleteMessage), but the loop has the same shape. The list of finished ids must live in shared storage such as a database, not in one worker's memory. A crash between sending and noting can still send twice, so careful systems write the id in the same transaction as the work, or pass it to the email service as an idempotency key.