앞에서 celery를 활용한 함수적 비동기/ Crontab 처리를 이해했다면, 이번에는 channels을 활용한 Web Socket 의 구현 을 하는 기능으로, 대표적인 예제로써 Chatbot 을 생각하면 된다.

서버에서 작업가능한 상황들로써

  1. AJAX : DB 와 temlplate 간의 연결
  2. Celery : 함수의 비동기적 처리
  3. Channels : 별도의 Web Socket 구현

의 기본개념과, Source Code 를 탄탄하게 익혀둔다면 어떠한 부가적인 내용에 대해서도 유연하게 해결책을 도출 가능 할 것이다.


Channels 의 내용

여기서는 Django2.0 이상, Python 3.6 이상, Channels 2.1.2 이상의 최신버젼(2018년 9월 기준)을 대상으로 작업을 진행할 것으로써, YouTube 를 참고하면 더 자세하게 알 수 있다. 추가 내용은 Document 를 통해서 Python 소스를 추가하면 된다


1 channels 설치 및 Django 적용

$ pip install channels

# settings.py
INSTALLED_APPS = [ 'channels', ]

위의 작업을 마치고 DB와 연동 가능한 ‘makemigrations’, ‘migrate’ 작업을 진행한 뒤 $ python manage.py runserver 를 실행하면

CommandError: You have not set ASGI_APPLICATION, which is needed to run the server.

와 같은 오류를 출력함을 알 수 있다. (여기까지도 정상이다!!)


2 ASGI_APPLICATION

웹소켓을 정의 하기만 했지, 이를 활용할 router를 지정하지 않은 문제로,

  1. settings.py 와 같은 폴더에 routing.py를 추가
  2. settings.py 에 위의 추가한 내용을 연결한다
# /server/routing.py

from channels.routing import ProtocolTypeRouter

application = ProtocolTypeRouter({
    # Empty for now (http->django views is added by default)
})
# /server/settings.py
ASGI_APPLICATION = "server.routing.application"

위 두가지 작업을 $ python manage.py runserver 를 실행하면 서버가 원활하게 작동하는 걸 볼 수 있다


3 ASGI_APPLICATION 출처

ASGI(Asynchronous Server Gateway Interface)WSGI 를 계승한 것으로 비동기 방식으로 실행된다.

웹 서버와 python 응용프로그램 간의 표준 인터페이스를 제공하기 위해 Django Channels와 배포에 사용되는 Daphne 서버에서 정의한 사양으로서 HTTP, HTTP/2 및 WebSocket와 같은 프로토콜을 지원한다.

ASGI는 비동기 요청인 웹 소켓을 처리하는 이벤트로서 connect, send, receive, disconnect가 있다.

Django 기본으로 설정된 WSGI를 비활성화 하고 ASGI 를 추가한다.

#server/asgi.py
import os, django
from channels.routing import get_default_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")
django.setup()
application = get_default_application()

그리고 settings.py 에서는 WGI 설정을 비활성화 하여 불필요한 작업을 제한한다

# server/settings.py

ROOT_URLCONF = 'cfehome.urls'
ASGI_APPLICATION = "cfehome.routing.application"
# WSGI_APPLICATION = 'cfehome.wsgi.application'


Chat Application

consumers.py

웹소켓을 통해서 채팅하는 객체를 정의한다

# chat/consumers.py

import asyncio, json
from django.contrib.auth import get_user_model
from channels.consumer import AsyncConsumer
from channels.db import database_sync_to_async

from .models import Thread, ChatMessage

class ChatConsumer(AsyncConsumer):
    def websocket_connect(self, event):
        print("connected", event)
Django Celery 구조도


redis 서버설치

메모리 기반의 서버로 간단하게 바로 설치된다

$ sudo apt-get install redis-server
$ redis-cli ping
PONG


Celery 설치

$ pip install 'celery[redis]' 를 사용하면 의존성 모듈을 자동으로 설치한다 pip list로 확인하면 amqp, billiard, celery, kombu, redis, vine 등이 함께 설치된다

