본문 바로가기

프로그래밍/Django

[Django] Django + Celery + Redis 이용하기

Redis_Celery

들어가기 전에

동기 vs 비동기

스크린샷, 2017-07-15 12-55-16
(이미치 출처 : http://ojava.tistory.com/17)

어떤 서비스에 회원가입을 하면 메일이 날라오는 경우가 있다.

  1. 동기 : 메일이 날라올 때까지 아무것도 하지 못한다.

  2. 비동기 : 메일이 날라오지 않더라도 다른 작업을 할 수 있다.

비동기 방식이 짱짱맨 아니냐 하겠지만 어떤 방식이건 간에 장단점이 존재한다. 아무튼, 회원가입 축하 이메일 발송은 사실 고객(client)에게 중요한 부분이 아니다. 일단 가입을 하고 서비스를 이용하기를 원할 것이다. 이러한 이메일 발송은 비동기 처리로 보낼 수 있다. 이를 백그라운드에 보낸다고 표현한다.

Djnago + Celery + Redis

스크린샷, 2017-07-14 20-59-32

Celery는 일꾼이다. 해야 할 일들을 처리한다.
Reids는 주인이다. 메시지 브로커를 담당한다.

Celery란?

Celery는 안 보이는 곳에서 열심히 일하는 (백그라운드)일꾼이다. 처리해야 할 일을 Queue로 쌓아둔다.큐(queue)에 쌓인 일을 일꾼들이 가져다가 열심히 일을한다. 파이썬 언어로 작성되어 있다.

Redis란?

Redis는 실제 컴퓨터 메모리를 이용한 캐쉬다. KeyValue값을 이용해 처리할 작업을 Celery에게 보낸 다음 캐쉬 시스템에서 해당 키를 없애는 방식으로 동작한다.

좋은 점은 로컬과 DB사이에서 자료가 왔다갔다 하는 것보다 메모리에서 캐쉬를 가져다 쓰는 것이 훨씬 빠르다는 것이다. 따라서 특정 데이터를 반복적으로 돌려줘야 한다면 메모리 캐쉬를 사용하면 좋다.

설치할 때 주의점

Celery는 파이썬 언어로 작성되어 있기 때문에 가상환경 위에서 pip를 이용해 설치한다.
Redis는 인 메모리를 이용한다고 했었다. 따라서 wget으로 설치한다.

1. 설치하기

1-1 Celery 설치하기

pip install 'celery[redis]'

pip를 이용해 cerlry 모듈과 redis와의 연동을 위한 dependency를 한 번에 설치한다. redis를 설치하지 않아도 일단 의존성 패키지는 설치 된다. 작은 따옴표('')를 꼭 붙여줘야 설치가 된다.
스크린샷, 2017-07-15 14-08-04
설치를 한뒤 pip list로 확인하면 amqp, billiard, celery, kombu, redis, vine 등이 함꼐 설치된다(나도 처음 알았다).

Celery는 수행할 작업(task) 및 실행을 의뢰받을 브로커(AMQP, Redis)를 정의한다.

1-2 Redis 설치하기

$ wget http://download.redis.io/redis-stable.tar.gz
$ tar xvzf redis-stable.tar.gz
cd redis-stable
$ make
$ redis-server # redis 실행
$ redis-cli ping # 정상 설치되었는지 확인
> PONG

PONG 메시지를 띄우면 설치 성공이다.

2. Django와 연동하기


디렉토리 구조
스크린샷, 2017-07-15 14-04-58

프로젝트 루트 폴더(project root folder) : celery (장고 프로젝트를 담는 최상위 폴더)
프로젝트 폴더(project folder) : django_app (여러 앱들로 구성되는 장고 프로젝트 폴더)
프로젝트 환경설정 폴더(project settings folder) : config


[프로젝트 폴더] / [환경설정 폴더] / __init__.py

from .tasks import app as celery_app
 
__all__ = ['celery_app']

[프로젝트 폴더] / [환경설정 폴더] / settings.py

BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'

[프로젝트 폴더] / [환경설정 폴더] / tasks.py(파일 추가)

import os
from celery import Celery
 
# `celery` 프로그램을 작동시키기 위한 기본 장고 세팅 값을 정한다. 
os.environ.setdefault('DJANGO_SETTINGS_MODULE''config.settings')
 
app = Celery('config')
 
# namespace='CELERY'는 모든 셀러리 관련 구성 키를 의미한다. 반드시 CELERY라는 접두사로 시작해야 한다. 
app.config_from_object('django.conf:settings'namespace='CELERY')
 
# 장고 app config에 등록된 모든 taks 모듈을 불러온다. 
app.autodiscover_tasks()
 
@app.task
def add(x, y):
    return x + y

3. Celery + Redis 실행시켜보기

실행하기
주의점 : 프로젝트 폴더에 진입한 뒤! 쉘에서 다음 명령을 입력해야 한다.

celery -A   [파일이름]   worker --loglevel=info
cerlry -A    config    worker --loglevel=info

실행 결과

(django_ev) ➜  django_app celery -A config worker --loglevel=info
 
 -------------- celery@chulgyoo-15ZD960-GX30K v4.0.2 (latentcall)
---- **** -----
--- * ***  * -- Linux-4.8.0-58-generic-x86_64-with-debian-stretch-sid 2017-07-15 05:53:49
-- * - **** ---
** ---------- [config]
** ---------- .> app:         config:0x7f5380456358
** ---------- .> transport:   redis://localhost:6379//
** ---------- .> results:     redis://localhost:6379/
*** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery
 
[tasks]
  . config.tasks.add # [tasks]를 통해서 비동기로 작업 할 수 있는 목록들이 나온다. 현재는 add 하나.
 
[2017-07-15 05:53:50,139: INFO/MainProcess] Connected to redis://localhost:6379//
[2017-07-15 05:53:50,149: INFO/MainProcess] mingle: searching for neighbors
[2017-07-15 05:53:51,172: INFO/MainProcess] mingle: all alone
[2017-07-15 05:53:51,214: WARNING/MainProcess] /home/chulgyoo/.pyenv/versions/3.6.1/envs/django_ev/lib/python3.6/site-packages/celery/fixups/django.py:202: UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments!
  warnings.warn('Using settings.DEBUG leads to a memory leak, never')
[2017-07-15 05:53:51,215: INFO/MainProcess] celery@chulgyoo-15ZD960-GX30K ready.

Debug(로컬)에서 실행하면 메모리

오류가 발생한다면

  1. Cannot connet 문제
    스크린샷, 2017-07-15 15-31-18
    BROKER_URL 또는 RESULT_BACKEND를 잘못 지정해줬을 가능성이 있다.

  2. No moule named '[모듈이름]'

    • 모듈 이름을 잘못 임포트 했다. 프로젝트 폴더 안에서 명령어를 실행한게 맞는지, 내가 만든 파일 이름이 맞는지 확인해야 한다.

4. Celery를 이용한 작업처리하기

작업을 실행시켜보자
스크린샷, 2017-07-15 14-58-07
Celery를 실행시킨 상태에서 터미널을 하나 더 켠 뒤, 작업을 진행해보자. 위와 같이 있으면 된다.

python shell
 
>>> from config.tasks import add
>>> result = add.delay(44)
>>> result = add.delay(44)
>>> result = add.delay(44)
>>> result = add.delay(44)
>>> result = add.delay(44)

5번을 실행시킨 뒤, Celery Shell을 확인해보면 Received task라는 메시지와 함께 작업시간을 확인할 수 있다. 간단한 계산이기 때문에 많은 시간이 걸리지 않는다.
스크린샷, 2017-07-15 15-01-39

5. Celery WorkFlow

  1. @app.task로 처리하고 싶은 일에 딱지를 붙인다.

  2. add 작업에 delay를 붙이면 Redis Backend에 기록이 저장된다.

  3. RedisCelery에게 일을 준다.

  4. 일을 받은 Celeryadd 작업을 시작한다.

Redis

Redis는 아무래도 In-memory를 사용하는 것이 포인트다.
Celery는 파이썬 언어로 건드려볼 수 있지만, Redis는 간단한 편(?)이다.

설치

$ wget http://download.redis.io/redis-stable.tar.gz
$ tar xvzf redis-stable.tar.gz
cd redis-stable
$ make

서버 시작

$ redis-server

서버 확인

$ redis-cli
 
127.0.0.1:6379 > ping
PONG

get, set 명령어 실행
서두에서 keyvalue값으로 작업을 Celery에게 보낸다고 언급했었다.

127.0.0.1:6379 > set mykey "myvalue"
OK
127.0.0.1:6379 > get mykey
"myvalue"

서버 정지

$ redis-cli shutdown

참고한 글

  1. Redis 설치하기
    http://www.tutorialbook.co.kr/entry/Ubuntu-Redis-%EC%B5%9C%EC%8B%A0-stable-%EB%B2%84%EC%A0%84-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0

  2. Celery + Redis
    http://dgkim5360.tistory.com/entry/python-celery-asynchronous-system-with-redis

    • Celery + Redis 연동에 관한 전반적인 정보를 얻었습니다.

  • dk 2019.02.08 13:09

    settings에서 브로커 url 지정시 CELERY_BROKER_URL 으로 지정해주어야 정상적으로 동작하네요.
    질문 있습니다.
    django, celery, redis로 스케쥴러 제작시 각각 서버, 즉 3개의 서버를 돌려야 하는건가요?

  • 행인 2019.03.18 12:46

    정보 감사합니다.
    커맨드 중 오타가 있습니다
    $ cerlry -A config worker --loglevel=info
    -> $ celery
    수정하시면 더 많은 사람이 사용할 수 있을 것 같습니다.

  • 님님 2019.07.24 11:31

    글 잘읽고 갑니다.

    좋은 점은 로컬과 DB사이에서 자료가 왔다갔다 하는 것보다 메모리에서 캐쉬를 가져다 쓰는 것이 훨씬 빠르다는 것이다. 따라서 특정 데이터를 반복적으로 돌려줘야 한다면 메모리 캐쉬를 사용하면 좋다

    여기가 조금 궁금한 것이 있는데 캐쉬에서 가져다 쓰는것이 key value 형태의 task목록 인가요?
    혹은 task에서 쓰이는 data들 자체를 캐싱해 놓고 쓴다는 것인가요?

    만약 key value형태라면... data양이 그렇게 크지 않은것 같은데
    궂이 이것을 db에 안담고 캐시에 담는 이유가 뭔가요?

    속도차이가 현저하게 나기 때문인가요?

    혹시 이게 task같은 경우에는 먼저 처리 요청을 받은 순서가 중요해서

    그 순서를 꼬이지 않고 최대한 즉각적으로 맞춰서 key value형태로 저장하기 위해 캐쉬 를 쓰는것 인가요?


    두서없이 질문을 남겨봅니다...

    감사합니다.

  • 님님 2019.07.24 11:52

    그리고 조금 했갈리는게 하나더 있는데요....

    `Celery는 안 보이는 곳에서 열심히 일하는 (백그라운드)일꾼이다. 처리해야 할 일을 Queue로 쌓아둔다.큐(queue)에 쌓인 일을 일꾼들이 가져다가 열심히 일을한다. `

    말씀해 주신것 관련해서 찾아봤는데
    celery = Distributed Task Queue (분산작업 대기열) 이렇게 정의되어 있더라구요

    제공해주신 이미지를 보면

    장고 -> redis - > celery -> 이렇게 task가 전달되는데

    redis도 결국 django로 부터 task의 목록을 key value형태로 저장하고 있고
    celery도 분산 작업 대기열 답게 처리할 목록을 갖고 있으면...

    궂이 왜? 대기열을 두개나 가지고 있는것인가요?

    그냥 celrey입장에서는 redis의 key, value만 보고 각각의 celery worker에 할당해 주면 되는것 아닌가요?
    왜 궂이 celery자체의 대기열을 따로 두는지 이해가 안됩니다...

    혹시 아시는 부분이라면.. 조금 알려주시면 감사하겠습니다.

    • 행인 2019.10.29 14:37

      `Celery는 안 보이는 곳에서 열심히 일하는 (백그라운드)일꾼이다. 처리해야 할 일을 Queue로 쌓아둔다.큐(queue)에 쌓인 일을 일꾼들이 가져다가 열심히 일을한다. `

      여기에 있는 queue역할을 하는 게 Redis에요.
      대기열을 두개나 가지고 있는게 아니라