web-dev-qa-db-ja.com

非ブロッキングRedis pubsubは可能ですか?

一部のメッセージを送信するためにredisのpubsubを使用したいが、以下のコードのようにlistenを使用してブロックされたくない:

import redis
rc = redis.Redis()

ps = rc.pubsub()
ps.subscribe(['foo', 'bar'])

rc.publish('foo', 'hello world')

for item in ps.listen():
    if item['type'] == 'message':
        print item['channel']
        print item['data']

最後のforセクションはブロックされます。特定のチャネルにデータがあるかどうかを確認したいだけですが、どうすればこれを実現できますか? checkのようなメソッドはありますか?

27
limboy

それは可能ではないと思います。チャネルには「現在のデータ」がありません。チャネルにサブスクライブし、チャネル上の他のクライアントによってプッシュされているメッセージの受信を開始するため、ブロッキングAPIです。また、Redis コマンドのドキュメント pub/subを見ると、より明確になります。

7
Ankur

非ブロッキングの非同期処理を考えている場合は、おそらく非同期フレームワーク/サーバーを使用している(または使用する必要があります)。

  • Tornado を使用している場合、 Tornado-Redis があります。ネイティブのトルネードジェネレーター呼び出しを使用しています。その Websocketデモ は、pub/subと組み合わせて使用​​する方法の例を提供します。

  • Twisted を使用している場合、 txRedis があります。そこにも pub/sub example があります。

  • Geventのサルのパッチgevent.monkey.patch_all())を使用しても、Redis-pyを Gevent と組み合わせて問題なく使用できるようです。

更新:元の回答から5年が経過しましたが、その間Python got native async IO support です。現在、 AIORedis、非同期IO Redisクライアント

43
vartec

Redis-pyは非ブロッキングget_message()の使用を推奨しているため、受け入れられた回答は廃止されました。ただし、スレッドを簡単に使用する方法も提供します。

https://pypi.python.org/pypi/redis

メッセージを読み取るには、3つの異なる方法があります。

バックグラウンドで、get_message()はシステムの「select」モジュールを使用して、接続のソケットをすばやくポーリングします。読み取ることができるデータがある場合、get_message()はそれを読み取ってメッセージをフォーマットし、それを返すか、メッセージハンドラーに渡します。読み取るデータがない場合、get_message()はすぐにNoneを返します。これにより、アプリケーション内の既存のイベントループに統合するのが簡単になります。

 while True:
     message = p.get_message()
     if message:
         # do something with the message
     time.sleep(0.001)  # be Nice to the system :)

古いバージョンのredis-pyは、pubsub.listen()でメッセージを読み取るだけです。 listen()は、メッセージが利用可能になるまでブロックするジェネレーターです。アプリケーションがredisから受信したメッセージを受信して​​処理する以外に何もする必要がない場合、listen()は実行を開始する簡単な方法です。

 for message in p.listen():
     # do something with the message

3番目のオプションは、別のスレッドでイベントループを実行します。 pubsub.run_in_thread()は新しいスレッドを作成し、イベントループを開始します。スレッドオブジェクトは、run_in_thread()の呼び出し元に返されます。呼び出し元は、thread.stop()メソッドを使用して、イベントループとスレッドをシャットダウンできます。舞台裏では、これは単に別のスレッドで実行されるget_message()のラッパーであり、本質的に小さな非ブロッキングイベントループを作成します。 run_in_thread()はオプションのsleep_time引数を取ります。指定した場合、イベントループは、ループの各反復の値を使用してtime.sleep()を呼び出します。

注:別のスレッドで実行しているため、登録されたメッセージハンドラーで自動的に処理されないメッセージを処理する方法はありません。したがって、メッセージハンドラーがアタッチされていないパターンまたはチャネルにサブスクライブしている場合、redis-pyはrun_in_thread()の呼び出しを防ぎます。

p.subscribe(**{'my-channel': my_handler})
thread = p.run_in_thread(sleep_time=0.001)
# the event loop is now running in the background processing messages
# when it's time to shut it down...
thread.stop()

したがって、質問に答えるには、メッセージが到着したかどうかを知りたいときにget_messageを確認してください。

14
dalore

Redis-pyの新しいバージョンは非同期pubsubをサポートしています。詳細については https://github.com/andymccurdy/redis-py を確認してください。ドキュメント自体の例を次に示します。

while True:
    message = p.get_message()
    if message:
        # do something with the message
    time.sleep(0.001)  # be Nice to the system :)
13

これは、ブロッキングリスナーをスレッド化するための実用的な例です。

import sys
import cmd
import redis
import threading


def monitor():
    r = redis.Redis(YOURHOST, YOURPORT, YOURPASSWORD, db=0)

    channel = sys.argv[1]
    p = r.pubsub()
    p.subscribe(channel)

    print 'monitoring channel', channel
    for m in p.listen():
        print m['data']


class my_cmd(cmd.Cmd):
    """Simple command processor example."""

    def do_start(self, line):
        my_thread.start()

    def do_EOF(self, line):
        return True


if __name__ == '__main__':
    if len(sys.argv) == 1:
        print "missing argument! please provide the channel name."
    else:
        my_thread = threading.Thread(target=monitor)
        my_thread.setDaemon(True)

        my_cmd().cmdloop()
7
dirkk0

スレッドのない非ブロッキングソリューションは次のとおりです。

_fd = ps.connection._sock.fileno();
rlist,, = select.select([fd], [], [], 0) # or replace 0 with None to block
if rlist:
    for rfd in rlist:
        if fd == rfd:
            message = ps.get_message()
_

ps.get_message()はそれ自体で十分ですが、このメソッドを使用して、redis接続だけでなく複数のfdsで待機できるようにします。

2
chacham15

最も効率的なアプローチは、スレッドベースではなく、グリーンレットベースです。 Greenletベースの同時実行フレームワークとして、geventはすでにPython=世界で確立されています。したがって、geventとredis-pyの統合は素晴らしいことです。それがまさにこの問題で議論されていることです。 github:

https://github.com/andymccurdy/redis-py/issues/31

1

Noneブロックコードに到達するには、別の種類のパラダイムコードを実行する必要があります。新しいスレッドを使用してすべての変更をリッスンし、メインスレッドに別の処理を任せることは難しくありません。

また、メインスレッドとredisサブスクライバースレッドの間でデータを交換するための何らかのメカニズムが必要になります。

1
pfreixes

Redisのpub/subは、チャネルでサブスクライブ(リッスン)しているクライアントにメッセージを送信します。聞いていない場合は、メッセージを見逃してしまいます(つまり、ブロッキングコール)。非ブロッキングにしたい場合は、代わりにキューを使用することをお勧めします(redisもかなり優れています)。 pub/subを使用する必要がある場合は、推奨されるgeventとして使用して、非同期のブロッキングリスナーを作成し、メッセージをキューにプッシュし、別のコンシューマーを使用して、そのキューからのメッセージを非ブロッキング方法で処理できます。

0
Glaslos

Gevent、gevent monkeyパッチを使用して、非ブロッキングredis pubsubアプリを構築できます。

0
Sri