python实现RabbitMQ同步跟异步消费模型
1,消息推送类
import pika # 同步消息推送类
class RabbitPublisher(object): # 传入RabbitMQ的ip,用户名,密码,实例化一个管道
def __init__(self, host, user, password):
self.host = host
self.user = user
self.password = password
self.connection = pika.BlockingConnection(pika.ConnectionParameters(host=self.host, credentials=pika.PlainCredentials(self.user, self.password)))
self.channel = self.connection.channel() # 发送消息在队列中
def send(self, queue_name, body):
self.channel.queue_declare(queue=queue_name, durable=True) # 声明一个持久化队列
self.channel.basic_publish(exchange='',
routing_key=queue_name, # 队列名字
body=body, # 消息内容
properties=pika.BasicProperties(
delivery_mode=2, # 消息持久化
)) # 清除指定队列的所有的消息
def purge(self, queue_name):
self.channel.queue_purge(queue_name) # 删除指定队列
def delete(self, queue_name, if_unused=False, if_empty=False):
self.channel.queue_delete(queue_name, if_unused=if_unused, if_empty=if_empty) # 断开连接
def stop(self):
self.connection.close()
2.消息消费类
(1)同步消息消费
在同步消息消费的时候可能会出现pika库断开的情况,原因是因为pika客户端没有及时发送心跳,连接就被server端断开了。解决方案就是做一个心跳线程来维护连接。
心跳线程类
class Heartbeat(threading.Thread):
def __init__(self, connection):
super(Heartbeat, self).__init__()
self.lock = threading.Lock() # 线程锁
self.connection = connection # rabbit连接
self.quitflag = False # 退出标志
self.stopflag = True # 暂停标志
self.setDaemon(True) # 设置为守护线程,当消息处理完,自动清除
# 间隔10s发送心跳
def run(self):
while not self.quitflag:
time.sleep(10) # 睡10s发一次心跳
self.lock.acquire() # 加线程锁
if self.stopflag:
self.lock.release()
continue
try:
self.connection.process_data_events() # 一直等待服务段发来的消息
except Exception as e:
print "Error format: %s" % (str(e))
self.lock.release()
return
self.lock.release()
# 开启心跳保护
def startheartbeat(self):
self.lock.acquire()
if self.quitflag:
self.lock.release()
return
self.stopflag = False
self.lock.release()
消息消费类
# 同步消息消费类
class RabbitConsumer(object): # 传入RabbitMQ的ip,用户名,密码,实例化一个管道
def __init__(self, host, user, password):
self.host = host
self.user = user
self.password = password
self.connection = pika.BlockingConnection(pika.ConnectionParameters(host=self.host, credentials=pika.PlainCredentials(self.user, self.password)))
self.channel = self.connection.channel() # 进行消费
def receive(self, queue_name, callback_worker, prefetch_count=1): # callback_worker为消费的回调函数
self.channel.queue_declare(queue=queue_name, durable=True)
self.channel.basic_qos(prefetch_count=prefetch_count) # 设置预取的数量,如果为0则不预取,消费者处理越快,可以将这个这设置的越高
self.channel.basic_consume(callback_worker, queue=queue_name) # callback_worker为消费的回调函数
heartbeat = Heartbeat(self.connection) # 实例化一个心跳类
heartbeat.start() # 开启一个心跳线程,不传target的值默认运行run函数
heartbeat.startheartbeat() # 开启心跳保护
self.channel.start_consuming() # 开始消费
调用方法
# 消费回调函数
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="")
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一次等
因此再试用异步的方式时会获得一定的优化
异步消息消费类
# 异步消息消费类
class RabbitConsumerAsync(object):
EXCHANGE = 'amq.direct'
EXCHANGE_TYPE = 'direct' def __init__(self, host, user, password, queue_name="fish_test", callback_worker=None, prefetch_count=1):
self.host = host
self.user = user
self.password = password
self._connection = None
self._channel = None
self._closing = False
self._consumer_tag = None
self.QUEUE = queue_name
self.callbackworker = callback_worker
self.prefetch_count = prefetch_count def connect(self):
return pika.SelectConnection(pika.ConnectionParameters(host=self.host, credentials=pika.PlainCredentials(self.user, self.password)), self.on_connection_open,
stop_ioloop_on_close=False) def on_connection_open(self, unused_connection):
self.add_on_connection_close_callback()
self.open_channel() def add_on_connection_close_callback(self):
self._connection.add_on_close_callback(self.on_connection_closed) def on_connection_closed(self, connection, reply_code, reply_text):
self._channel = None
if self._closing:
self._connection.ioloop.stop()
else:
self._connection.add_timeout(5, self.reconnect) def reconnect(self):
self._connection.ioloop.stop()
if not self._closing:
self._connection = self.connect()
self._connection.ioloop.start() def open_channel(self):
self._connection.channel(on_open_callback=self.on_channel_open) def on_channel_open(self, channel):
self._channel = channel
self._channel.basic_qos(prefetch_count=self.prefetch_count)
self.add_on_channel_close_callback()
self.setup_exchange(self.EXCHANGE) def add_on_channel_close_callback(self):
self._channel.add_on_close_callback(self.on_channel_closed) def on_channel_closed(self, channel, reply_code, reply_text):
print reply_text
self._connection.close() def setup_exchange(self, exchange_name):
self._channel.exchange_declare(self.on_exchange_declareok, exchange_name, self.EXCHANGE_TYPE, durable=True) def on_exchange_declareok(self, unused_frame):
self.setup_queue() def setup_queue(self):
self._channel.queue_declare(self.on_queue_declareok, self.QUEUE, durable=True) def on_queue_declareok(self, method_frame):
self._channel.queue_bind(self.on_bindok, self.QUEUE, self.EXCHANGE, self.QUEUE) def on_bindok(self, unused_frame):
self.start_consuming() def start_consuming(self):
self.add_on_cancel_callback()
self._consumer_tag = self._channel.basic_consume(self.on_message, self.QUEUE) def add_on_cancel_callback(self):
self._channel.add_on_cancel_callback(self.on_consumer_cancelled) def on_consumer_cancelled(self, method_frame):
if self._channel:
self._channel.close() def on_message(self, unused_channel, basic_deliver, properties, body):
self.callbackworker(body)
self.acknowledge_message(basic_deliver.delivery_tag) def acknowledge_message(self, delivery_tag):
self._channel.basic_ack(delivery_tag) def stop_consuming(self):
if self._channel:
self._channel.basic_cancel(self.on_cancelok, self._consumer_tag) def on_cancelok(self, unused_frame):
self.close_channel() def close_channel(self):
self._channel.close() def run(self):
self._connection = self.connect()
self._connection.ioloop.start() def stop(self):
self._closing = True
self.stop_consuming()
self._connection.ioloop.start() def close_connection(self):
self._connection.close()
调用方法
# 消费回调函数
def callback(body):
print(" [x] Received %r" % body) consumer = RabbitConsumerAsync(host="12.12.12.12", user="test", password="", 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)\n' % (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)\n' % (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\n' % 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?\n'
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?\n' % 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()'
调用
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)
参考链接
https://pika.readthedocs.io/en/0.10.0/examples.html
https://www.jianshu.com/p/a4671c59351a
源码下载地址:https://github.com/sy159/RabbiyMQ.git
python实现RabbitMQ同步跟异步消费模型的更多相关文章
- python下载mp4 同步和异步下载支持断点续下
Range 用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式: Range:(unit=first byte pos)-[last byte pos] Range 头部的格式有以下几种 ...
- python操作rabbitmq,实现生产消费者模型
更多详情参考官方文档:https://www.rabbitmq.com/tutorials/tutorial-six-python.html 参考博客:https://blog.csdn.net/we ...
- Python番外之 阻塞非阻塞,同步与异步,i/o模型
1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: 同步/异步主要针对C端: 同步: 所谓同步,就 ...
- Python之协程、异步IO、redis缓存、rabbitMQ队列
本节内容 Gevent协程 Select\Poll\Epoll异步IO与事件驱动 Python连接Mysql数据库操作 RabbitMQ队列 Redis\Memcached缓存 Paramiko SS ...
- ActiveMQ( 一) 同步,异步,阻塞 JMS 消息模型
同步请求:浏览器 向服务器 发送一个登录请求,如果服务器 没有及时响应,则浏览器则会一直等待状态,直至服务器响应或者超时. 异步请求:浏览器 向服务器 发送一个登录请求,不管服务器是否立即响应,浏览器 ...
- python 全栈开发,Day44(IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)
昨日内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yield,greenlet 遇到IO gevent: 检测到IO,能够使用greenlet实现自动切换,规避了IO阻 ...
- python 全栈开发,Day43(引子,协程介绍,Greenlet模块,Gevent模块,Gevent之同步与异步)
昨日内容回顾 I/O模型,面试会问到I/O操作,不占用CPU.它内部有一个专门的处理I/O模块.print和写log 属于I/O操作,它不占用CPU 线程GIL保证一个进程中的多个线程在同一时刻只有一 ...
- python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)
python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程 并行与并发 同步与异步 阻塞与非阻塞 CPU密集型与IO密集型 线程与进程 进 ...
- python全栈开发,Day43(引子,协程介绍,Greenlet模块,Gevent模块,Gevent之同步与异步)
昨日内容回顾 I/O模型,面试会问道 I/O操作,不占用CPU,它内部有一个专门的处理I/O模块 print和写log属于I/O操作,它不占用CPU 线程 GIL保证一个进程中的多个线程在同一时刻只有 ...
随机推荐
- jquery checkbox选中状态以及实现全选反选
jquery1.6以下版本获取checkbox的选中状态: $('.ck').attr('checked'); $('.ck').attr('checked',true);//全选 $('.ck'). ...
- 工具_HBuilder使用快捷方式
HBuilder常用快捷键大概共9类([4 13 3]文件.编辑.插入:[4 9 8]选择.跳转.查找:[1 1 6]运行.工具.视图) 1.文件(4) 新建 Ctrl + N 关闭 Ctrl + F ...
- map,set的底层实现:红黑树[多图,手机慎入]
最近天下有一种颇不太平的感觉,各地的乱刀砍人,到处是贪官服法.京东准备上市了,阿里最近也提交申请了,猎豹也逆袭了,据说猎豹移动在国际市场上表现甚是抢眼.只有屌丝还在写着代码.花开花又谢,花谢花又开,为 ...
- python基础===requests学习笔记
这里有一个新的学习requests网站:http://docs.python-requests.org/zh_CN/latest/user/quickstart.html2017/11/30 Requ ...
- 集合类---set
定义:一个不包含重复元素的collection.set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素,不保证集合里元素的顺序. 方法使用详解: 1 ...
- win10安装提示“我们无法创建新的分区”
今日于笔记本安装win10时突然出现提示:我们无法创建新的分区.网上搜了不少建议,尝试了都无果. 由于我的笔记本是固态硬盘与机械硬盘混合,所以情况可能更加特殊. 最后成功的方法是: 1. 先将Win1 ...
- 给mongodb设置密码权限
昨天装了个mongodb数据库用于测试用,装好后没有密码,现在就讲讲怎么设置密码 1.首先进入C:\mongodb\bin下面运行mongod.exe启动数据库. 2.在相同目录下启动mongo.ex ...
- 在 Visual Studio 中使用正则表达式
Visual Studio 使用 .NET framework 正则表达式查找和替换文本. 在 Visual Studio 2010 和早期版本中,Visual Studio 在“查找和替换”窗口中使 ...
- Leetcode 之Balanced Binary Tree(49)
用递归的方式来做,左右两棵子树的高度差不超过1.分成两部分,一部分递归得到树的高度,一部分递归检查左右子树是否是平衡二叉树. int getHeight(TreeNode *root) { ; ; } ...
- java的装饰设计模式
类似python中的装饰器. 示例: public class Test5 { public static void main(String[] args) { Worker w = new Work ...