Python 如何异步使用Tornado和Redis?

Python 如何异步使用Tornado和Redis?,python,asynchronous,redis,tornado,Python,Asynchronous,Redis,Tornado,我试图找到如何异步使用Redis和Tornado。我找到了,但我需要的不仅仅是在代码中添加一个yield 我有以下代码: import redis import tornado.web class WaiterHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): client = redis.StrictRedis(port=6279) pub

我试图找到如何异步使用Redis和Tornado。我找到了,但我需要的不仅仅是在代码中添加一个
yield

我有以下代码:

import redis
import tornado.web

class WaiterHandler(tornado.web.RequestHandler):

    @tornado.web.asynchronous
    def get(self):
        client = redis.StrictRedis(port=6279)
        pubsub = client.pubsub()
        pubsub.subscribe('test_channel')

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

        self.write(item['data'])
        self.finish()


class GetHandler(tornado.web.RequestHandler):

    def get(self):
        self.write("Hello world")


application = tornado.web.Application([
    (r"/", GetHandler),
    (r"/wait", WaiterHandler),
])

if __name__ == '__main__':
    application.listen(8888)
    print 'running'
    tornado.ioloop.IOLoop.instance().start()
我需要访问
/
url并获取“Hello World”,同时
/wait
中有一个待处理的请求。
我该怎么做?

您不应该在Tornado主线程中使用Redis pub/sub,因为它会阻塞IO循环。您可以在主线程中处理来自web客户端的长轮询,但是您应该创建一个单独的线程来侦听Redis。然后,您可以使用
ioloop.add_callback()
和/或
threading.Queue
在接收消息时与主线程通信。

您需要使用与Tornado ioloop兼容的redis客户端

它们很少有,等等


下面是toredis中的pubsub示例:

好的,下面是我如何处理get请求的示例

我添加了两个主要组件:

第一个是一个简单的线程化pubsub侦听器,它将新消息附加到本地列表对象中。 我还向类中添加了列表访问器,因此您可以像从常规列表中读取一样从侦听器线程中读取。就
WebRequest
而言,您只是从本地列表对象读取数据。这会立即返回,不会阻止当前请求完成,也不会阻止将来的请求被接受和处理

class OpenChannel(threading.Thread):
    def __init__(self, channel, host = None, port = None):
        threading.Thread.__init__(self)
        self.lock = threading.Lock()
        self.redis = redis.StrictRedis(host = host or 'localhost', port = port or 6379)
        self.pubsub = self.redis.pubsub()
        self.pubsub.subscribe(channel)

        self.output = []

    # lets implement basic getter methods on self.output, so you can access it like a regular list
    def __getitem__(self, item):
        with self.lock:
            return self.output[item]

    def __getslice__(self, start, stop = None, step = None):
        with self.lock:
            return self.output[start:stop:step]

    def __str__(self):
        with self.lock:
            return self.output.__str__()

    # thread loop
    def run(self):
        for message in self.pubsub.listen():
            with self.lock:
                self.output.append(message['data'])

    def stop(self):
        self._Thread__stop()
第二个是ApplicationMixin类。这是让web请求类继承的第二个对象,以便添加功能和属性。在本例中,它检查请求的频道是否已经存在一个频道侦听器,如果没有找到,则创建一个,并将侦听器句柄返回给WebRequest

# add a method to the application that will return existing channels
# or create non-existing ones and then return them
class ApplicationMixin(object):
    def GetChannel(self, channel, host = None, port = None):
        if channel not in self.application.channels:
            self.application.channels[channel] = OpenChannel(channel, host, port)
            self.application.channels[channel].start()
        return self.application.channels[channel]
WebRequest类现在将侦听器视为一个静态列表(请记住,您需要给
self.write
一个字符串)

最后,在创建应用程序之后,我添加了一个空字典作为属性

# add a dictionary containing channels to your application
application.channels = {}
以及在退出应用程序后对正在运行的线程进行一些清理

