본문 바로가기
개발 공부/Django

django channels - Consumer

by 느림보어른 2021. 7. 18.

Consumer

Channels은 ASGI라는 기본 low-level 사양을 기반으로 구축되었지만 복잡한 애플리케이션을 작성하는 것보다 상호 운용성을 위해 설계되었습니다. 따라서 Channels은 ASGI 애플리케이션을 쉽게 만들 수 있는 풍부한 추상화인 Consumer를 제공합니다.

 

Consumer은 django의 view에 대응한다. app에 연결된 모든 사용자는 "사용자" group에 추가되고 서버에서 보내는 message를 받게 된다. 클라이언트가 app에서 연결이 끊기면 해당 channel이 group에서 제거되고 사용자는 message 수신을 중단한다.

Consumer은 특히 다음과 같은 몇 가지 일을 합니다.

  • 이벤트 루프를 작성하도록 만드는 대신 이벤트가 발생할 때마다 호출할 일련의 함수로 코드를 구성합니다.
  • 동기식 또는 비동기식 코드를 작성하고 handoffs(?) 및 threading을 처리할 수 있습니다.

물론, Consumer를 무시하고 라우팅, 세션 처리, 인증과 같은 Channels의 다른 부분을 ASGI 앱과 함께 사용하는 것은 자유롭지만 일반적으로 애플리케이션 코드를 작성하는 가장 좋은 방법입니다.

(위의 설명은 아직 잘 이해하지 못했지만 현재까지 이해한 바로는 Consumer를 사용하지 않은 채 Channels를 이용할 수 있지만 Consumer를 사용하는 편이 더 효율적인 코드 작성법이라고 언급하는 것 같음)

AsyncConsumer VS SyncConsumer

django channels 공식문서에서는 기본적으로 Sync Consumer를 쓰는 것을 추천한다.

비동기 처리를 통해 개선되는 작업을 수행하고 있고 async-native libraries만 사용하는 경우에만 Async Consumer를 사용하는 것이 좋다고 한다.

Closing Consumers

Consumer에 연결된 소켓이나 연결이 닫히면(예: http.disconnect 또는 websocket.disconnect) 이벤트가 전송되고 애플리케이션 인스턴스에 해당 이벤트가 발생할 짧은 시간이 주어집니다.

 

연결 해제 후 정리를 마쳤으면 ASGI 응용 프로그램을 완전히 중지하고 서버가 정리하도록 channels.exceptions.StopConsumer을 발생시켜야 합니다.

이 예외가 발생하지 않도록 실행 상태로 두면 서버가 애플리케이션 종료 시간제한(기본적으로 Daphne에서는 10초)에 도달한 다음 애플리케이션을 중지하고 경고를 표시합니다.

 

generic consumer가대신 위 작업을 수행하므로 AsyncConsumer 또는 SyncConsumer를 기반으로 사용자 고유의 Consumer 클래스를 작성하는 경우에만 필요합니다. 그러나 __call__ 메서드를 override 하거나 호출된 처리 메서드가 반환되지 않도록 차단한 경우에도 이러한 문제가 발생할 수 있습니다.

 

또한 사용자 고유의 background coroutines을 시작하는 경우 연결이 완료될 때 해당 루틴을 종료해야 합니다. 그렇지 않으면 코루틴이 서버로 유출됩니다.

(추가) coroutine이란?

코루틴(coroutine)은 루틴의 일종으로서, 협동 루틴이라 할 수 있다(코루틴의 "Co"는 with 또는 together를 뜻한다). 상호 연계 프로그램을 일컫는다고도 표현 가능하다. 루틴과 서브 루틴은 서로 비대칭적인 관계이지만, 코루틴들은 완전히 대칭적인, 즉 서로가 서로를 호출하는 관계이다. 코루틴들에서는 무엇이 무엇의 서브루틴인지를 구분하는 것이 불가능하다. 코루틴 A와 B가 있다고 할 때, A를 프로그래밍할 때는 B를 A의 서브루틴으로 생각한다. 그러나 B를 프로그래밍할 때는 A가 B의 서브루틴이라고 생각한다. 어떠한 코루틴이 발동될 때마다 해당 코루틴은 이전에 자신의 실행이 마지막으로 중단되었던 지점 다음의 장소에서 실행을 재개한다.

 

