• python实现RabbitMQ同步跟异步消费模型


    1,消息推送类

     1 import pika
     2 
     3 
     4 # 同步消息推送类
     5 class RabbitPublisher(object):
     6 
     7     # 传入RabbitMQ的ip,用户名,密码,实例化一个管道
     8     def __init__(self, host, user, password):
     9         self.host = host
    10         self.user = user
    11         self.password = password
    12         self.connection = pika.BlockingConnection(pika.ConnectionParameters(host=self.host, credentials=pika.PlainCredentials(self.user, self.password)))
    13         self.channel = self.connection.channel()
    14 
    15     # 发送消息在队列中
    16     def send(self, queue_name, body):
    17         self.channel.queue_declare(queue=queue_name, durable=True)  # 声明一个持久化队列
    18         self.channel.basic_publish(exchange='',
    19                                    routing_key=queue_name,  # 队列名字
    20                                    body=body,  # 消息内容
    21                                    properties=pika.BasicProperties(
    22                                        delivery_mode=2,  # 消息持久化
    23                                    ))
    24 
    25     # 清除指定队列的所有的消息
    26     def purge(self, queue_name):
    27         self.channel.queue_purge(queue_name)
    28 
    29     # 删除指定队列
    30     def delete(self, queue_name, if_unused=False, if_empty=False):
    31         self.channel.queue_delete(queue_name, if_unused=if_unused, if_empty=if_empty)
    32 
    33     # 断开连接
    34     def stop(self):
    35         self.connection.close()
    View Code

    2.消息消费类

    (1)同步消息消费

     在同步消息消费的时候可能会出现pika库断开的情况,原因是因为pika客户端没有及时发送心跳,连接就被server端断开了。解决方案就是做一个心跳线程来维护连接。

    心跳线程类

     1 class Heartbeat(threading.Thread):
     2 
     3     def __init__(self, connection):
     4         super(Heartbeat, self).__init__()
     5         self.lock = threading.Lock()  # 线程锁
     6         self.connection = connection  # rabbit连接
     7         self.quitflag = False  # 退出标志
     8         self.stopflag = True  # 暂停标志
     9         self.setDaemon(True)  # 设置为守护线程,当消息处理完,自动清除
    10 
    11     # 间隔10s发送心跳
    12     def run(self):
    13         while not self.quitflag:
    14             time.sleep(10)  # 睡10s发一次心跳
    15             self.lock.acquire()  # 加线程锁
    16             if self.stopflag:
    17                 self.lock.release()
    18                 continue
    19             try:
    20                 self.connection.process_data_events()  # 一直等待服务段发来的消息
    21             except Exception as e:
    22                 print "Error format: %s" % (str(e))
    23                 self.lock.release()
    24                 return
    25             self.lock.release()
    26 
    27     # 开启心跳保护
    28     def startheartbeat(self):
    29         self.lock.acquire()
    30         if self.quitflag:
    31             self.lock.release()
    32             return
    33         self.stopflag = False
    34         self.lock.release()
    View Code

    消息消费类

     1 # 同步消息消费类
     2 class RabbitConsumer(object):
     3 
     4     # 传入RabbitMQ的ip,用户名,密码,实例化一个管道
     5     def __init__(self, host, user, password):
     6         self.host = host
     7         self.user = user
     8         self.password = password
     9         self.connection = pika.BlockingConnection(pika.ConnectionParameters(host=self.host, credentials=pika.PlainCredentials(self.user, self.password)))
    10         self.channel = self.connection.channel()
    11 
    12     # 进行消费
    13     def receive(self, queue_name, callback_worker, prefetch_count=1):  # callback_worker为消费的回调函数
    14         self.channel.queue_declare(queue=queue_name, durable=True)
    15         self.channel.basic_qos(prefetch_count=prefetch_count)  # 设置预取的数量,如果为0则不预取,消费者处理越快,可以将这个这设置的越高
    16         self.channel.basic_consume(callback_worker, queue=queue_name)  # callback_worker为消费的回调函数
    17         heartbeat = Heartbeat(self.connection)  # 实例化一个心跳类
    18         heartbeat.start()  # 开启一个心跳线程,不传target的值默认运行run函数
    19         heartbeat.startheartbeat()  # 开启心跳保护
    20         self.channel.start_consuming()  # 开始消费
    View Code

    调用方法

    # 消费回调函数
    def callback(ch, method, properties, body):
        print(" [x] Received %r" % body)
        ch.basic_ack(delivery_tag=method.delivery_tag)  # 告诉生产者处理完成
    
    consumer = RabbitConsumer(host="12.12.12.12", user="test", password="123")
    consumer.receive(queue_name="queue1", callback_worker=callback)  

    (2)异步消息消费(推荐)

    pika提供了支持异步发送模式的selectconnection方法支持异步发送接收(通过回调的方式)

    在连接的时候stop_ioloop_on_close=False需要低版本的pika,比如0.13.1,安装方式 pip install pika==0.13.1

    connectioon建立时回调建立channel, channel建立时一次回调各种declare方法,declare建立时依次回调publish。

    同使用blockconnection方法相比,通过wireshark抓包来看,使用 异步的方式会对发包进行一些优化,会将几个包合并成一个大包,然后做一次ack应答从而提高效率,与之相反使用blockconnection时将会做至少两次ack,head一次content一次等

    因此再试用异步的方式时会获得一定的优化 

    异步消息消费类

      1 # 异步消息消费类
      2 class RabbitConsumerAsync(object):
      3     EXCHANGE = 'amq.direct'
      4     EXCHANGE_TYPE = 'direct'
      5 
      6     def __init__(self, host, user, password, queue_name="fish_test", callback_worker=None, prefetch_count=1):
      7         self.host = host
      8         self.user = user
      9         self.password = password
     10         self._connection = None
     11         self._channel = None
     12         self._closing = False
     13         self._consumer_tag = None
     14         self.QUEUE = queue_name
     15         self.callbackworker = callback_worker
     16         self.prefetch_count = prefetch_count
     17 
     18     def connect(self):
     19         return pika.SelectConnection(pika.ConnectionParameters(host=self.host, credentials=pika.PlainCredentials(self.user, self.password)), self.on_connection_open,
     20                                      stop_ioloop_on_close=False)
     21 
     22     def on_connection_open(self, unused_connection):
     23         self.add_on_connection_close_callback()
     24         self.open_channel()
     25 
     26     def add_on_connection_close_callback(self):
     27         self._connection.add_on_close_callback(self.on_connection_closed)
     28 
     29     def on_connection_closed(self, connection, reply_code, reply_text):
     30         self._channel = None
     31         if self._closing:
     32             self._connection.ioloop.stop()
     33         else:
     34             self._connection.add_timeout(5, self.reconnect)
     35 
     36     def reconnect(self):
     37         self._connection.ioloop.stop()
     38         if not self._closing:
     39             self._connection = self.connect()
     40             self._connection.ioloop.start()
     41 
     42     def open_channel(self):
     43         self._connection.channel(on_open_callback=self.on_channel_open)
     44 
     45     def on_channel_open(self, channel):
     46         self._channel = channel
     47         self._channel.basic_qos(prefetch_count=self.prefetch_count)
     48         self.add_on_channel_close_callback()
     49         self.setup_exchange(self.EXCHANGE)
     50 
     51     def add_on_channel_close_callback(self):
     52         self._channel.add_on_close_callback(self.on_channel_closed)
     53 
     54     def on_channel_closed(self, channel, reply_code, reply_text):
     55         print reply_text
     56         self._connection.close()
     57 
     58     def setup_exchange(self, exchange_name):
     59         self._channel.exchange_declare(self.on_exchange_declareok, exchange_name, self.EXCHANGE_TYPE, durable=True)
     60 
     61     def on_exchange_declareok(self, unused_frame):
     62         self.setup_queue()
     63 
     64     def setup_queue(self):
     65         self._channel.queue_declare(self.on_queue_declareok, self.QUEUE, durable=True)
     66 
     67     def on_queue_declareok(self, method_frame):
     68         self._channel.queue_bind(self.on_bindok, self.QUEUE, self.EXCHANGE, self.QUEUE)
     69 
     70     def on_bindok(self, unused_frame):
     71         self.start_consuming()
     72 
     73     def start_consuming(self):
     74         self.add_on_cancel_callback()
     75         self._consumer_tag = self._channel.basic_consume(self.on_message, self.QUEUE)
     76 
     77     def add_on_cancel_callback(self):
     78         self._channel.add_on_cancel_callback(self.on_consumer_cancelled)
     79 
     80     def on_consumer_cancelled(self, method_frame):
     81         if self._channel:
     82             self._channel.close()
     83 
     84     def on_message(self, unused_channel, basic_deliver, properties, body):
     85         self.callbackworker(body)
     86         self.acknowledge_message(basic_deliver.delivery_tag)
     87 
     88     def acknowledge_message(self, delivery_tag):
     89         self._channel.basic_ack(delivery_tag)
     90 
     91     def stop_consuming(self):
     92         if self._channel:
     93             self._channel.basic_cancel(self.on_cancelok, self._consumer_tag)
     94 
     95     def on_cancelok(self, unused_frame):
     96         self.close_channel()
     97 
     98     def close_channel(self):
     99         self._channel.close()
    100 
    101     def run(self):
    102         self._connection = self.connect()
    103         self._connection.ioloop.start()
    104 
    105     def stop(self):
    106         self._closing = True
    107         self.stop_consuming()
    108         self._connection.ioloop.start()
    109 
    110     def close_connection(self):
    111         self._connection.close()
    View Code

    调用方法

    # 消费回调函数
    def callback(body):
        print(" [x] Received %r" % body)
    
    consumer = RabbitConsumerAsync(host="12.12.12.12", user="test", password="123", queue_name="fish_test", callback_worker=callback, prefetch_count=2)
    consumer.run()

     (后面这两个可不加入)守护进程类(保证消费运行)

    class CDaemon(object):
        """
        a generic daemon class.
        usage: subclass the CDaemon class and override the run() method
        stderr  表示错误日志文件绝对路径, 收集启动过程中的错误日志
        verbose 表示将启动运行过程中的异常错误信息打印到终端,便于调试,建议非调试模式下关闭, 默认为1, 表示开启
        save_path 表示守护进程pid文件的绝对路径
        """
    
        def __init__(self, save_path, stdin=os.devnull, stdout=os.devnull, stderr=os.devnull, home_dir='.', umask=022, verbose=1):
            self.stdin = stdin
            self.stdout = stdout
            self.stderr = stderr
            self.pidfile = save_path  # pid文件绝对路径
            self.home_dir = home_dir
            self.verbose = verbose  # 调试开关
            self.umask = umask
            self.daemon_alive = True
    
        def daemonize(self):
            try:
                pid = os.fork()
                if pid > 0:
                    sys.exit(0)
            except OSError, e:
                sys.stderr.write('fork #1 failed: %d (%s)
    ' % (e.errno, e.strerror))
                sys.exit(1)
    
            os.chdir(self.home_dir)
            os.setsid()
            os.umask(self.umask)
    
            try:
                pid = os.fork()
                if pid > 0:
                    sys.exit(0)
            except OSError, e:
                sys.stderr.write('fork #2 failed: %d (%s)
    ' % (e.errno, e.strerror))
                sys.exit(1)
    
            sys.stdout.flush()
            sys.stderr.flush()
    
            si = file(self.stdin, 'r')
            so = file(self.stdout, 'a+')
            if self.stderr:
                se = file(self.stderr, 'a+', 0)
            else:
                se = so
    
            os.dup2(si.fileno(), sys.stdin.fileno())
            os.dup2(so.fileno(), sys.stdout.fileno())
            os.dup2(se.fileno(), sys.stderr.fileno())
    
            def sig_handler(signum, frame):
                self.daemon_alive = False
    
            signal.signal(signal.SIGTERM, sig_handler)
            signal.signal(signal.SIGINT, sig_handler)
    
            if self.verbose >= 1:
                print 'daemon process started ...'
    
            atexit.register(self.del_pid)
            pid = str(os.getpid())
            file(self.pidfile, 'w+').write('%s
    ' % pid)
    
        def get_pid(self):
            try:
                pf = file(self.pidfile, 'r')
                pid = int(pf.read().strip())
                pf.close()
            except IOError:
                pid = None
            except SystemExit:
                pid = None
            return pid
    
        def del_pid(self):
            if os.path.exists(self.pidfile):
                os.remove(self.pidfile)
    
        def start(self, *args, **kwargs):
            if self.verbose >= 1:
                print 'ready to starting ......'
            # check for a pid file to see if the daemon already runs
            pid = self.get_pid()
            if pid:
                msg = 'pid file %s already exists, is it already running?
    '
                sys.stderr.write(msg % self.pidfile)
                sys.exit(0)
            # start the daemon
            self.daemonize()
            self.run(*args, **kwargs)
    
        def stop(self):
            if self.verbose >= 1:
                print 'stopping ...'
            pid = self.get_pid()
            if not pid:
                msg = 'pid file [%s] does not exist. Not running?
    ' % self.pidfile
                sys.stderr.write(msg)
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
                return
            # try to kill the daemon process
            try:
                i = 0
                while 1:
                    os.kill(pid, signal.SIGTERM)
                    time.sleep(0.1)
                    i = i + 1
                    if i % 10 == 0:
                        os.kill(pid, signal.SIGHUP)
            except OSError, err:
                err = str(err)
                if err.find('No such process') > 0:
                    if os.path.exists(self.pidfile):
                        os.remove(self.pidfile)
                else:
                    print str(err)
                    sys.exit(1)
                if self.verbose >= 1:
                    print 'Stopped!'
    
        def restart(self, *args, **kwargs):
            self.stop()
            self.start(*args, **kwargs)
    
        def is_running(self):
            pid = self.get_pid()
            # print(pid)
            return pid and os.path.exists('/proc/%d' % pid)
    
        def run(self, *args, **kwargs):
            # NOTE: override the method in subclass
            print 'base class run()'
    View Code

    调用

    class RabbitDaemon(CDaemon):
        def __init__(self, name, save_path, stdin=os.devnull, stdout=os.devnull, stderr=os.devnull, home_dir='.', umask=022, verbose=1):
            CDaemon.__init__(self, save_path, stdin, stdout, stderr, home_dir, umask, verbose)
            self.name = name  # 派生守护进程类的名称
    
        def run(self, **kwargs):
            # 新建一个队列链接
            rab_con = RabbitConsumerAysnc(queuename="test", callbackworker=liando_sf_consumer, prefetch_count=2)
            # 开启消费者进程
            rab_con.run()
    
    
    p_name = 'test'  # 守护进程名称
    pid_fn = '/www/rabbit/test.pid'  # 守护进程pid文件的绝对路径
    err_fn = '/www/rabbit/test_err.log'  # 守护进程启动过程中的错误日志,内部出错能从这里看到
    cD = RabbitDaemon(p_name, pid_fn, stderr=err_fn, verbose=1)
    View Code

    参考链接

    https://pika.readthedocs.io/en/0.10.0/examples.html

    https://www.jianshu.com/p/a4671c59351a

    源码下载地址:https://github.com/sy159/RabbiyMQ.git

  • 相关阅读:
    scrapy+splash 爬取京东动态商品
    分布式文件系统HDFS 练习
    爬取全部的校园新闻
    字符串操作、文件操作,英文词频统计预处理
    了解大数据的特点、来源与数据呈现方式
    EF常用的添加和修改数据
    ASP.NET Core快速入门(第5章:认证与授权)
    ASP.NET Core MVC 项目在IIS中部署
    Cookiebased认证实现
    ASP.NET Core中的授权(3) — 基于自定义策略
  • 原文地址:https://www.cnblogs.com/zzqit/p/10165069.html
Copyright © 2020-2023  润新知