web-dev-qa-db-ja.com

pika / RabbitMQでの長時間実行タスクの処理

プロデューサーが複数のタスクを生成し、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オブジェクトを使用しています。そのスレッドは安全ですか?

要するに、これを行うための好ましい方法は何ですか?これは非常に乱雑で、スレッドセーフでない可能性があるため、適切に処理していない可能性があります。ありがとう!

46
jmacdonagh

今のところ、あなたの最善の策はハートビートをオフにすることです。これにより、ブロックが長すぎる場合にRabbitMQが接続を閉じないようにします。私はpikaのコア接続管理とIOバックグラウンドスレッドで実行中のループを試していますが、リリースするには十分に安定していません。

pika v1.1. これはConnectionParameters(heartbeat=0)です

22
Gavin M. Roy

あなたと同じ問題が発生しました。
私のソリューションは:

  1. サーバー側のハートビートをオフに
  2. タスクが取り得る最大時間を評価する
  3. クライアントのハートビートタイムアウトをstep2から取得した時間に設定します

なんでこれ?

私は次のケースでテストします:

  1. サーバーのハートビートがオン、1800
  2. クライアント設定解除

タスクが非常に長い時間実行されているときにエラーが発生する-> 1800

  1. サーバーのハートビートをオフにする
  2. クライアントのハートビートをオフにする

1つの問題を除いて、クライアント側にエラーはありません-クライアントがクラッシュした場合(一部の障害でOSが再起動した場合)、Rabbitmq管理プラグインでtcp接続が引き続き表示されます。そして、それは紛らわしいです。

  1. サーバーのハートビートをオフにする
  2. クライアントハートビートをオンにし、予測最大実行時間に設定します

この場合、個々のクライアントのすべてのヒートビートを動的に変更できます。実際、頻繁にクラッシュするマシンにハートビートを設定しました。さらに、Rabbitmq Manangementプラグインを通じてオフラインのマシンを確認できます。

環境

OS:centos x86_64
ピカ:0.9.13
rabbitmq:3.3.1

10
Mr. C

ハートビートを無効にしないでください!

Pika 0.12.0の時点で、 このサンプルコード で説明されている手法を使用して、別のスレッドで長期実行タスクを実行し、そのスレッドからのメッセージを確認してください。


注意: RabbitMQチームは rabbitmq-usersメーリングリスト を監視し、StackOverflowの質問に回答することもあります。

6
Luke Bakken

ハートビートを無効にしないでください。
最良の解決策は、タスクを別のスレッドで実行し、prefetch_count1に設定して、コンシューマーがこのchannel.basic_qos(prefetch_count=1)

5
Abdullah Saleem
  1. connection.process_data_events()long_running_task(connection)を定期的に呼び出すことができます。この関数は、呼び出されたときにサーバーにハートビートを送信し、pikaクライアントを近くから遠ざけます。
  2. ピカBlockingConnectionconnection.process_data_events()呼び出しよりも大きいハートビート値を設定します。
3
Mars

また、新しいスレッドを設定し、この新しいスレッドでメッセージを処理し、このスレッドが生きている間に接続で.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

0
Demircan Celebi