简体   繁体   English

在另一个消费者的一个Django Channels消费者中断开循环

[英]Break loop in one Django Channels consumer from another consumer

I'm building web-app with Django and Django Channels that utilizes websockets. 我正在使用带有websockets的Django和Django Channels构建web-app。

When user clicks the button in browser, websocket sends data to my server and consumer on the server starts to send messages to the client once per second (in loop). 当用户单击浏览器中的按钮时,websocket将数据发送到我的服务器,服务器上的使用者开始每秒向客户端发送一次消息(循环中)。

I want to create another button that will stop this data sending process. 我想创建另一个按钮来停止此数据发送过程。 When user clicks on this new button, websocket sends another data to server and consumer on the server must somehow stop the loop in previous consumer. 当用户点击这个新按钮时,websocket会向服务器发送另一个数据,服务器上的消费者必须以某种方式停止前一个消费者的循环。 Also I will require this to stop the loop when client disconnects. 此外,我将要求它在客户端断开连接时停止循环。

I felt a desire to use global variable. 我觉得有使用全局变量的愿望。 But Django Channels documentation states that they strongly not recommend to use global variables as they want keep the app network transparent (don't really understand this). 但Django Channels文档指出,他们强烈建议不要使用全局变量,因为他们希望保持应用程序网络透明(不要真正理解这一点)。

I've tried to use channel session. 我试过使用频道会话。 I made second consumer to update value in channel session, but channel session values didn't get updated in first consumer. 我让第二个消费者在频道会话中更新了值,但是第一个消费者没有更新频道会话值。

Here is simplified code. 这是简化的代码。 Browser: 浏览器:

var socket = new WebSocket("ws://" + window.location.host + "/socket/");
$('#button1').on('click', function() { 
    socket.send(JSON.stringify({action: 'start_getting_values'}))
});
$('#button2').on('click', function() { 
    socket.send(JSON.stringify({action: 'stop_getting_values'}))
});

Consumer on server: 服务器消费者:

@channel_session
def ws_message(message):
    text = json.loads(message.content['text'])

    if text['action'] == 'start_getting_values':
        while True:
            # Getting some data here
            # ...
            message.reply_channel.send({"text": some_data}, immediately=True)
            time.sleep(1)

    if text['action'] == 'stop_getting_values':
        do_something_to_stop_the_loop_above()

Well, I managed to solve the task myself after I contacted Django Channels developers. 好吧,在我联系了Django Channels的开发者之后,我设法自己解决了这个问题。

The approach of making loop inside consumer is bad because it will block the site once consumer will be run amount of times equal to amount of threads of all workers running this consumer. 在消费者中制作循环的方法很糟糕,因为一旦消费者运行的次数等于运行该消费者的所有工作人员的线程数量,它将阻止该网站。

So my approach was the following: once consumer gets 'start_getting_values' signal, it adds current reply channel to a group as well as increments value on Redis server it connected to (I use Redis as channel layer backend but it will work on any other backend). 所以我的方法如下:一旦消费者获得'start_getting_values'信号,它会将当前回复通道添加到一个组,并在连接到的Redis服务器上增加值(我使用Redis作为通道层后端,但它可以在任何其他后端工作)。

What value does it increments? 它增加了什么价值? On Redis I have a key say 'groups' of hash object type. 在Redis上,我有一个关键的哈希对象类型'组'。 Each key of this key represents a group in Channels and value represents amount of reply channels in this group. 此键的每个键表示通道中的一个组,值表示该组中的回复通道数量。

Then I created a new python file where I connected to same Redis server. 然后我创建了一个新的python文件,我连接到同一个Redis服务器。 In this file I run infinite loop which loads dict from key 'groups' from Redis. 在这个文件中,我运行无限循环,从Redis的关键'组'加载dict。 Then I loop through each keys in this dict (each key represents Channels groups name) and broadcast the data to each group which have non-zero value. 然后我循环遍历此dict中的每个键(每个键代表通道组名称)并将数据广播到每个具有非零值的组。 When I run this file, it's run as separate process and thus doesn't block anything on consumer side. 当我运行此文件时,它作为单独的进程运行,因此不会阻止消费者端的任何内容。

To stop broadcasting to the user, when I get appropriate signal from him, I just remove him from the group as well as decrement appropriate Redis value. 为了停止向用户广播,当我从他那里得到适当的信号时,我只是将他从组中删除,并减少适当的Redis值。

Consumer code: 消费者代码:

import redis

redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)

@channel_session_user
def ws_message(message):

    text = json.loads(message.content['text'])

    if text['stream'] == 'start_getting_values':
        value_id = text['value_id']
        redis_client.hincrby('redis_some_key', value_id, 1)
        Group(value_id).add(message.reply_channel)
        channel_session['value_id'] = value_id
        return 0

    if text['stream'] == 'stop_getting_values':
        if message.channel_session['value_id'] != '':
            value_id = message.channel_session['value_id']
            Group(value_id).discard(message.reply_channel)

            l = redis_client.lock(name='del_lock')
            val = redis_client.hincrby('redis_some_key', value_id, -1)
            if (val <= 0):
                redis_client.hdel('redis_some_key', value_id)
            l.release()
        return 0

Separate python file: 单独的python文件:

import time
import redis
from threading import Thread
import asgi_redis


redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
cl = asgi_redis.RedisChannelLayer()

def some_action(value_id):

    # getting some data based on value_id
    # ....

    cl.send_group(value_id, {
        "text": some_data,
    })


while True:
    value_ids = redis_client.hgetall('redis_some_key')

    ths = []
    for b_value_id in value_ids.keys():
        value_id = b_value_id.decode("utf-8")
        ths.append(Thread(target=some_action, args=(value_id,)))

    for th in ths:
        th.start()
    for th in ths:
        th.join()


    time.sleep(1)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM