一部のメッセージを送信するために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
のようなメソッドはありますか?
それは可能ではないと思います。チャネルには「現在のデータ」がありません。チャネルにサブスクライブし、チャネル上の他のクライアントによってプッシュされているメッセージの受信を開始するため、ブロッキングAPIです。また、Redis コマンドのドキュメント pub/subを見ると、より明確になります。
非ブロッキングの非同期処理を考えている場合は、おそらく非同期フレームワーク/サーバーを使用している(または使用する必要があります)。
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クライアント 。
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を確認してください。
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 :)
これは、ブロッキングリスナーをスレッド化するための実用的な例です。
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()
スレッドのない非ブロッキングソリューションは次のとおりです。
_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で待機できるようにします。
最も効率的なアプローチは、スレッドベースではなく、グリーンレットベースです。 Greenletベースの同時実行フレームワークとして、geventはすでにPython=世界で確立されています。したがって、geventとredis-pyの統合は素晴らしいことです。それがまさにこの問題で議論されていることです。 github:
Noneブロックコードに到達するには、別の種類のパラダイムコードを実行する必要があります。新しいスレッドを使用してすべての変更をリッスンし、メインスレッドに別の処理を任せることは難しくありません。
また、メインスレッドとredisサブスクライバースレッドの間でデータを交換するための何らかのメカニズムが必要になります。
Redisのpub/subは、チャネルでサブスクライブ(リッスン)しているクライアントにメッセージを送信します。聞いていない場合は、メッセージを見逃してしまいます(つまり、ブロッキングコール)。非ブロッキングにしたい場合は、代わりにキューを使用することをお勧めします(redisもかなり優れています)。 pub/subを使用する必要がある場合は、推奨されるgeventとして使用して、非同期のブロッキングリスナーを作成し、メッセージをキューにプッシュし、別のコンシューマーを使用して、そのキューからのメッセージを非ブロッキング方法で処理できます。
Gevent、gevent monkeyパッチを使用して、非ブロッキングredis pubsubアプリを構築できます。