# clean up the subscribed channels
for channel in application.channels:
    application.channels[channel].stop()
    application.channels[channel].join()
完整的代码:

import threading
import redis
import tornado.web



class OpenChannel(threading.Thread):
    def __init__(self, channel, host = None, port = None):
        threading.Thread.__init__(self)
        self.lock = threading.Lock()
        self.redis = redis.StrictRedis(host = host or 'localhost', port = port or 6379)
        self.pubsub = self.redis.pubsub()
        self.pubsub.subscribe(channel)

        self.output = []

    # lets implement basic getter methods on self.output, so you can access it like a regular list
    def __getitem__(self, item):
        with self.lock:
            return self.output[item]

    def __getslice__(self, start, stop = None, step = None):
        with self.lock:
            return self.output[start:stop:step]

    def __str__(self):
        with self.lock:
            return self.output.__str__()

    # thread loop
    def run(self):
        for message in self.pubsub.listen():
            with self.lock:
                self.output.append(message['data'])

    def stop(self):
        self._Thread__stop()


# add a method to the application that will return existing channels
# or create non-existing ones and then return them
class ApplicationMixin(object):
    def GetChannel(self, channel, host = None, port = None):
        if channel not in self.application.channels:
            self.application.channels[channel] = OpenChannel(channel, host, port)
            self.application.channels[channel].start()
        return self.application.channels[channel]

class ReadChannel(tornado.web.RequestHandler, ApplicationMixin):
    @tornado.web.asynchronous
    def get(self, channel):
        # get the channel
        channel = self.GetChannel(channel)
        # write out its entire contents as a list
        self.write('{}'.format(channel[:]))
        self.finish() # not necessary?


class GetHandler(tornado.web.RequestHandler):

    def get(self):
        self.write("Hello world")


application = tornado.web.Application([
    (r"/", GetHandler),
    (r"/channel/(?P<channel>\S+)", ReadChannel),
])


# add a dictionary containing channels to your application
application.channels = {}


if __name__ == '__main__':
    application.listen(8888)
    print 'running'
    try:
        tornado.ioloop.IOLoop.instance().start()
    except KeyboardInterrupt:
        pass

    # clean up the subscribed channels
    for channel in application.channels:
        application.channels[channel].stop()
        application.channels[channel].join()
导入线程
导入redis
导入tornado.web
类OpenChannel(threading.Thread):
def _;初始化(self,channel,host=None,port=None):
threading.Thread.\uuuuu init\uuuuuu(自)
self.lock=threading.lock()
self.redis=redis.StrictRedis(主机=主机或'localhost',端口=端口或6379)
self.pubsub=self.redis.pubsub()
self.pubsub.subscribe(频道)
self.output=[]
#让我们在self.output上实现基本的getter方法,这样您就可以像访问常规列表一样访问它
定义获取项目(自身,项目):
使用self.lock:
返回自输出[项目]
def uu getslice uu(self、start、stop=None、step=None):
使用self.lock:
返回自输出[开始:停止:步骤]
定义(自我):
使用self.lock:
返回self.output.\uuu str\uuu()
#螺纹环
def运行(自):
对于self.pubsub.listen()中的消息:
使用self.lock:
self.output.append(消息['data'])
def停止(自):
self.\u线程\u停止()
#向应用程序添加将返回现有频道的方法
#或者创建不存在的,然后返回它们
类ApplicationMixin(对象):
def GetChannel(自身、通道、主机=无、端口=无):
如果通道不在self.application.channels中:
self.application.channels[channel]=OpenChannel(通道、主机、端口)
self.application.channels[channel].start()
返回self.application.channels[频道]
类ReadChannel(tornado.web.RequestHandler,ApplicationMixin):
@tornado.web.asynchronous
def get(自我,通道):
#接通频道
channel=self.GetChannel(通道)
#把它的全部内容列成一个列表
self.write({}.format(通道[:]))
self.finish()#不需要吗?
类GetHandler(tornado.web.RequestHandler):
def get(自我):
self.write(“你好,世界”)
application=tornado.web.application([
(r“/”,GetHandler),
(r“/通道/(?P\S+)”,读通道),
])
#向应用程序添加包含频道的字典
application.channels={}
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
申请。听(8888)
打印“正在运行”
尝试:
tornado.ioloop.ioloop.instance().start()
除键盘中断外:
通过
#清理订阅的频道
对于application.channels中的频道:
application.channels[channel].stop()
application.channels[channel].join()

