RabbitMQ durable queue bindings - python

I am trying to reliably send a message from a publisher to multiple consumers using RabbitMQ topic exchange.
I have configured durable queues (one per consumer) and I am sending persistent messages delivery_mode=2. I am also setting the channel in confim_delivery mode, and have added mandatory=True flag to publish.
Right now the service is pretty reliable, but messages get lost to one of the consumers if it stays down during a broker restart followed by a
message publication.
It seems that broker can recover queues and messages on restart, but it doesn't seem to keep the binding between consumers and queues. So messages only reach one of the consumers and get lost for the one that is down.
Note: Messages do reach the queue and the consumer if the broker doesn't suffer a restart during the time a consumer is down. They accumulate properly on the queue and they are delivered to the consumer when it is up again.
Edit - adding consumers code:
import pika
class Consumer(object):
def __init__(self, queue_name):
self.queue_name = queue_name
def consume(self):
credentials = pika.PlainCredentials(
username='myuser', password='mypassword')
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='myhost', credentials=credentials))
channel = connection.channel()
channel.exchange_declare(exchange='myexchange', exchange_type='topic')
channel.queue_declare(queue=self.queue_name, durable=True)
channel.queue_bind(
exchange='myexchange', queue=self.queue_name, routing_key='my.route')
channel.basic_consume(
consumer_callback=self.message_received, queue=self.queue_name)
channel.start_consuming()
def message_received(self, channel, basic_deliver, properties, body):
print(f'Message received: {body}')
channel.basic_ack(delivery_tag=basic_deliver.delivery_tag)
You can assume each consumer server does something similar to:
c = Consumer('myuniquequeue') # each consumer has a permanent queue name
c.consume()
Edit - adding publisher code:
def publish(message):
credentials = pika.PlainCredentials(
username='myuser', password='mypassword')
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='myhost', credentials=credentials))
channel = connection.channel()
channel.exchange_declare(exchange='myexchange', exchange_type='topic')
channel.confirm_delivery()
success = channel.basic_publish(
exchange='myexchange',
routing_key='my.route',
body=message,
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent
),
mandatory=True
)
if success:
print("Message sent")
else:
print("Could not send message")
# Save for sending later
It is worth saying that I am handling the error case on my own, and it is not the part I would like to improve. When my messages get lost to some of the consumers the flow goes through the success section

Use basic.ack(delivery_tag=basic_deliver.delivery_tag) in your consumer callback method. This acknowledgement tells whether the consumer has received a message and processed it or not. If it's a negative acknowledgement, the message will be requeued.
Edit #1
In order to receive messages during broker crash, the broker needs to be distributed. It is a concept called Mirrored Queues in RabbitMQ. Mirrored Queues lets your queues to be replicated across the nodes in your cluster. If one of the nodes containing the queue goes down, the other node containing the queue will act as your broker.
For complete understanding refer this Mirrored Queues

Related

How to return ACK from consumer as soon as message is received/consumed?

I am using nameko.messaging.consume for consuming messages from a queue. Here’s a sample code -
from kombu import Queue
from nameko.messaging import consume
class Service:
name = "sample_service"
QUEUE = Queue("queue_name", no_declare=True)
#consume(QUEUE)
def process_message(self, payload):
# Some long running code ...
return result
By default behaviour, ACK will be sent to rabbitMQ broker after process_message function returns a response (Here, statement return result). I want to send an ACK as soon as consumer consumes the message. How can I do that?
*In library pika, consumer acknowledges as soon as message is consumed. That will be good example what I want to replicate with nameko’s consumer.
Thanks :)

Fastapi consuming live messages

I have a virtual assistant which receives messages and send it to a event broker (e.g rabbitmq).
An event broker allows me to connect my running assistant to other services that process the data coming in from conversations.
Example:
If I have a RabbitMQ server running, as well as another application that consumes the events, then this consumer needs to implement Pika's start_consuming() method with a callback action. Here's a simple example:
import json
import pika
def _callback(self, ch, method, properties, body):
# Do something useful with your incoming message body here, e.g.
# saving it to a database
print('Received event {}'.format(json.loads(body)))
if __name__ == '__main__':
# RabbitMQ credentials with username and password
credentials = pika.PlainCredentials('username', 'password')
# Pika connection to the RabbitMQ host - typically 'rabbit' in a
# docker environment, or 'localhost' in a local environment
connection = pika.BlockingConnection(
pika.ConnectionParameters('rabbit', credentials=credentials))
# start consumption of channel
channel = connection.channel()
channel.basic_consume(_callback,
queue='events',
no_ack=True)
channel.start_consuming()
What is the correct way to use fastapi with Pika to consume these live messages and saving it to a database ?
Do I need a websocket route ?

How to Acknowledge a Google PubSub message using AckID in Python