$ pip install celery
$ pip install redis
$ pip install django-celery-beat
$ pip install django-celery-results


Django Setting for Celery

Coding for Enterprise
Celery Document

Basic Structure

- Django_Project/
  - manage.py
  - server/
    - __init__.py
    - settings.py
    - urls.py

Django_Project/server/init.py

from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app

__all__ = ('celery_app',)

Django_Project/server/settings.py

INSTALLED_APPS = [
    'django_celery_results',   # Celery 
    'django_celery_beat',
]

# Celery 설정
# CELERY_TIMEZONE 는 django 설정값이 앞에 있어야 한다
CELERY_TIMEZONE          = TIME_ZONE  
CELERY_BROKER_URL        = 'redis://localhost:6379'
CELERY_RESULT_BACKEND    = 'redis://localhost:6379'
CELERY_ACCEPT_CONTENT    = ['application/json']
CELERY_TASK_SERIALIZER   = 'json'
CELERY_RESULT_SERIALIZER = 'json' 


Django_Project/server/celery.py

from __future__ import absolute_import, unicode_literals
import os
from celery import Celery

# `celery` 프로그램을 작동시키기 위한 기본 장고 세팅 값을 정한다.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')

# @app 데코레이션 활성화
app = Celery('server')

# namespace='CELERY'는 모든 셀러리 관련 구성 키를 의미한다.
# 반드시 CELERY라는 접두사로 시작해야 한다.
app.config_from_object('django.conf:settings', namespace='CELERY')

# Django App Config에 등록된 모든 taks 모듈을 불러온다.
app.autodiscover_tasks()

# celery 터미널 실행내용을 정의한다
@app.task(bind=True)
def debug_task(self):
    print('Celery DATABASE Request: {0!r}'.format(self.request))


Celery + Redis 실행 확인하기


연결된 app 에 celery 작업명령을 정의한다

server/app이름/tasks.py

# Create your tasks here
from __future__ import absolute_import, unicode_literals
from celery import shared_task
import random

@shared_task
def add(x, y):
    return x + y

@shared_task
def mul(x, y):
    total = x * (y * random.randint(3, 100))
    return total

@shared_task
def xsum(numbers):
    return sum(numbers)


Redis 서버를 실행하여 tasks.py 및 Celery 연결확인

$ celery -A server worker -l info

별도의 터미널을 열고서 redis 로 실행하는 celery DataBase 를 실행한다. 만약 오류가 발생한다면 1) 파이썬 모듈의 문법오류, 2)RESULT_BACKEND 를 잘못 지정해줬을 가능성 등을 점검한다 작업결과 tasks.py 내의 문법오류시 오류가 발생하는 경우가 제일 많았다


위에서 설정한 tasks.py 내부 함수를 Test 한다

$ python manage.py shell

In []: from chartjs.tasks import add

In []: add(10,3)
Out[]: 13

In []: add.delay(10,3)
Out[]: <AsyncResult: 1d0e3f35-f6ac-4688-b4dd-58cd37defb93>


위의 django-shell 실행 결과들이 Celery redis 에서 출력됨을 볼 수 있다

[2018-07-05 16:37:59,854: INFO/MainProcess] Received task: sum_two_numbers[1d0e3f35-f6ac-4688-b4dd-58cd37defb93]

[2018-07-05 16:37:59,857: INFO/ForkPoolWorker-1] Task sum_two_numbers[1d0e3f35-f6ac-4688-b4dd-58cd37defb93] succeeded in 0.0016897160003281897s: 13


app celery 실행에 CronTab 기능추가


settings.py

참고 사이트

from celery.schedules import crontab

CELERY_BEAT_SCHEDULE = {

    'task-number-one': {
        'task': 'app이름.tasks.실행함수',
        'schedule': crontab(minute=59, hour=23)
    },

    'task-number-two': {
        'task': 'app이름.tasks.실행함수',
        'schedule': crontab(minute=0, hour='*/3,10-19')
    }
}

Crontab schedule 설정

정식 Document

