우선 celery는 비동기 작업을 하기 위해 사용하고 있습니다. 이런 비동기 작업이 왜 필요한지 살펴보면 다음과 같습니다.
동기는 말 그대로 동시에 일어난다는 뜻입니다. 요청과 그 결과가 동시에 일어난다는 약속인데, 바로 요청을 하면 시간이 얼마나 걸리던지 요청한 자리에서 결과가 나타나야 합니다.
비동기는 동시에 일어나지 않는다는 의미입니다. 요청한 결과는 동시에 일어나지 않을거라는 말입니다.
동기 방식은 설계가 매우 간단하고 직관적이지만, 결과가 주어질 때까지 아무것도 하지 못하고 대기해야 된다는 단점을 가지고 있습니다.
비동기 방식은 동기보다 복잡하지만, 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있다는 장점을 가지고 있습니다.
Celery는 위에서 얘기했다시피 비동기 작업을 이용하기 위해 사용하려 합니다. 그러기 위해서는 크게 3가지의 구성을 필요로 합니다.
- Client : Task를 생성
- Broker : Task를 Worker에게 전달 (Redis)
- Worker : Task를 실행하고 처리 (Celery)
이 중에 우선 Broker를 살펴보겠습니다.
우선 Broker에는 대표적으로 2개가 있습니다. 어떤 작업을 하고 어떤 것을 중요시 하느냐에 따라 Broker를 선택해주면 됩니다. 이번 포스팅은 Redis를 가지고 설명하겠습니다.
- RabbitMQ - 안정성 👍, 속도 👎
- Redis - 안정성 👎, 속도 👍
아래의 명령어를 가지고 설치해주면 마지막에 ping-pong
이 나오면 성공적으로 설치한 것입니다.
$ 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
python으로 작성된 비동기 작업 큐(Asynchronous task queue/job queue)
Celery는 Redis가 가져다 준 Task를 처리하는 모듈입니다.
pip를 이용해 cerlry 모듈과 redis와의 연동을 위한 dependency를 한 번에 설치해줍니다.
pip install 'celery[redis]'
이후에 Celery 환경설정을 해주면 됩니다.
수정하고 할 것도 없습니다. 일단 적어주세요.
# config/settings.py
CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Seoul'
app폴더가 아닌 프로젝트 폴더. settings.py 가 있는 폴더 안에 만드세요!
# config/celery.py
import os
from celery import Celery
from django.conf import settings
# DJANGO_SETTINGS_MODULE의 환경 변수를 설정해준다.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
app = Celery('qna')
# namespace를 설정해준 것은 celery 구성 옵션들이 모두 앞에 CELERY_가 붙게 되는 것을 의미합니다.
app.config_from_object('django.conf:settings', namespace='CELERY')
# 다음을 추가해주면 celery가 자동적으로 tasks를 찾는데, 우리가 설치되어 있는 앱에서 찾아줍니다.
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
@app.task(bind=True)
def debug_task(self):
print("Request: {0!r}".format(self.request))
이렇게 해주면 Django가 시작될 때 @shared_task 데코레이터가 앱을 사용할 수 있도록 앱이 로드됩니다.
우선 이번에는 @app_task를 사용하기 때문에 따로 적어주지 않아도 괜찮습니다.
# config/__init__.py
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ['celery_app']
앞서 SMTP를 가지고 메일을 보내는 코드를 비동기로 연동해보려 합니다.
우선 view에 있던 send_email 기능을 worker에게 전달할 업무로 따로 빼주겠습니다.
하나만 있으면 섭섭하니 아래에 덧셈 함수도 호딱 만들어줍니다.
# qna/tasks.py
from django.core.mail import EmailMessage
from config.celery import app
@app.task()
def task_send_email():
subject = "message"
to = ["odh0112@naver.com"]
from_email = "odh0112@naver.com"
message = "메세지 테스트"
EmailMessage(subject=subject, body=message, to=to, from_email=from_email).send()
@app.task()
def task_add(x, y):
return x + y
아 그리고 @app.task를 이용하는 방법이 있고, @shared_task를 이용하는 방법이 있는데 이 둘의 차이는 우선 패쑤
그러면 View는 이렇게 허전하게 남아있게 되구용
# qna/views.py
from rest_framework.response import Response
from .tasks import task_send_email
def send_email(request):
try:
task_send_email.delay()
task_add.delay(1, 2)
return Response("성공했습니다.")
except Exception as e:
return Response("성공적으로 보내지 못했습니다.")
이를 사용하기 위해 URL 라우팅을 해주면 끝입니다.
# config/urls.py
from qna.views import *
urlpatterns = [
path('qna/', send_email, name="send_email"),
]
다음과 같이 입력해주면 redis를 간단히 실행할 수 있습니다.
# Redis 실행
redis-server
# Redis 정지
$ redis-cli shutdown
or
$ systemctl stop redis
(venv) root@ip-172-31-45-200:/home/ubuntu/celery_test# redis-server
2114:C 03 Jan 2023 10:19:51.571 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2114:C 03 Jan 2023 10:19:51.571 # Redis version=6.0.16, bits=64, commit=00000000, modified=0, pid=2114, just started
2114:C 03 Jan 2023 10:19:51.571 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
2114:M 03 Jan 2023 10:19:51.572 * Increased maximum number of open files to 10032 (it was originally set to 1024).
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.0.16 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 2114
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
2114:M 03 Jan 2023 10:19:51.575 # Server initialized
2114:M 03 Jan 2023 10:19:51.575 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
2114:M 03 Jan 2023 10:19:51.576 * Loading RDB produced by version 6.0.16
2114:M 03 Jan 2023 10:19:51.577 * RDB age 1017261 seconds
2114:M 03 Jan 2023 10:19:51.577 * RDB memory usage when created 0.77 Mb
2114:M 03 Jan 2023 10:19:51.577 * DB loaded from disk: 0.001 seconds
2114:M 03 Jan 2023 10:19:51.577 * Ready to accept connection
# shell 환경에서 입력해주면 됩니다.
celery -A [파일이름] worker --loglevel=info
celery -A config worker --loglevel=info
[tasks] - 비동기로 작업할 수 있는 목록들이 표시됩니다.
웹서버와 WSGI로 구성되어 있으면 그렇게 실행해주면 되고, 그게 아니라면 그냥 runserver로 실행해주면 됩니다.
python manage.py runserver
or
gunicorn --bind 0:8000 config.wsgi:appication
- @app_task로 처리하고 싶은 일에 딱지를 붙인다.
- task_send_email 작업에 delay를 붙이면 Redis Backend에 기록이 저장되고
- Redis는 Celery에게 일을 전달해줍니다.
- 일을 받은 Celery는 task_send_email 작업을 시작합니다.
참고자료 📩
Celery 공식문서 - Django settings 방법
Celery 공식문서 - Django에서의 응용 방법들