web-dev-qa-db-ja.com

Python 3でのasyncioおよびwebsocketの使用における長い遅延

WebSocketサーバーからclient.pyにプッシュされたデータを処理する際に、長い(3時間)遅延(編集:最初は短時間であり、その後1日を通して長くなる)が発生しています。私はそれがサーバーによって遅れないことを知っています。

たとえば、5秒ごとにkeep_aliveログイベントとそのそれぞれのタイムスタンプが表示されます。そのため、スムーズに実行されます。 しかし、ログで処理されたデータフレームが実際に表示されるのは、サーバーが送信してから実際に3時間です私はこのプロセスを遅らせるために何かをしていますか?

コルーチンを「keep_alive」と正しく呼んでいますか? keep_aliveは、接続を維持するためのサーバーへの単なるメッセージです。サーバーはメッセージをエコーバックします。また、あまりにも多くのログを記録していますか?処理が遅れているのでしょうか(ログイベントがすぐに発生しているので、そうは思いません)。

async def keep_alive(websocket):
                """
                 This only needs to happen every 30 minutes. I currently have it set to every 5 seconds.
                """
                await websocket.send('Hello')   
                await asyncio.sleep(5)

async def open_connection_test():
    """
    Establishes web socket (WSS). Receives data and then stores in csv.
    """
    async with websockets.connect( 
            'wss://{}:{}@localhost.urlname.com/ws'.format(user,pswd), ssl=True, ) as websocket:
        while True:    
            """
            Handle message from server.
            """
            message = await websocket.recv()
            if message.isdigit():
                # now = datetime.datetime.now()
                rotating_logger.info ('Keep alive message: {}'.format(str(message)))
            else:
                jasonified_message = json.loads(message)
                for key in jasonified_message:
                    rotating_logger.info ('{}: \n\t{}\n'.format(key,jasonified_message[key]))    
                """
                Store in a csv file.
                """
                try:            
                    convert_and_store(jasonified_message)
                except PermissionError:
                    convert_and_store(jasonified_message, divert = True)                        
            """
            Keep connection alive.
            """            
            await keep_alive(websocket)

"""
Logs any exceptions in logs file.
"""
try:
    asyncio.get_event_loop().run_until_complete(open_connection())
except Exception as e:
    rotating_logger.info (e)

編集: ドキュメント から-これは何か関係があるのではないかと思っています-ドットを接続していません。

Max_queueパラメータは、着信メッセージを保持するキューの最大長を設定します。デフォルト値は32です。0は制限を無効にします。メッセージは、受信されるとメモリ内のキューに追加されます。次に、recv()がそのキューからポップします。メッセージが処理されるよりも速く受信されるときに過度のメモリ消費を防ぐには、キューを制限する必要があります。キューがいっぱいになると、プロトコルはrecv()が呼び出されるまで着信データの処理を停止します。この状況では、さまざまな受信バッファー(少なくともasyncioとOSで)がいっぱいになると、TCP受信ウィンドウが縮小し、パケット損失を回避するために送信速度が低下します。

編集9/28/2018:キープアライブメッセージなしでテストしていますが、それは問題ではないようです。それは、convert_and_store()関数に関連している可能性がありますか?これは非同期のdefである必要があり、同様に待機する必要がありますか?

def convert_and_store(data, divert = False, test = False):
    if test:
        data = b
    fields = data.keys()
    file_name =  parse_call_type(data, divert = divert)
    json_to_csv(data, file_name, fields)

EDIT 10/1/2018:keep-aliveメッセージとconvert_and_storeの両方が問題になっているようです。キープアライブメッセージを60秒に延長すると、convert_and_storeは60秒ごとに1回だけ実行されます。したがって、convert_and_storeはkeep_alive()を待機しています...

9
Liam Hanninen

それは、convert_and_store()関数に関連していますか?

はい、可能です。ブロッキングコードを直接呼び出すことはできません。関数がCPUを集中的に使用する計算を1秒間実行すると、すべてのasyncioタスクとIO操作が1秒遅れます。

エグゼキューターを使用して、別のスレッド/プロセスでブロッキングコードを実行できます。

import asyncio
import concurrent.futures
import time

def long_runned_job(x):
    time.sleep(2)
    print("Done ", x)

async def test():
    loop = asyncio.get_event_loop()
    with concurrent.futures.ProcessPoolExecutor() as pool:
        for i in range(5):
            loop.run_in_executor(pool, long_runned_job, i)
            print(i, " is runned")
            await asyncio.sleep(0.5)
loop = asyncio.get_event_loop()
loop.run_until_complete(test())

あなたの場合、それは次のようになります:

import concurrent.futures

async def open_connection_test():
    loop = asyncio.get_event_loop()
    with concurrent.futures.ProcessPoolExecutor() as pool:
        async with websockets.connect(...) as websocket:
            while True:    
                ...
                loop.run_in_executor(pool, convert_and_store, args)

[〜#〜]編集済み[〜#〜]

キープアライブメッセージとconvert_and_storeの両方に問題があるようです

keep_aliveバックグラウンド:

async def keep_alive(ws):
    while ws.open:
        await ws.ping(...)   
        await asyncio.sleep(...)

async with websockets.connect(...) as websocket:
    asyncio.ensure_future(keep_alive(websocket))
    while True:    
        ...
3

あなた必須確かにこのkeep_alive()関数の新しいスレッドを開始します。

_async-await_の場合、次のステップに進む前にすべてのタスクが完了していることを約束します。

したがって、await keep_alive(websocket)は、この意味で実際にスレッドをブロックします。ここで_keep_alive_を待たずにプロセスを続行できますが、確かにそれは望んでいることではありません。

実際に必要なのは、サーバーとの通信用とサーバーの存続用の2つの時間枠です。それらは異なるコルーチンにあるため、分離する必要があります。

したがって、正しい方法はThreadを使用することであり、この場合はasyncioを使用する必要はありません。単純にしてください。

まず、keep_alive()を以下のように変更します。

_def keep_alive():
    """
        This only needs to happen every 30 minutes. I currently have it set to every 5 seconds.
    """
    while True:
        websocket.send('Hello') 
        time.sleep(1)
_

open_connection_test()

_async def open_connection_test():
    """
    Establishes web socket (WSS). Receives data and then stores in csv.
    """
    thread = threading.Thread(target=keep_alive, args=())
    thread.daemon = True   # Daemonize
    thread.start()
    async with websockets.connect(...) as websocket:
        ....
        #No need this line anymore.
        #await keep_alive(websocket) 
_
1
MT-FreeHongKong

これはより明確になると思います。ThreadPoolExecutorを使用して、ブロックコードをバックグラウンドで実行します

from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(max_workers=4)

def convert_and_store(data, divert=False, test=False):
    loop = asyncio.get_event_loop()
    loop.run_in_executor(pool, _convert_and_store, divert, test)


def _convert_and_store(data, divert=False, test=False):
    if test:
        data = b
    fields = data.keys()
    file_name = parse_call_type(data, divert=divert)
    json_to_csv(data, file_name, fields)

asyncio send keep alive msg demo

async def kepp_alive(websocket):
    while True:
        await websocket.send_str(ping)
        await asyncio.sleep(10)
0
Kr.98