쉽게 말해 필요에 따라 일시 정지할 수 있는 함수를 말한다.

코루틴을 사용하여 I/O 처리를 극대화할 수 있는데, 이는 단순히 대기하는 작업을 기다리는 동안 다른 작업을 먼저 처리함으로써 CPU의 유휴 시간(Idle time)을 최소화할 수 있기 때문이다. 코루틴은 멀티스레드를 대체하기 위해 등장한 것은 아니다.

파이썬 코루틴 예제

async/await 문법으로 선언된 코루틴이 asyncio 응용 프로그램을 작성하는 기본 방법입니다. 예를 들어, 다음 코드(파이썬 3.7 이상 필요)는 《hello》를 인쇄하고, 1초 동안 기다린 다음, 《world》를 인쇄합니다:

>>> import asyncio

>>> async def main():
...     print('hello')
...     await asyncio.sleep(1)
...     print('world')

>>> asyncio.run(main())
hello
world
>>> main()
<coroutine object main at 0x1053bb7c8>

코루틴을 실제로 실행하기 위해, asyncio가 세 가지 주요 메커니즘을 제공합니다:

 

  • 최상위 진입점 《main()》 함수를 실행하는 asyncio.run() 함수 (위의 예를 보세요.)
  • 코루틴을 기다리기. 다음 코드는 1초를 기다린 후 《hello》를 인쇄한 다음 또 2초를 기다린 후 《world》를 인쇄합니다:
    import asyncio
    import time
    
    async def say_after(delay, what):
        await asyncio.sleep(delay)
        print(what)
    
    async def main():
        print(f"started at {time.strftime('%X')}")
    
        await say_after(1, 'hello')
        await say_after(2, 'world')
    
        print(f"finished at {time.strftime('%X')}")
    
    asyncio.run(main())​
    예상 출력:
    started at 17:13:52
    hello
    world
    finished at 17:13:55
  • 코루틴을 asyncio 태스크로 동시에 실행하는 asyncio.create_task() 함수. 위의 예를 수정해서 두 개의 say_after 코루틴을 동시에 실행해 봅시다:
    async def main():
        task1 = asyncio.create_task(
            say_after(1, 'hello'))
    
        task2 = asyncio.create_task(
            say_after(2, 'world'))
    
        print(f"started at {time.strftime('%X')}")
    
        # Wait until both tasks are completed (should take
        # around 2 seconds.)
        await task1
        await task2
    
        print(f"finished at {time.strftime('%X')}")
    예상 출력은 이전보다 1초 빠르게 실행되었음을 보여줍니다.
    started at 17:14:32
    hello
    world
    finished at 17:14:34​

Channel Layers

Consumer는 또한 Channel’s channel layers을 처리할 수 있도록 하여 일대일 또는 groups이라고 하는 브로드캐스트 시스템을 통해 서로 간에 메시지를 보낼 수 있도록 합니다.

제공된 Consumer 클래스를 하위분류할 때 channel_layer_alias 특성이 설정되지 않는 한 소비자들은 채널 계층 기본값을 사용합니다.

 

channel layers에 관한 내용은 다른 포스팅에서 더 자세히 포스팅할 예정이다.

Scope

Consumers은 요청을 받을 때 연결 scope를 받게 되는데, 여기에는 Django view의 request 객체에 대한 많은 정보가 포함되어 있습니다. Consumer 메서드 내에서 self.scope로 사용할 수 있습니다.

 

scope는 ASGI 사양의 일부이지만 다음과 같은 몇 가지 일반적인 사항을 사용할 수 있습니다.

  • scope["path"], the path on the request. (HTTP and WebSocket)
  • scope["headers"], raw name/value header pairs from the request (HTTP and WebSocket)
  • scope["method"], the method name used for the request. (HTTP)

Authentication과 같은 기능을 사용하도록 설정하면 사용자 객체에 scope["user"]로 액세스 할 수 있으며, URL 라우터는 예를 들어 URL에서 캡처된 groups을 scope["url_route"]로 이동합니다.

 

