设计
- 该文章主要是花了比较长的时间解决AMQP 数据收发异步的问题。该问题主要在于PIKA包不支持异步的问题。收发数据使用同一个连接和同一个通道会在某些环境异常(Windows 和部分Linux没有出现,在生产环境的Linux下会产生异常) 。而且问题很多,其中一个就是 Stream connection lost: AssertionError((’_AsyncTransportBase._produce() tx buffer size underflow’, -275, 1),)。
设计一个AMQPClientUtil类 用户AMQPClient管理
- AMQMClient管理 这里会创建两个AMQP对象,一个是用户定义的比如 hello_amqp,主要用户其他服务来请求数据,另一个有系统定义hello_amqprep,在用户定义的queue末尾增加req主要用户向其他服务请求数据。这样设计的目的在于将主动请求和主动接受分开。避免一个数据队列数据量过多
- 外部通过AMQPClientUntil的对象调用 只存在启动时,调用run函数,已初始化AMQP连接。发送数据时调用 send
设计一个AMQPClient类
- 该类的主要作用的是连接AMQP,并进行异步收发数据
- 详情见源码内说明
源码
import pika
import json
import threading
import time
import zlib
import datetime
import sys
KEEP_ACTIVE = "keep_active"
def getlocaltime():
'''
格式化 输出时间 这一块主要用于日志输出
'''
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
class AMQPClientUtil():
'''
AMQMClient管理 这里会创建两个AMQP对象,一个是用户定义的比如 hello_amqp,主要用户其他服务来请求数据,另一个有系统定义hello_amqprep,在用户定义的queue末尾增加req主要用户向其他服务请求数据。这样设计的目的在于将主动请求和主动接受分开。避免一个数据队列数据量过多
'''
def __init__(self, queue, exchange, cb_func, username, userpasswd, host, port):
self.recvChannel = AMQPClient(queue, exchange, cb_func, username, userpasswd, host, port)
req_queue = queue + "req"
self.sendChannel = AMQPClient(req_queue, exchange, cb_func, username, userpasswd, host, port)
pass
def run(self):
self.recvChannel.run()
self.sendChannel.run()
def Send(self, routing_key, type, strMsg, szip="false"):
self.sendChannel.Send(routing_key, type, strMsg, szip)
class AMQPClient():
def __init__(self, queue, exchange, cb_func, username, userpasswd, host, port):
'''
:param queue: 队列和RoutineKey
:param exchange: 交换机
:param cb_func: 接收消息的回调函数 函数原型 ( DealMesIn(self, msgType, msg, routineKey) )
'''
print(getlocaltime(), "正在初始化AMQP---- " + queue)
self.queue = queue
self.exchange = exchange
self.pid = 1
self.cb_func = cb_func
self.timmer = None
self.cur_reciveDateTime = datetime.datetime.now()
self.channel = None
self.connection = None
self.channel_send = None
self.connection_send = None
self.properties = pika.BasicProperties(headers={})
credentials = pika.PlainCredentials(username, userpasswd)
self.parameters = pika.ConnectionParameters(host=host, port=port, credentials=credentials, heartbeat=0)
thread = threading.Thread(target=self.__checkReciveTime)
thread.start()
def __checkReciveTime(self):
while 1:
cur_time = datetime.datetime.now()
if (cur_time - self.cur_reciveDateTime).seconds > 600:
print ('AMQP断了正在重新连接......')
self.__ConnectAMQP()
pass
time.sleep(61 * 5)
def __getPid(self):
self.pid += 1
return self.pid
def run(self):
thread = threading.Thread(target=self.__ConnectAMQP)
thread.start()
self.__keep_active()
pass
def __ConnectAMQP(self):
try:
print(getlocaltime(), "ConnectAMQP---- " + self.queue)
self.createAMQP = False
self.__reset()
self.connection, self.channel = self.__getAMQPConeAndChannel()
self.connection_send, self.channel_send = self.__getAMQPConeAndChannel()
print(getlocaltime(), "初始化AMQP成功---- " + self.queue)
self.createAMQP = True
self.channel.start_consuming()
except BaseException as e:
self.__reset()
if isinstance(e, KeyboardInterrupt):
return
exc_type, exc_value, exc_obj = sys.exc_info()
traceback.print_exception(exc_type,exc_value,exc_obj,limit=2,file=sys.stdout)
print(getlocaltime(), "{}:{}".format(sys._getframe().f_code.co_name, sys._getframe().f_lineno), "AMQP异常, ", e)
timmer = threading.Timer(10, AMQPClient.__ConnectAMQP, args=(self, ))
timmer.start()
def __reset(self):
try:
if self.connection:
self.connection.close()
if self.connection_send:
self.connection_send.close()
if self.channel:
self.channel.stop_consuming()
if self.channel:
self.channel_send.stop_consuming()
except Exception as e:
pass
finally:
pass
def __getAMQPConeAndChannel(self):
connection = pika.BlockingConnection(self.parameters)
channel = connection.channel()
channel.exchange_declare(
exchange=self.exchange,
exchange_type='direct',
passive=True,
durable=False,
auto_delete=True)
self.bind_queue(self.queue, channel)
return connection, channel
def bind_queue(self, queue,channel):
dic_args = {"x-expires":60000, "x-message-ttl":30000}
channel.queue_declare(queue=queue, arguments=dic_args)
channel.queue_bind(queue=queue, exchange=self.exchange, routing_key=queue)
channel.basic_qos(prefetch_count=54)
channel.basic_consume(queue=queue, on_message_callback=self.DealMesIn, auto_ack=False)
def __keep_active(self):
self.Send(self.queue, KEEP_ACTIVE, KEEP_ACTIVE)
if self.timmer != None:
self.timmer.cancel()
self.timmer = threading.Timer(15, AMQPClient.__keep_active, args=(self, ))
self.timmer.start()
def DealMesIn(self, ch, method, properties, body):
self.cur_reciveDateTime = datetime.datetime.now()
dic_args = {"x-expires": 60000, "x-message-ttl": 30000}
queue_declare = self.channel.queue_declare(queue=self.queue, arguments=dic_args)
headers = properties.headers
try:
if headers["zip"] == 'false' and (body.decode() == KEEP_ACTIVE):
print(getlocaltime(), body.decode())
else:
msg = ""
if headers["zip"] == "true":
decompress = zlib.decompressobj()
msg = decompress.decompress(body)
else:
msg = body.decode()
self.cb_func(headers["type"], msg, headers["from"])
except Exception as e:
exc_type, exc_value, exc_obj = sys.exc_info()
traceback.print_exception(exc_type,exc_value,exc_obj,limit=2,file=sys.stdout)
print (getlocaltime(), "{}:{}".format(sys._getframe().f_code.co_name, sys._getframe().f_lineno), "recive...error:",e)
finally:
ch.basic_ack(delivery_tag=method.delivery_tag)
def Send(self, routing_key, type, strMsg, szip="false"):
if not self.createAMQP:
return
if szip:
szip = "false"
dict_header = {}
dict_header["type"] = type
dict_header["from"] = self.queue
dict_header["pid"] = str(self.__getPid())
dict_header["structlen"] = str(len(strMsg))
dict_header["zip"] = szip
try:
self.properties.headers = dict_header
self.channel_send.basic_publish(exchange=self.exchange, routing_key=routing_key, properties=self.properties, body=strMsg)
except:
print (getlocaltime(), "{}:{}".format(sys._getframe().f_code.co_name, sys._getframe().f_lineno), "send...error")
finally:
pass
def OnMessage(ch, method, properties, body):
headers = properties.headers
if headers["zip"] == 'false':
print(datetime.datetime.now().strftime("%H:%M:%S"), "nozip", body)
else:
print(zlib.decompress(body))
pass
def aaa():
print (getlocaltime(), "{}:{}".format(sys._getframe().f_code.co_name, sys._getframe().f_lineno))
if __name__ == '__main__':
aaa()
a = None
try:
a = AMQPClientUtil("hello_test2", "exchange", OnMessage, "username", "password", "amqp地址", "port")
a.run()
dict_data = {}
dict_data["SubscribeMarketData"] = ["AP101"]
time.sleep(3)
while 1:
a.Send("hello_test1", "SubscribeMarketData", json.dumps(dict_data))
time.sleep(60)
pass
finally:
if a != None:
pass