I was going through the PubSub pull docs here
from google.cloud import pubsub_v1
# TODO project_id = "Your Google Cloud Project ID"
# TODO subscription_name = "Your Pub/Sub subscription name"
# TODO timeout = 5.0 # "How long the subscriber should listen for
# messages in seconds"
subscriber = pubsub_v1.SubscriberClient()
# The `subscription_path` method creates a fully qualified identifier
# in the form `projects/{project_id}/subscriptions/{subscription_name}`
subscription_path = subscriber.subscription_path(
project_id, subscription_name
)
def callback(message):
print("Received message: {}".format(message))
message.ack()
streaming_pull_future = subscriber.subscribe(
subscription_path, callback=callback
)
print("Listening for messages on {}..\n".format(subscription_path))
# result() in a future will block indefinitely if `timeout` is not set,
# unless an exception is encountered first.
try:
streaming_pull_future.result(timeout=timeout)
except: # noqa
streaming_pull_future.cancel()
In the above example, message is ack-ed as soon as it is received. But I want to acknowledge only when my local celery workers finish processing the message so that PubSub can redeliver the message if the worker fails. So I take the ack_id of the message, and pass it onto the worker.
params["ack_id"] = message._ack_id
start_aggregation.delay(params)
I just can't figure out how I can use the ack_id in the worker to acknowledge the message. I know that you can use a pubsub end-point to ack a message like given here. But I can't figure out how I can use a service account credentials to do the same - they do it using OAuth in that doc. Any pointers are appreciated. Thanks.
Acking messages received from the client library with a direct call to the acknowledge API would cause issues in the client. The client has flow control limits, which determine the maximum number of messages that can be outstanding (delivered, but not acked). The removal of messages from the count occurs when one calls message.ack() or message.nack(). If you were to call the acknowledge API directly, then this count would not change, resulting in messages no longer flowing once the limit is reached.
If you are trying to use celery to get more parallelism in your processing, you can probably do it directly without this intermediate step. One option is to start up instances of the subscriber client with the same subscription in different processes. The messages will be distributed among the subscribers. Alternatively, you could replace the scheduler with one that is process-based instead of thread-based, though that would be some more work.

Pika - RabbitMQ - Why is my deliver-rate greater than acknowledge rate when using prefetch-count 1 for consumer

I have a problem with the RabbitMQ implementation PIKA in Python.
I want to consume 1 message from a queue, work with it and acknowledge it when the work is done. Then the next message should be received.
I used the prefetch_count = 1 option, to tell rabbitMQ that this consumer only wants 1 message at a time and don't want a new message until this message is acknowledged.
Here is my (very simple) code:
credentials = pika.PlainCredentials("username","password")
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='1.2.3.4', credentials=credentials))
channel = connection.channel()
def consume(ch, method, properties, body):
time.sleep(5) # Here is the work, now just hold 5 seconds
ch.basic_ack(method.delivery_tag)
def init():
channel.basic_consume(
queue="raw.archive", on_message_callback=consume, auto_ack=False)
channel.basic_qos(prefetch_count=1)
channel.start_consuming()
if __name__ == "__main__":
init()
So my question is, why does rabbitmq deliver more documents (40/sec) than acknowledged (0.20/sec, correct, because of 5 seconds pause). Shouldn't these two be equal?
Furthermore the Unacked value (1650) should never be greater than 1, because it should not deliver any document, until this document gets acknowleged.
The second view shows, that the consumer has no prefetch count. But the prefetch count is set on the connection. Maybe I must set it to the consumer, but I don't know, how to set this.
What am I doing wrong?
Thanks in advance.
As confirmed by Marcel,
Issue is related to when the basic_qos is set on the channel.
It seems it should be set prior to the basic_consume.
def init():
channel.basic_qos(prefetch_count=1)
channel.basic_consume(
queue="raw.archive", on_message_callback=consume, auto_ack=False)
channel.start_consuming()

Pub/sub based on events - Python

I am trying to build a system where I can send messages to diffferent users based on their subscription to certain events. Basically I have an api which gives me live stream events. Some of the users will be subscribed to those events. My task is to send message to those users whenever such an event occurs. I am trying to design the system in Python.
Currently I have the following questions.
How yo continously poll for events from a live stream api in Python.
How to find out which users are subscribed to that particular event. (Redis or Mysql)
How to send notification to all the users of a particular event. (Pub/sub)
I am thinking of using Amazon SNS. But not quite sure about the overall architecture.
RabbitMQ is lightweight and easy to deploy on premise and in the
cloud. It supports multiple messaging protocols. RabbitMQ can be
deployed in distributed and federated configurations to meet
high-scale, high-availability requirements.
Just small example:
Producer sends messages to the "hello" queue. The consumer receives messages from that queue. This will create a Queue (hello) with a message on the RabbitMQ cluster.
#!/usr/bin/env python
import pika
RABBITMQ_USERNAME = 'ansible'
RABBITMQ_PASSWORD = 'ansible'
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='example.eu-central-1.elb.amazonaws.com',
heartbeat_interval=25,
credentials=pika.PlainCredentials(RABBITMQ_USERNAME,RABBITMQ_PASSWORD)))
channel = connection.channel()
channel.queue_declare(queue='hello')
channel.basic_publish(exchange='',
routing_key='hello',
body='Hello World!')
print(" [x] Sent 'Hello World!'")
connection.close()
Receive a message from a named queue:
#!/usr/bin/env python
import pika
RABBITMQ_USERNAME = 'ansible'
RABBITMQ_PASSWORD = 'ansible'
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='example.elb.amazonaws.com',
heartbeat_interval=25,
credentials=pika.PlainCredentials(RABBITMQ_USERNAME,RABBITMQ_PASSWORD)))
channel = connection.channel()
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
channel.basic_consume(callback,
queue='hello',
no_ack=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

Categories

Resources