python RabbitMQ

Version 1

Copy# rabbit_util.py

import json

from pika.adapters.blocking_connection import BlockingConnection, BlockingChannel
from pika.credentials import PlainCredentials
from pika.connection import ConnectionParameters
from pika.spec import Basic, BasicProperties


class BaseConnection:
    DEFAULT_USERNAME = 'guest'
    DEFAULT_PASSWORD = 'guest'

    def __init__(self, host, port, username=DEFAULT_USERNAME, password=DEFAULT_PASSWORD, virtual_host='/'):
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.virtual_host = virtual_host
        self.credentials = PlainCredentials(self.username, self.password)
        self.conn = BlockingConnection(ConnectionParameters(
            host=self.host,
            port=self.port,
            virtual_host=self.virtual_host,
            credentials=self.credentials,
        ))
        self.channel = self.conn.channel()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, value, traceback):
        if self.channel and self.channel.is_open:
            self.channel.stop_consuming()
            self.channel.close()
        if self.conn and self.conn.is_open:
            self.conn.close()


class Consumer(BaseConnection):
    def __init__(self, host, port, username, password, virtual_host):
        super().__init__(host, port, username=username, password=password, virtual_host=virtual_host)

    def start(self, queue, exchange='', routing_key=None):
        self.channel.exchange_declare(exchange, durable=True)  # 聲明持久化交換機,不存在則創建
        self.channel.queue_declare(queue, durable=True)  # 聲明持久化隊列,不存在則創建
        self.channel.queue_bind(queue, exchange=exchange, routing_key=routing_key)  # 交換機與隊列進行綁定
        self.channel.basic_qos(prefetch_count=1)  # 每次消費數量, 1爲最佳,視業務情況
        self.channel.basic_consume(queue, self.consume_callback)  # 消費隊列與消費回調
        self.channel.start_consuming()  # 開啓消費者服務

    def consume_callback(self, channel: BlockingChannel, method: Basic.Deliver, properties: BasicProperties,
                         body: bytes):
        print(f'[CurrentConsume]: {method.consumer_tag} [Body]: {body}')
        body = json.loads(body.decode('utf-8'))
        print(body, type(body))
        channel.basic_ack(delivery_tag=method.delivery_tag)

    def run(self, queue, exchange='', routing_key=None):
        print('Consumer Service Started')
        print(' [*] Waiting for messages. To exit press CTRL+C')
        try:
            self.start(queue, exchange=exchange, routing_key=routing_key)
        except Exception as e:
            print(e)


class Producer(BaseConnection):
    def __init__(self, host, port, username, password, virtual_host):
        super().__init__(host, port, username=username, password=password, virtual_host=virtual_host)

    def publish_message(self, message, exchange='', routing_key=None):
        body = json.dumps(message).encode('utf-8')
        self.channel.basic_publish(
            exchange=exchange,
            routing_key=routing_key,
            body=body,
            properties=BasicProperties(delivery_mode=2)
        )

# consumer.py
from rabbit import Consumer


if __name__ == '__main__':
    with Consumer('192.168.79.128', 5672, username='admin', password='admin', virtual_host='my_vhost') as consumer:
        try:
            consumer.run('queue_1', exchange='exchange_1', routing_key='r_k_1')
        except KeyboardInterrupt as e:
            print(e)

# producer.py
import time

from rabbit import Producer


if __name__ == '__main__':
    with Producer('192.168.79.128', 5672, username='admin', password='admin', virtual_host='my_vhost') as producer:
        for i in range(10):
            time.sleep(2)
            producer.publish_message({'number': i}, exchange='exchange_1', routing_key='r_k_1')

Version2

# rabbit.py
import json
import logging
import traceback
from pika.adapters.blocking_connection import BlockingConnection, BlockingChannel
from pika.credentials import PlainCredentials
from pika.connection import ConnectionParameters
from pika.spec import Basic, BasicProperties
from pika.exchange_type import ExchangeType
from typing import Callable