对于Python>=3.3,我建议您使用。 我没有测试下面的代码,但应该是这样的:

import redis
import tornado.web
from tornado.web import RequestHandler

import aioredis
import asyncio
from aioredis.pubsub import Receiver


class WaiterHandler(tornado.web.RequestHandler):

    @tornado.web.asynchronous
    def get(self):
        client = await aioredis.create_redis((host, 6279), encoding="utf-8", loop=IOLoop.instance().asyncio_loop)

        ch = redis.channels['test_channel']
        result = None
        while await ch.wait_message():
            item = await ch.get()
            if item['type'] == 'message':
                print item['channel']
                print item['data']
                result = item['data']

        self.write(result)
        self.finish()


class GetHandler(tornado.web.RequestHandler):

    def get(self):
        self.write("Hello world")


application = tornado.web.Application([
    (r"/", GetHandler),
    (r"/wait", WaiterHandler),
])

if __name__ == '__main__':
    print 'running'
    tornado.ioloop.IOLoop.configure('tornado.platform.asyncio.AsyncIOLoop')
    server = tornado.httpserver.HTTPServer(application)
    server.bind(8888)
    # zero means creating as many processes as there are cores.
    server.start(0)
    tornado.ioloop.IOLoop.instance().start()

Redis pub/sub不应在
web.RequestHandler
中使用,因为它将在等待
pubsub.listen()
时阻止ioloop。请看一个工作的websocket示例。websocket是一个不错的选择,但是我的应用程序需要在不支持websocket的浏览器中工作。我正在使用长轮询。这就是我需要“异步get”的原因。@HelieelsonSantos在这种情况下,最好的办法是维护订阅的频道历史记录的本地状态(由单独的线程输入),然后立即将该状态写入响应并完成
get
操作。客户机应该维护一些上次获取索引或上次获取时间等的记录,这样您就可以为不同的客户机保持连续性。当我有时间的时候,我会在几个小时内用一个例子写一个答案。你可以很容易地用一个队列或其他支持非阻塞访问的对象替换列表,并且只返回自上次请求以来收到的消息。但是,您必须为每个客户机维护一个队列,并确保使用非阻塞get并正确处理
Empty
异常。
import redis
import tornado.web
from tornado.web import RequestHandler

import aioredis
import asyncio
from aioredis.pubsub import Receiver


class WaiterHandler(tornado.web.RequestHandler):

    @tornado.web.asynchronous
    def get(self):
        client = await aioredis.create_redis((host, 6279), encoding="utf-8", loop=IOLoop.instance().asyncio_loop)

        ch = redis.channels['test_channel']
        result = None
        while await ch.wait_message():
            item = await ch.get()
            if item['type'] == 'message':
                print item['channel']
                print item['data']
                result = item['data']

        self.write(result)
        self.finish()


class GetHandler(tornado.web.RequestHandler):

    def get(self):
        self.write("Hello world")


application = tornado.web.Application([
    (r"/", GetHandler),
    (r"/wait", WaiterHandler),
])

if __name__ == '__main__':
    print 'running'
    tornado.ioloop.IOLoop.configure('tornado.platform.asyncio.AsyncIOLoop')
    server = tornado.httpserver.HTTPServer(application)
    server.bind(8888)
    # zero means creating as many processes as there are cores.
    server.start(0)
    tornado.ioloop.IOLoop.instance().start()