プロデューサーが複数のタスクを生成し、1人以上のコンシューマーが一度にタスクを取得して処理し、メッセージを確認する基本的なダイレクトキューシステムを設定しようとしています。
問題は、処理に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()
がその接続パラメーターを使用してタスクを新しいキューに追加することを想像してください(つまり、この長いプロセスの最初の部分が完了したら、タスクを2番目の部分に送信しましょう)。そのため、スレッドはconnection
オブジェクトを使用しています。そのスレッドは安全ですか?
要するに、これを行うための好ましい方法は何ですか?これは非常に乱雑で、スレッドセーフでない可能性があるため、適切に処理していない可能性があります。ありがとう!
今のところ、あなたの最善の策はハートビートをオフにすることです。これにより、ブロックが長すぎる場合にRabbitMQが接続を閉じないようにします。私はpikaのコア接続管理とIOバックグラウンドスレッドで実行中のループを試していますが、リリースするには十分に安定していません。
pika v1.1. これはConnectionParameters(heartbeat=0)
です
あなたと同じ問題が発生しました。
私のソリューションは:
私は次のケースでテストします:
タスクが非常に長い時間実行されているときにエラーが発生する-> 1800
1つの問題を除いて、クライアント側にエラーはありません-クライアントがクラッシュした場合(一部の障害でOSが再起動した場合)、Rabbitmq管理プラグインでtcp接続が引き続き表示されます。そして、それは紛らわしいです。
この場合、個々のクライアントのすべてのヒートビートを動的に変更できます。実際、頻繁にクラッシュするマシンにハートビートを設定しました。さらに、Rabbitmq Manangementプラグインを通じてオフラインのマシンを確認できます。
OS:centos x86_64
ピカ:0.9.13
rabbitmq:3.3.1
ハートビートを無効にしないでください!
Pika 0.12.0
の時点で、 このサンプルコード で説明されている手法を使用して、別のスレッドで長期実行タスクを実行し、そのスレッドからのメッセージを確認してください。
注意: RabbitMQチームは rabbitmq-users
メーリングリスト を監視し、StackOverflowの質問に回答することもあります。
ハートビートを無効にしないでください。
最良の解決策は、タスクを別のスレッドで実行し、prefetch_count
を1
に設定して、コンシューマーがこのchannel.basic_qos(prefetch_count=1)
connection.process_data_events()
でlong_running_task(connection)
を定期的に呼び出すことができます。この関数は、呼び出されたときにサーバーにハートビートを送信し、pikaクライアントを近くから遠ざけます。BlockingConnection
のconnection.process_data_events()
呼び出しよりも大きいハートビート値を設定します。また、新しいスレッドを設定し、この新しいスレッドでメッセージを処理し、このスレッドが生きている間に接続で.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 __== '__main__':
main()
python
リンク: https://github.com/pika/pika/issues/930#issuecomment-360333837