class RabbitMQClient:
    DEFAULT_USERNAME = 'guest'
    DEFAULT_PASSWORD = 'guest'

    DEAD_EXCHANGE_NAME = "dead_exchange"
    DEAD_QUEUE_NAME = "dead_queue"
    DEAD_ROUTING_KEY = "dead_key"

    RETRY_EXCHANGE_NAME = "retry_exchange"
    RETRY_QUEUE_NAME = "retry_queue"
    RETRY_ROUTING_KEY = "retry_key"

    def __init__(self, host, port, username=DEFAULT_USERNAME, password=DEFAULT_PASSWORD, virtual_host='/'):
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.virtual_host = virtual_host
        self.credentials = PlainCredentials(self.username, self.password)
        self.connection = BlockingConnection(ConnectionParameters(
            host=self.host,
            port=self.port,
            virtual_host=self.virtual_host,
            credentials=self.credentials,
        ))
        self.channel = self.connection.channel()
        self.channel.confirm_delivery()
        logging.debug("connection established")

    def close_connection(self):
        self.connection.close()
        logging.debug("connection closed")

    def declare_exchange(self, exchange, exchange_type=ExchangeType.direct.value, is_durable=False):
        self.channel.exchange_declare(exchange=exchange,
                                      exchange_type=exchange_type,
                                      durable=is_durable)

    def declare_queue(self, queue, arguments=None, is_durable=False):
        self.channel.queue_declare(queue=queue, durable=is_durable, arguments=arguments)

    def declare_retry_queue(self):
        """
        創建異常交換器和隊列,用於存放沒有正常處理的消息。
        :return:
        """
        self.channel.exchange_declare(exchange=self.RETRY_EXCHANGE_NAME,
                                      exchange_type=ExchangeType.direct.value,
                                      durable=True)
        self.channel.queue_declare(queue=self.RETRY_QUEUE_NAME,
                                   durable=True)
        self.channel.queue_bind(self.RETRY_QUEUE_NAME, exchange=self.RETRY_EXCHANGE_NAME, routing_key=self.RETRY_ROUTING_KEY)

    def publish_common_message(self, msg, queue, exchange='', routing_key=None):
        """
        發送消息到指定的交換器
        :param queue: 隊列
        :param routing_key: RoutingKey
        :param exchange: RabbitMQ交換器
        :param msg: 消息實體
        :return:
        """
        self.declare_common_stuff(exchange=exchange, queue=queue, routing_key=routing_key)
        if not isinstance(msg, bytes):
            msg = json.dumps(msg).encode('utf-8')
        self.channel.basic_publish(exchange=exchange,
                                   routing_key=routing_key,
                                   body=msg,
                                   properties=BasicProperties(
                                       delivery_mode=2,
                                       type=exchange
                                   ))
        self.close_connection()
        print("message send out to %s" % exchange)
        # logging.debug("message send out to %s" % exchange)

    def publish_delay_message(self, msg, exchange='', queue='', routing_key=None, ttl=1 * 1000):
        """
        發佈延時消息
        :param routing_key:
        :param queue:
        :param exchange:
        :param msg: 消息實體
        :param ttl: 延時時間,毫秒
        :return:
        """
        self.declare_delay_stuff()
        # self.delete_old(exchange=exchange, queue=queue)  # 刪除舊交換機和舊隊列,不建議。可以用隨機值做爲交換機和隊列名,然後消費後刪除該交換機和隊列
        arguments = {
            'x-dead-letter-exchange': self.DEAD_EXCHANGE_NAME,
            'x-dead-letter-routing-key': self.DEAD_ROUTING_KEY,
            # 'x-message-ttl': ttl,
        }
        self.declare_publish_stuff(exchange=exchange, queue=queue, routing_key=routing_key, arguments=arguments)
        if not isinstance(msg, bytes):
            msg = json.dumps(msg).encode('utf-8')
        self.channel.basic_publish(exchange=exchange,
                                   routing_key=routing_key,
                                   body=msg,
                                   properties=BasicProperties(
                                       delivery_mode=2,
                                       expiration=str(ttl)
                                   ))
        # self.close_connection()
        print("message send out to %s" % exchange)
        # logging.debug("message send out to %s" % self.DEAD_EXCHANGE_NAME)

    def start_common_consume(self, callback: Callable, exchange='', queue='#', routing_key=None):
        """
        啓動消費者,開始消費RabbitMQ中的消息
        :return:
        """
        self.declare_common_stuff(exchange=exchange, queue=queue, routing_key=routing_key)
        self.start_consume(queue, callback)

    def delete_old(self, exchange, queue):
        self.channel.exchange_delete(exchange=exchange)
        self.channel.queue_delete(queue=queue)

    def start_delay_consume(self, callback: Callable):
        self.declare_delay_stuff()
        self.start_consume(self.DEAD_QUEUE_NAME, callback)

    def start_retry_consume(self, callback: Callable):
        self.declare_retry_queue()
        self.start_consume(self.RETRY_QUEUE_NAME, callback)

    def start_consume(self, queue, callback: Callable):
        self.channel.basic_qos(prefetch_count=1)
        try:
            self.channel.basic_consume(
                queue,  # 你要從那個隊列裏收消息
                callback,  # 如果收到消息,就調用callback函數來處理消息
            )
            self.channel.start_consuming()
        except KeyboardInterrupt:
            self.stop_consuming()

    def declare_delay_stuff(self):
        self.declare_exchange(self.DEAD_EXCHANGE_NAME, exchange_type=ExchangeType.topic.value, is_durable=True)
        self.declare_queue(self.DEAD_QUEUE_NAME, is_durable=True)
        self.channel.queue_bind(queue=self.DEAD_QUEUE_NAME, exchange=self.DEAD_EXCHANGE_NAME,
                                routing_key=self.DEAD_ROUTING_KEY)

    def declare_common_stuff(self, exchange='', queue='#', routing_key=None):
        self.declare_exchange(exchange, exchange_type=ExchangeType.topic.value, is_durable=True)
        self.declare_queue(queue, is_durable=True)
        self.channel.queue_bind(queue, exchange=exchange, routing_key=routing_key)

    def declare_publish_stuff(self, exchange='', queue='#', routing_key=None, arguments=None):
        self.declare_exchange(exchange, exchange_type=ExchangeType.topic.value, is_durable=True)
        self.declare_queue(queue, arguments=arguments, is_durable=True)
        self.channel.queue_bind(queue, exchange=exchange, routing_key=routing_key)

    def stop_consuming(self):
        self.channel.stop_consuming()
        self.close_connection()

    @staticmethod
    def message_handle_successfully(channel: BlockingChannel, method: Basic.Deliver):
        """
        如果消息處理正常完成,必須調用此方法,
        否則RabbitMQ會認爲消息處理不成功,重新將消息放回待執行隊列中
        :param channel: 回調函數的channel參數
        :param method: 回調函數的method參數
        :return:
        """
        channel.basic_ack(delivery_tag=method.delivery_tag)

    @staticmethod
    def message_handle_failed(channel: BlockingChannel, method: Basic.Deliver):
        """
        如果消息處理失敗,應該調用此方法,會自動將消息放入異常隊列
        :param channel: 回調函數的channel參數
        :param method: 回調函數的method參數
        :return:
        """
        channel.basic_reject(delivery_tag=method.delivery_tag, requeue=False)


