我们正在尝试建立一个基本的有向队列系统,其中生产者将生成多个任务,一个或多个消费者将一次获取任务,处理它并确认该消息。
问题是,处理过程可能需要10-20分钟,我们当时没有响应消息,导致服务器断开连接。
这是我们消费者的一些伪代码:
#!/usr/bin/env python
import pika
import time
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
print ' [*] Waiting for messages. To exit press CTRL+C'
def callback(ch, method, properties, body):
long_running_task(connection)
ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(callback,
queue='task_queue')
channel.start_consuming()
第一个任务完成后,会在BlockingConnection内部某处抛出异常,抱怨套接字已重置。此外,RabbitMQ日志显示消费者因未及时响应而断开连接(为什么重置连接而不是发送FIN很奇怪,但我们不会担心这一点。)
我们搜索了很多,因为我们认为这是RabbitMQ的正常使用案例(有许多长期运行的任务应该在许多消费者中分开),但似乎没有其他人真正有这个问题。最后,我们偶然发现了一个线程,建议使用心跳并在单独的线程中生成long_running_task()
。
所以代码变成了:
#!/usr/bin/env python
import pika
import time
import threading
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost',
heartbeat_interval=20))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
print ' [*] Waiting for messages. To exit press CTRL+C'
def thread_func(ch, method, body):
long_running_task(connection)
ch.basic_ack(delivery_tag = method.delivery_tag)
def callback(ch, method, properties, body):
threading.Thread(target=thread_func, args=(ch, method, body)).start()
channel.basic_qos(prefetch_count=1)
channel.basic_consume(callback,
queue='task_queue')
channel.start_consuming()
这似乎有效,但它非常混乱。我们确定ch
对象是线程安全的吗?另外,假设long_running_task()
正在使用该连接参数将任务添加到新队列(即,完成此长进程的第一部分,让我们将任务发送到第二部分)。因此,该线程正在使用connection
对象。这个线程安全吗?
更重要的是,这样做的首选方式是什么?我觉得这很麻烦,可能不是线程安全的,所以也许我们做得不对。谢谢!
答案 0 :(得分:20)
目前,您最好的选择是关闭心跳,如果您长时间阻止,这将阻止RabbitMQ关闭连接。我正在尝试在后台线程中运行pika的核心连接管理和IO循环,但它不够稳定,不能发布。
答案 1 :(得分:10)
我遇到了同样的问题。
我的解决方案是:
我测试以下情况:
案例一当任务运行很长时间时仍然会出错 - > 1800
案例二客户端没有错误,除了一个问题 - 当客户端崩溃时(我的操作系统重新启动某些故障),仍然可以在Rabbitmq Management插件中看到tcp连接。而且令人困惑。
案例三在这种情况下,我可以动态地改变不同客户的每一次热潮。事实上,我经常在经常崩溃的机器上设置心跳。此外,我可以通过Rabbitmq Manangement插件看到离线机器。
操作系统:centos x86_64
皮卡:0.9.13
rabbitmq:3.3.1
答案 2 :(得分:6)
请不要禁用心跳!
从Pika 0.12.0
开始,请使用this example code中描述的技术在单独的线程上运行长时间运行的任务,然后确认来自该线程的消息。
注意: RabbitMQ团队监视the rabbitmq-users
mailing list,并且有时仅在StackOverflow上回答问题。
答案 3 :(得分:5)
请勿禁用心跳
最佳解决方案是在单独的线程中运行任务,并将prefetch_count
设置为1
,以便使用者只获得1条未确认的消息
使用类似channel.basic_qos(prefetch_count=1)
答案 4 :(得分:3)
connection.process_data_events()
中定期致电long_running_task(connection)
,此功能会在呼叫时向服务器发送心跳,并让鼠标客户端远离关闭。connection.process_data_events()
中设置心跳值大于呼叫BlockingConnection
句点。答案 5 :(得分:0)
您还可以设置一个新线程,并在该新线程中处理消息,并在该线程处于活动状态时在连接上调用.sleep
,以防止丢失心跳。这是从github中的@gmr提取的示例代码块,以及指向该问题的链接以供将来参考。
import re
import json
import threading
from google.cloud import bigquery
import pandas as pd
import pika
from unidecode import unidecode
def process_export(url, tablename):
df = pd.read_csv(csvURL, encoding="utf-8")
print("read in the csv")
columns = list(df)
ascii_only_name = [unidecode(name) for name in columns]
cleaned_column_names = [re.sub("[^a-zA-Z0-9_ ]", "", name) for name in ascii_only_name]
underscored_names = [name.replace(" ", "_") for name in cleaned_column_names]
valid_gbq_tablename = "test." + tablename
df.columns = underscored_names
# try:
df.to_gbq(valid_gbq_tablename, "some_project", if_exists="append", verbose=True, chunksize=10000)
# print("Finished Exporting")
# except Exception as error:
# print("unable to export due to: ")
# print(error)
# print()
def data_handler(channel, method, properties, body):
body = json.loads(body)
thread = threading.Thread(target=process_export, args=(body["csvURL"], body["tablename"]))
thread.start()
while thread.is_alive(): # Loop while the thread is processing
channel._connection.sleep(1.0)
print('Back from thread')
channel.basic_ack(delivery_tag=method.delivery_tag)
def main():
params = pika.ConnectionParameters(host='localhost', heartbeat=60)
connection = pika.BlockingConnection(params)
channel = connection.channel()
channel.queue_declare(queue="some_queue", durable=True)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(data_handler, queue="some_queue")
try:
channel.start_consuming()
except KeyboardInterrupt:
channel.stop_consuming()
channel.close()
if __name__ == '__main__':
main()
python
链接: https://github.com/pika/pika/issues/930#issuecomment-360333837
答案 6 :(得分:0)
这是使用线程处理此问题的一种更简单的方法。如果消费者应用程序在当前作业完成之前不应消耗另一个作业,则特别有用。 ack 可以随时发送 - 在这种情况下,我选择仅在工作完成时发送它(线程不再处于活动状态)。
在其自己的线程中启动长时间运行的进程,然后通过调用 channel.process_data_events() 在循环中监视该线程。在主线程中保留对连接对象的引用,因为它不是线程安全的。本质上:
import time
import pika
from threading import Thread
from functools import partial
rmqconn = pika.BlockingConnection( ... )
rmqchan = rmqconn.channel()
rmqchan.basic_consume(
queue='test',
on_message_callback=partial(launch_process,rmqconn)
)
rmqchan.start_consuming()
def launch_process(conn,ch,method,properties,body):
runthread = Thread(target=run_process,args=body)
runthread.start()
while runthread.is_alive():
time.sleep(2)
conn.process_data_events()
ch.basic_ack(delivery_tag=method.delivery_tag)
def run_process(body):
#do the long-running thing
time.sleep(10)