非阻塞Redis pubsub可能吗?

时间:2011-10-24 05:13:20

标签: python redis redis-py

我想使用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的方法?

10 个答案:

答案 0 :(得分:43)

如果您正在考虑非阻塞,异步处理,那么您可能正在使用(或应该使用)异步框架/服务器。

更新: 距离原始答案已经过去了5年,同时Python获得native async IO support。现在有AIORedis, an async IO Redis client

答案 1 :(得分:14)

已接受的答案已过时,因为redis-py建议您使用非阻塞get_message()。但它也提供了一种轻松使用线程的方法。

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

阅读信息有三种不同的策略。

在幕后,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

第三个选项在单独的线程中运行事件循环。 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。

答案 2 :(得分:13)

新版本的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 :)

答案 3 :(得分:7)

我认为不可能。频道没有任何“当前数据”,您订阅频道并开始接收频道上其他客户端正在推送的消息,因此它是一个阻止API。此外,如果您查看发布/订阅的Redis Commands documentation,它会更清晰。

答案 4 :(得分:7)

这是阻塞阻塞侦听器的一个工作示例。

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()

答案 5 :(得分:2)

这是一个没有线程的非阻塞解决方案:

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()本身就足够了,但我使用这种方法,以便我可以等待多个fds而不仅仅是redis连接。

答案 6 :(得分:1)

要获得无阻塞代码,您必须执行另一种范例代码。使用新线程来监听所有更改并让主线程执行其他操作并不难。

此外,您需要一些机制来在主线程和redis订户线程之间交换数据。

答案 7 :(得分:1)

最有效的方法是基于greenlet而不是基于线程。作为一个基于greenlet的并发框架,gevent已经在Python世界中得到了很好的建立。因此,与redis-py的gevent集成将是美妙的。这正是github上本期讨论的内容:

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

答案 8 :(得分:0)

你可以使用gevent,gevent monkey补丁来构建一个非阻塞的redis pubsub应用程序。

答案 9 :(得分:0)

的Redis' pub / sub向在频道上订阅(收听)的客户端发送消息。如果您没有收听,您将错过该消息(因此阻止呼叫)。如果你想让它无阻塞,我建议使用一个队列(redis也很擅长)。如果必须使用pub / sub,则可以使用建议的gevent来创建异步的阻塞侦听器,将消息推送到队列并使用单独的使用者以非阻塞方式处理来自该队列的消息。