class celery.schedules.crontab(minute=u’’, hour=u’’, day_of_week=u’’, day_of_month=u’’, month_of_year=u’*’, **kwargs)

예제 소스코드
1분 간격 crontab()
15분 간격 crontab(minute=’*/15’)
매일 자정 crontab(minute=0, hour=0)
3배수시간 실행 crontab(minute=0, hour=’*/3’)
3배수시간 실행 crontab(minute=0,hour=’0,3,6,9,12,15,18,21’)
일요일 1분 간격 crontab(day_of_week=’sunday’)
일요일 1분 간격 crontab(minute=’’, hour=’’, day_of_week=’sun’)
목요일과 금요일 between 3-4 am, 5-6 pm, and 10-11 pm 에 10분마다 실행 crontab(minute=’*/10’, hour=’3,17,22’,day_of_week=’thu,fri’)
짝수시간 & 3배수 시간에 실행 crontab(minute=0, hour=’/2,/3’)
5배수시간 실행 crontab(minute=0, hour=’*/5’)
오전8시~오후5시 사이 3배수시간 crontab(minute=0, hour=’*/3,8-17’)
매월 두번째 날 crontab(0, 0, day_of_month=’2’)
짝수 날에 실행 crontab(0, 0, day_of_month=’2-30/3’)
매달 첫째주, 셋째주 crontab(0, 0, day_of_month=’1-7,15-21’)
매년 5월 11일 crontab(0, 0, day_of_month=’11’,month_of_year=’5’)
4분기의 첫달마다 실행 crontab(0, 0, month_of_year=’*/3’)


Redis Server

비동기 처리를 위한 message Q 구조

관련 Blog
redis CRUD
radis django

RabbitMQ 는 Ubuntu 17.04 와 호환성이 좋지 않으므로, redis 설치를 추천한다. 인메모리 성능을 요하므로 sudo 권한으로 설치한다

RedisREmote DIctionary Server의 약자로, Salvatore Sanfilippo라는 이탈리아 해커가 MySQL로 어떤 어플을 개발하다가 느려터졌다고 생각했고, 직접 메모리 기반의 빠른 Redis를 개발하게 되었다는 비하인드 스토리가 있다


redis-server Installation

$ sudo apt-get install redis-server
$ redis-cli
127.0.0.1:6379> 

초간단 메모리 server로 접속하면 6379번 port 로 바로 접속된다.


CRUD 기본 명령어

Create 데이터 저장하기

set key value

127.0.0.1:6379> set django server
OK

Read 데이터 읽기

get key

127.0.0.1:6379> get django
"server"

Update 데이터 수정

127.0.0.1:6379> set django python
OK
127.0.0.1:6379> get django
"python"

Delete 데이터 삭제

del key

127.0.0.1:6379> set redis memserver
OK
127.0.0.1:6379> KEYS *
1) "django"
2) "redis"

127.0.0.1:6379> del redis
(integer) 1
127.0.0.1:6379> KEYS *
1) "django"

리스트 형태로 데이터 저장

리스트 데이터 Create

lpush key value

127.0.0.1:6379> lpush dog mung
(integer) 1

리스트 데이터 Update

lpush key value

127.0.0.1:6379> lpush dog mung
(integer) 2

리스트 데이터 Read (리스트 데이터의 인덱스를 지정)

lrange key startindex endindex

127.0.0.1:6379> lrange dog 0 -1
1) "mung"
2) "mung"

데이터 삭제 Delete (저장된 전체 데이터를 삭제한다)

flushdb

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty list or set)


데이터 유효기간 설정 (유효 시간(초)를 설정)

set key time(sec)

127.0.0.1:6379> set testvalue 100
OK
127.0.0.1:6379> KEYS *
1) "testvalue"
127.0.0.1:6379> setex testvalue 10 testvalue
OK
127.0.0.1:6379> get testvalue
"testvalue"
127.0.0.1:6379> get testvalue   # 10  삭제를 확인
(nil)
127.0.0.1:6379> exit
@ubuntu$