django / channels

Developer-friendly asynchrony for Django

Home Page:https://channels.readthedocs.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

sub redis channel from django channels

zotil opened this issue · comments

commented

I am doing tests with channels and I was looking for information, the most close is #235 issue that have a clue.

I need to subscribe to a redis channel and send to the client by django channels.
I tried creating a manage command but I get much CPU usage, example:

from django.core.management import BaseCommand
import asyncio

import aioredis
import json
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync

class Command(BaseCommand):
    async def handle_msg(self, msg):
        channel = msg[0].decode('utf-8')
        data = msg[1]
        message = {
            'type': 'redis_data',
            'message': data
        }

        asyncio.ensure_future(self.channel_layer.group_send(channel, message))

    async def reader(self, ch):
        while (await ch.wait_message()):
            msg = await ch.get_json()
            asyncio.ensure_future(self.handle_msg(msg))
            await asyncio.sleep(0.001)

    async def main(self):
        sub = await aioredis.create_redis_pool(('redis', 6379), minsize=20, maxsize=30)
        res = await sub.psubscribe('BotManager.*')
        ch1 = res[0]
        tsk = await self.reader(ch1)

    def handle(self, *args, **options):
        print("[+] Init django channels websocket data")
        #asyncio.run(self.listen_redis())
        self.channel_layer = get_channel_layer()

        loop = asyncio.get_event_loop()
        loop.run_until_complete(self.main())
        loop.close() 

This is working, but when a clients connect to the consumer, the CPU usage from django goes high.
Here is my consumer:

import json
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from apps.bot.models import Bot

class BotConsumer(AsyncWebsocketConsumer):
    @database_sync_to_async
    def get_bot(self, bot_id):
        return Bot.objects.get(pk=bot_id)

    async def connect(self):
        self.user = self.scope['user']

        # Only authenticated users
        if self.user.id:
            bot_id = self.scope['url_route']['kwargs']['bot_id']

            # Get bot info from DB
            bot = await self.get_bot(bot_id)
            # Generate redis channel
            exchange_name = bot.exchange
            symbol = bot.symbol.replace('/', '')
            redis_channel = f"BotManager.{exchange_name}.{symbol}"

            # Add group with redis channel and django channel
            self.room_group_name = redis_channel
            await self.channel_layer.group_add(self.room_group_name,
                                               self.channel_name)

            await self.accept()
    
    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(self.room_group_name,
                                               self.channel_name)

    async def redis_data(self, event):
        events_allowed = (
            'aggtrade',
            'ticker',
        )
        msg = event['message']
        if msg.get('e'):
            event = msg['e']
            if event in events_allowed:
                await self.send(text_data=json.dumps(msg))

Is there a better way to do this?

The idea is to have the client connected to the django channel websocket and read the data that I have on the redis pubsub.

Thank you in advance.

aioredis==1.3.1
asgiref==3.3.4
channels==3.0.4
channels-redis==3.3.1
daphne==3.0.2
Django==3.2.12
redis==4.1.1