【问题标题】:Asyncio + aiohttp - redis Pub/Sub and websocket read/write in single handlerAsyncio + aiohttp - redis Pub/Sub 和 websocket 在单个处理程序中读/写
【发布时间】:2015-10-18 15:27:36
【问题描述】:

我目前正在使用aiohttp 来了解它作为具有 websocket 连接的移动应用程序的服务器应用程序的性能。

这是一个简单的“Hello world”示例 (as gist here):

import asyncio
import aiohttp
from aiohttp import web


class WebsocketEchoHandler:

    @asyncio.coroutine
    def __call__(self, request):
        ws = web.WebSocketResponse()
        ws.start(request)

        print('Connection opened')
        try:
            while True:
                msg = yield from ws.receive()
                ws.send_str(msg.data + '/answer')
        except:
            pass
        finally:
            print('Connection closed')
        return ws


if __name__ == "__main__":
    app = aiohttp.web.Application()
    app.router.add_route('GET', '/ws', WebsocketEchoHandler())

    loop = asyncio.get_event_loop()
    handler = app.make_handler()

    f = loop.create_server(
        handler,
        '127.0.0.1',
        8080,
    )

    srv = loop.run_until_complete(f)
    print("Server started at {sock[0]}:{sock[1]}".format(
        sock=srv.sockets[0].getsockname()
    ))
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        pass
    finally:
        loop.run_until_complete(handler.finish_connections(1.0))
        srv.close()
        loop.run_until_complete(srv.wait_closed())
        loop.run_until_complete(app.finish())
    loop.close()

问题

现在我想使用下面描述的结构(节点服务器 = python aiohttp)。更具体地说,使用Redis Pub/Sub 机制和asyncio-redis 在我的WebsocketEchoHandler 中读取和写入websocket 连接和Redis。

WebsocketEchoHandler 是一个简单的死循环,所以我不确定应该如何完成。使用Tornadobrükva 我只会使用回调。

额外的(也许是离题的)问题

由于我已经在使用Redis,我应该采取两种方法中的哪一种:

  1. 就像在“经典”网络应用中一样,对所有内容都有一个控制器/视图,使用 Redis 仅用于消息传递等。
  2. Web 应用程序应该只是客户端和Redis 之间的一层,也用作任务队列(最简单的Python RQ)。每个请求都应委托给工作人员。

编辑

图片来自http://goldfirestudios.com/blog/136/Horizontally-Scaling-Node.js-and-WebSockets-with-Redis

编辑 2

看来我需要澄清一下。

  • 上面显示了仅限 Websocket 的处理程序
  • Redis Pub/Sub 处理程序可能如下所示:

    class WebsocketEchoHandler:
    
        @asyncio.coroutine
        def __call__(self, request):
            ws = web.WebSocketResponse()
            ws.start(request)
    
            connection = yield from asyncio_redis.Connection.create(host='127.0.0.1', port=6379)
            subscriber = yield from connection.start_subscribe()
            yield from subscriber.subscribe(['ch1', 'ch2'])
    
            print('Connection opened')
            try:
                while True:
                    msg = yield from subscriber.next_published()
                    ws.send_str(msg.value + '/answer')
            except:
                pass
            finally:
                print('Connection closed')
            return ws
    

    这个处理程序只是订阅 Redis 通道 ch1ch2 并将每个从这些通道接收到的消息发送到 websocket。

  • 我想要这个处理程序:

    class WebsocketEchoHandler:
    
        @asyncio.coroutine
        def __call__(self, request):
            ws = web.WebSocketResponse()
            ws.start(request)
    
            connection = yield from asyncio_redis.Connection.create(host='127.0.0.1', port=6379)
            subscriber = yield from connection.start_subscribe()
            yield from subscriber.subscribe(['ch1', 'ch2'])
    
            print('Connection opened')
            try:
                while True:
                    # If message recived from redis OR from websocket
                    msg_ws = yield from ws.receive()
                    msg_redis = yield from subscriber.next_published()
                    if msg_ws:
                        # push to redis / do something else
                        self.on_msg_from_ws(msg_ws)
                    if msg_redis:
                        self.on_msg_from_redis(msg_redis)
            except:
                pass
            finally:
                print('Connection closed')
            return ws
    

    但以下代码总是按顺序调用,因此从 websocket 读取会阻止从 Redis 读取:

    msg_ws = yield from ws.receive()
    msg_redis = yield from subscriber.next_published()
    

我希望在 事件 上完成阅读,其中 事件 是从两个来源之一收到的消息。

【问题讨论】:

  • 对不起,我不关注。这正是你的问题?
  • @AndrewSvetlov 已更新。

标签: python redis python-asyncio aiohttp


【解决方案1】:

您应该使用两个while 循环 - 一个处理来自 websocket 的消息,另一个处理来自 redis 的消息。您的主处理程序可以启动两个协程,一个处理每个循环,然后等待它们两个

class WebsocketEchoHandler:
    @asyncio.coroutine
    def __call__(self, request):
        ws = web.WebSocketResponse()
        ws.start(request)

        connection = yield from asyncio_redis.Connection.create(host='127.0.0.1', port=6379)
        subscriber = yield from connection.start_subscribe()
        yield from subscriber.subscribe(['ch1', 'ch2'])

        print('Connection opened')
        try:
            # Kick off both coroutines in parallel, and then block
            # until both are completed.
            yield from asyncio.gather(self.handle_ws(ws), self.handle_redis(subscriber))
        except Exception as e:  # Don't do except: pass
            import traceback
            traceback.print_exc()
        finally:
            print('Connection closed')
        return ws

    @asyncio.coroutine
    def handle_ws(self, ws):
        while True:
            msg_ws = yield from ws.receive()
            if msg_ws:
                self.on_msg_from_ws(msg_ws)

    @asyncio.coroutine
    def handle_redis(self, subscriber):
        while True:
            msg_redis = yield from subscriber.next_published()
            if msg_redis:
                self.on_msg_from_redis(msg_redis)

通过这种方式,您可以从两个潜在来源中的任何一个进行阅读,而不必关心另一个。

【讨论】:

  • 谢谢@dano,这正是我想要的。我一直在玩 asyncio.gather 但将它放在一个循环和其他混乱中......
  • 感谢分享。在我看到这里之前,我不知道如何通过 websocket 从服务器端向客户端发送消息。我可以在 redis-cli 中“发布 ch1 ”并在客户端接收它。很好的问答。
  • @dano 我在这里看不到两个 循环。你能澄清一下吗?
  • @EugeneNaydenov handle_redishandle_ws 都包含 while True: 循环。
  • @EugeneNaydenov 啊,我明白你的困惑。我已经编辑了我的答案以消除歧义。
【解决方案2】:

最近我们可以在 python 3.5 及更高版本中使用异步等待..

async def task1(ws):
    async for msg in ws:
        if msg.type == WSMsgType.TEXT:
            data = msg.data
            print(data)
            if data:
                await ws.send_str('pong')
## ch is a redis channel
async def task2(ch):
    async for msg in ch1.iter(encoding="utf-8", decoder=json.loads):
        print("receving", msg)
        user_token = msg['token']
        if user_token in r_cons.keys():
            _ws = r_cons[user_token]
            await  _ws.send_json(msg)

coroutines = list()
coroutines.append(task1(ws))
coroutines.append(task2(ch1))

await asyncio.gather(*coroutines)

这就是我要做的。当 websockets 需要等待来自多源的消息时。

这里的要点是使用 asyncio.gather 一起运行两个 corotine 就像 提到了@dano。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-12
    • 2011-10-17
    • 1970-01-01
    • 2022-10-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多