일반적으로 scope는 연결 정보를 가져올 수 있는 장소이며 미들웨어가 액세스 할 수 있는 속성을 넣을 위치입니다.(Django의 middleware가 요청할 항목을 추가하는 것과 동일한 방식)

예시

{'type': 'websocket', 
'path': '/ws/play/tuna/', 
'raw_path': b'/ws/play/tuna/', 
'headers': [(b'host', b'127.0.0.1:8000'), 
            (b'connection', b'Upgrade'), 
            (b'pragma', b'no-cache'), 
            (b'cache-control', b'no-cache'), 
            (b'user-agent', b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'), 
            (b'upgrade', b'websocket'), (b'origin', b'http://127.0.0.1:8000'), 
            (b'sec-websocket-version', b'13'), 
            (b'accept-encoding', b'gzip, deflate, br'), 
            (b'accept-language', b'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6'), 
            (b'cookie', b'XSRF-TOKEN=kJmxw1eb3BDglHiEqlev891gCcOmbe9s8qON5Nh7gX5F01VnZ52fdMUvWKipqC6n; csrftoken=RGAY4hPeyqvn4lEkbAvXRPptvpKFhdHQDTtro8A4nKOmYJ9qBfs08nKfw9B86Ozq; username-127-0-0-1-8888="2|1:0|10:1626318657|23:username-127-0-0-1-8888|44:YzY0ZmY2NzdkNzA1NDgwNmEwMzg5MGNjNmE4OGI2MTg=|782934da0b0fe8eab0b76f73effb7412ab6ff65c051603b70d725d661a5a100c"'), 
            (b'sec-websocket-key', b'Yof2ukTeOY8gwJiwz19kbw=='), 
            (b'sec-websocket-extensions', b'permessage-deflate; client_max_window_bits')], 
'query_string': b'', 'client': ['127.0.0.1', 55285], 
'server': ['127.0.0.1', 8000], 
'subprotocols': [], 
'asgi': {'version': '3.0'}, 
'cookies': {'XSRF-TOKEN': 'kJmxw1eb3BDglHiEqlev891gCcOmbe9s8qON5Nh7gX5F01VnZ52fdMUvWKipqC6n', 'csrftoken': 'RGAY4hPeyqvn4lEkbAvXRPptvpKFhdHQDTtro8A4nKOmYJ9qBfs08nKfw9B86Ozq', 'username-127-0-0-1-8888': '2|1:0|10:1626318657|23:username-127-0-0-1-8888|44:YzY0ZmY2NzdkNzA1NDgwNmEwMzg5MGNjNmE4OGI2MTg=|782934da0b0fe8eab0b76f73effb7412ab6ff65c051603b70d725d661a5a100c'}, 
'session': <django.utils.functional.LazyObject object at 0x00000281A73CFC50>, 
'user': <channels.auth.UserLazyObject object at 0x00000281A7368550>, 
'path_remaining': '', 
'url_route': {'args': (), 'kwargs': {'room_code': 'tuna'}}}

출처

https://channels.readthedocs.io/en/stable/topics/consumers.html

 

Consumers — Channels 3.0.4 documentation

Consumers While Channels is built around a basic low-level spec called ASGI, it’s more designed for interoperability than for writing complex applications in. So, Channels provides you with Consumers, a rich abstraction that allows you to make ASGI appli

channels.readthedocs.io

https://ko.wikipedia.org/wiki/%EC%BD%94%EB%A3%A8%ED%8B%B4

 

코루틴 - 위키백과, 우리 모두의 백과사전

코루틴(coroutine)은 루틴의 일종으로서, 협동 루틴이라 할 수 있다(코루틴의 "Co"는 with 또는 togather를 뜻한다). 상호 연계 프로그램을 일컫는다고도 표현가능하다. 루틴과 서브 루틴은 서로 비대칭

ko.wikipedia.org

https://unityindepth.tistory.com/21

 

코루틴(Coroutine)++

목적 만약 코루틴에 대해서 확실하게 모르거나, 원하는 작업을 하고자 할 때 문제가 발생했다면 당신은 반드시 이 튜토리얼을 읽어야 한다. 코루틴은 다음과 같은 특성을 가진다 : 특정 작업을

unityindepth.tistory.com