def consume_callback(channel: BlockingChannel, method: Basic.Deliver, properties: BasicProperties, body: bytes):
    print(f'[CurrentConsume]: {method.consumer_tag} [Body]: {body}')
    try:
        body = json.loads(body.decode('utf-8'))
        print(body, type(body))
        # 如果處理成功,則調用此消息回覆ack,表示消息成功處理完成。
        RabbitMQClient.message_handle_successfully(channel, method)
    except Exception as e:
        traceback.print_exc()
        RabbitMQClient.message_handle_failed(channel, method)

        
# consumer.py
from rabbit import RabbitMQClient, consume_callback


def main():
    print("start program")
    client = RabbitMQClient('192.168.79.128', 5672, username='admin', password='admin', virtual_host='my_vhost')
    # client.start_common_consume(consume_callback, exchange='test1', queue='test1', routing_key='test1')
    client.start_delay_consume(consume_callback)


if __name__ == '__main__':
    main()


    
# producer.py
from uuid import uuid4
from rabbit import RabbitMQClient

if __name__ == '__main__':
    print("start publish")
    client = RabbitMQClient('192.168.79.128', 5672, username='admin', password='admin', virtual_host='my_vhost')
    msg1 = {"key": "value"}
    client.publish_delay_message(msg1, exchange='ttl_exchange', queue='ttl_exchange', routing_key='ttl_test', ttl=3 * 1000)
    # client.publish_common_message('test1', 'test1', 'test1', 'test1')
    print("message send out")
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章