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()
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()
消息消费类
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() # 开始消费
调用方法
# 消费回调函数
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()
调用方法
# 消费回调函数
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)\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
来源:oschina
链接:https://my.oschina.net/u/4383224/blog/3702453