[4th TeamProject] - Celery 라이브러리를 이용한 인증 이메일 비동기 발송

장현웅·2023년 10월 24일
0

이메일 인증 기능을 구현했지만, celery 라이브러리를 이용해서 비동기 방식으로 이메일 발송을 처리해보고 싶어서 시도해봤습니다.

먼저 Celery 라이브러리를 설치합니다.

Celery 는 python 동시성 프로그래밍에서 가장 많이 사용되는 방법 중 하나로 실시간 처리에 중점을 두고 작업 예약을 지원하는 작업 큐입니다. Celery로 python 코드를 실행하는 worker 를 만들 수 있습니다. Celery 는 task 를 broker 를 통해 전달하고 worker 가 처리하는 구조입니다.

  • Broker는 task(message)를 client와 worker 사이에서 task(message) 전달을 중개하는 역할로 worker에게 task(message)를 전달하는 역할을 합니다.
  • Client는 task(message)를 생성합니다.
  • Worker는 task(message)를 실행하고 처리합니다.

settings.py가 있는 메인 프로젝트 폴더에 celery.py 파일을 생성해서 Celery Instance를 만들어주겠습니다. 장고에서는 이를 app이라고 부릅니다.

pip install celery

settings.py 파일에 Celery 설정을 추가합니다.

[ settings.py ]

EMAIL_HOST = 'smtp.gmail.com' 		                # 메일 호스트 서버
EMAIL_PORT = 587 			                        # SMTP 포트 번호
EMAIL_HOST_USER = 'hyeonwoongjang01@gmail.com' 	    # 서비스에서 사용할 Gmail
EMAIL_HOST_PASSWORD = 'ljyl btuq epgs tvfd'         # 서비스에서 사용할 Gmail의 password

EMAIL_USE_TLS = True                                # 사용자의 이메일 서버와의 통신에 TLS(Transport Layer Security)를 사용하면 데이터를 암호화하여 안전한 통신을 보장합니다.
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

CELERY_BROKER_URL = 'amqp://guest@localhost:5672//' # Celery와 같은 작업 대기열 시스템에서 작업을 비동기적으로 처리하기 위해 활용되는 메시지 브로커입니다.
                                                    # 정의한 url은 RabbitMQ 메시지 브로커 서버에 연결하는데 사용되는 URL입니다.
CELERY_RESULT_BACKEND = 'rpc://'                    # Celery 작업의 결과를 저장하는 백엔드 저장소를 설정하는 데 사용되는 환경 변수 또는 설정 옵션입니다. 이 설정은 Celery가 비동기 작업을 처리하고 작업의 결과를 저장하는 방법을 지정합니다.
                                                    # rpc://로 설정하면 작업 결과가 메시지 브로커에 저장되며 작업 수행자는 해당 결과를 검색합니다.
CELERY_RESULT_EXPIRES = 3600                        # 작업 결과를 설정된 시간(초) 동안 보관합니다.

브로커는 메시지 큐 시스템에서 메시지 전송자(Sender)와 수신자(Receiver) 간의 통신을 중개하는 역할을 합니다. 메시지 큐에서 사용자가 보낸 메시지를 저장하고 수신자에게 전달하는 중간자 역할을 합니다. 브로커는 대기열 메시지 시스템의 중심 요소로 사용되며, 주요 브로커 시스템으로는 RabbitMQ, Apache Kafka, Apache ActiveMQ, Redis, 등이 있습니다.

저는 RabbitMQ를 선택했습니다. 설치 방법은 저는 도커를 사용했기 때문에 $ docker run -d -p 5672:5672 rabbitmq 명령어로 RabbitMQ 서버를 추가 및 실행합니다.

Celery를 Django와 함께 사용하려면 Celery 애플리케이션(또는 인스턴스)을 정의해야 합니다.

[ celery.py ]

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'facebug_back_end.settings')                                                            # Django 프로젝트의 설정 모듈을 지정하기 위한 코드입니다. Celery 설정 파일인 celery.py에서 Django 설정을 참조하여 Celery 설정을 Django와 함께 사용하기 위해 추가해줍니다.

app = Celery('facebug_back_end', broker=settings.CELERY_BROKER_URL, backend=settings.CELERY_RESULT_BACKEND, include=['user.tasks'])     # ask 모듈을 지정하여 Celery가 해당 모듈에서 task를 찾고 처리할 수 있도록 하고 작업을 백그라운드에서 진행할 수 있도록 메시지 브로커와 작업 결과 저장 백엔드 설정을 적용한 Celery 인스턴스(app)을 생성합니다.

app.config_from_object('django.conf:settings', namespace='CELERY')                                                                      # Celery 애플리케이션의 설정을 가져오는 경로를 지정합니다.

app.conf.update(result_expires=settings.CELERY_RESULT_EXPIRES,)                                                                         # Celery 애플리케이션의 설정 중 'result_expires' 옵션을 Django 설정 파일(settings.py)에서 가져온 값으로 업데이트하는 부분입니다. 'result_expires' : Celery 작업 결과가 저장될(유효한) 시간을 지정합니다.


if __name__ == '__main__':                                                                                                              # 현재 스크립트가 직접 실행될 때 Celery 애플리케이션을 시작합니다. Celery 애플리케이션을 시작하면 이 애플리케이션은 백그라운드에서 작업을 처리할 준비가 됩니다.
    app.start()

app.autodiscover_tasks()                                                                                                                # Django에서 Celery 작업 모듈을 자동으로 찾고 등록하는 메서드입니다. 이 메서드를 사용하면 Celery 애플리케이션에 대한 작업 모듈을 명시적으로 지정하지 않고도 Django 애플리케이션 내에서 사용 가능한 모든 작업을 자동으로 등록할 수 있습니다.
  • os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'facebug_back_end.settings') : Celery 작업자(worker)가 어떤 Django 프로젝트를 사용할 것인지 설정합니다. 'DJANGO_SETTINGS_MODULE' 환경 변수를 설정하여 Django 설정 파일이 어디에 있는지 알려줍니다.
  • app = Celery('앱 이름', broker='amqp://', backend='rpc://', include=['user.tasks']) : Celery 애플리케이션(app)을 만듭니다.
    • 앱 이름 : Celery 애플리케이션의 이름입니다. 이 이름은 Celery 애플리케이션을 식별하는 데 사용됩니다. 보통 Django 프로젝트의 이름을 사용하거나, 프로젝트와 관련된 명칭을 설정합니다.
    • broker : Celery가 사용할 메시지 브로커를 설정합니다. 'amqp://'은 Advanced Message Queuing Protocol (AMQP)를 사용하며, RabbitMQ와 호환되는 메시지 브로커를 가리킵니다. 이 부분은 메시지를 전송하고 받는 데 사용됩니다.
    • backend : Celery가 사용할 백엔드를 설정합니다. 'rpc://'은 결과를 저장하고 검색하는 데 사용되는 백엔드입니다. 여기에서는 Remote Procedure Call (RPC)을 사용하므로 결과를 반환하기 위한 백엔드를 지정하고 있습니다.
    • include : Celery 작업을 수행할 때 포함할 모듈을 지정합니다. 이 경우 user앱 내의 tasks.py 모듈을 포함하도록 지정하여 'user.tasks' 모듈에 정의된 Celery 작업을 이 애플리케이션에서 사용할 수 있게 됩니다.
  • app.config_from_object('django.conf:settings', namespace='CELERY') : Celery의 동작을 프로젝트의 Django 설정과 통합하는 데 사용됩니다.
    • namespace='CELERY' : Celery 관련된 설정이 반드시 CELERY_로 시작하며, 대문자로 와야 함을 의미합니다. (이 설정은 원하는대로 정의해줄 수 있습니다.)
  • app.conf.update(result_expires=3600) : 작업 결과가 3600초(즉, 1시간) 동안 유효하다는 것을 의미합니다.
  • if __name__ == '__main__': app.start() : 명령줄에서 Celery worker를 실행할 때 사용되며, 개발 환경에서 Celery worker를 수동으로 시작하려는 경우에 사용됩니다.
  • app.autodiscover_tasks() : Django 앱 내에서 사용 가능한 모든 Celery 작업을 자동으로 찾아서 등록하는 역할을 합니다. include로 지정한 모듈과 더불어 프로젝트 내에서 생성한 모든 Celery 작업을 등록하여 Celery가 이러한 작업을 수행할 수 있도록 합니다.

이 코드는 Django 프로젝트와 Celery를 통합하고, Celery를 사용하여 백그라운드 작업을 실행하고, 작업자(worker)가 프로젝트 내의 작업을 찾을 수 있도록 설정합니다.

__init__.py에 아래 코드를 작성합니다.

[ init.py ]

from .celery import app as celery_app           # 이 코드는 Django 프로젝트 내부의 celery.py 또는 celery와 같은 파일에서 정의된 Celery 인스턴스(app 객체)를 가져옵니다. = Django와 Celery를 연동하는 단계 
                                                # 그런 다음, shared_task 데코레이터를 이용하여 Celery가 수행할 작업들을 import할 필요 없이 가져올 수 있습니다.
__all__ = ('celery_app',)

__init__.py 파일은 파이썬 패키지의 초기화 파일로, 패키지 내부의 모듈들을 외부에서 사용하기 쉽게 만드는 역할을 합니다. Django 프로젝트에서 Celery를 사용하는 경우, Celery 인스턴스를 Django 프로젝트 내의 다른 모듈에서도 쉽게 접근하고 사용해야 합니다. 이렇게 설정해주면 Django가 시작할떄 Celery가 항상 import 되고, shared_task 데코레이션(@shared_task)이 Celery를 이용하게 됩니다.

  • @shared_task : 이 데코레이터를 사용하면 Django 프로젝트 내에서 Celery 작업을 정의할 수 있으며, Django 애플리케이션 내에서 사용자 요청에 대한 빠른 응답을 유지하면서 시간이 오래 걸리는 작업들 예를 들어, 이메일 발송, 이미지 처리, 큰 파일 업로드 및 다운로드, 데이터베이스 작업 등을 백그라운드에서 실행할 때 사용됩니다. 혹은 Celery의 스케줄링 기능을 사용하여 정기적으로 실행해야 하는 예약된 작업을 실행할 때 사용됩니다.

이제 celery가 수행할 작업을 app 디렉토리에 tasks.py 파일을 생성하여 정의해줍니다.

[ tasks.py ]

from celery import shared_task
from django.core.mail import send_mail


@shared_task                                                                                # '@shared_task' : Django에서 Celery 작업을 정의하는 데 사용되는 데코레이터입니다. Celery를 통해 백그라운드에서 실행되어야 하는 비동기 작업을 만들 때 사용됩니다.
def send_verification_email(user_id, verification_url, recipient_email):                    # 이 작업은 Celery worker에서 실행될 때 백그라운드에서 비동기로 실행됩니다. 
    subject = '이메일 확인 링크'
    message = f'이메일 확인을 완료하려면 다음 링크를 클릭하세요: {verification_url}'
    from_email = 'hyeonwoongjang01@gmail.com'
    print(f'subject:{subject}')
    send_mail(subject, message, from_email, [recipient_email])
  • @shared_task데코레이터를 사용하여 'send_verification_email' 함수를 Celery 작업으로 표시하고 이 함수를 비동기로 실행할 수 있습니다.

이 코드를 Celery 작업으로 정의하면, 이메일 전송 작업을 백그라운드에서 실행할 수 있으며, 웹 애플리케이션이 블록되지 않고 계속 실행됩니다.

[ 회원가입 뷰 ]

class RegisterView(APIView):
    def post(self, request):
        """사용자 정보를 받아 회원가입 합니다."""
        
        serializer = UserSerializer(data=request.data, context={'profile_img':request.FILES})
        
        if serializer.is_valid():
            user = serializer.save()
            
            # 이메일 확인 토큰 생성
            token = default_token_generator.make_token(user)
            uid = urlsafe_base64_encode(force_bytes(user.pk))
            
            # 이메일에 인증 링크 포함하여 보내기
            verification_url = f"http://127.0.0.1:8000/verify-email/{uid}/{token}/"
            
            send_verification_email.delay(user.id, verification_url, user.email)                        # 'delay' : Celery 작업을 예약하여 나중에 실행되도록 하는 메소드입니다. 'delay' 메소드를 호출하면 작업이 백그라운드에서 비동기적으로 실행됩니다.
            
            return Response({'message':'회원가입 성공'}, status=status.HTTP_201_CREATED)
        else:
            return Response({'message': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
  • send_verification_email.delay(user.id, verification_url, user.email) : 'send_verification_email' 함수는 tasks.py에 정의한 Celery 작업 함수입니다.
    • delay() : Celery 작업을 예약하고 비동기로 실행하기 위한 메소드입니다. 'send_verification_email' 함수에 전달된 인수를 사용하여 이 작업을 실행합니다.

참고 (이메일 인증)

이제 Celery 작업을 수행할 작업자(Worker)을 실행해줘야합니다.

$ celery -A 장고앱이름 worker -l INFO		# Celery를 실행하여 작업 워커를 시작하는 명령입니다. 

가동하면 아래와 같은 결과가 나옵니다.

-------------- celery@DESKTOP-L81ERLR v5.3.4 (emerald-rush)
--- ***** -----
-- ******* ---- Windows-10-10.0.19041-SP0 2023-10-25 01:29:26
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app:         facebug_back_end:0x180b8d9ba00        
- ** ---------- .> transport:   amqp://guest:**@localhost:5672//      
- ** ---------- .> results:     rpc://
- *** --- * --- .> concurrency: 12 
(eventlet)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> celery
 exchange=celery(direct) key=celery

[tasks]
  . user.tasks.send_verification_email

만약, 작업자(Worker)는 연결되었지만, Celery 작업을 인식하지 못한다면 Window환경 때문일 수도 있다. 그럴 때는,

pip install eventlet
celery -A <module> worker -l info -P eventlet

아래 명령어로 실행하면 정상 실행됩니다.

WindowsUnix 계열의 운영체제와는 다른 프로세스 처리 모델을 사용하므로 Celery와 같은 비동기 작업을 관리하기에 일부 문제가 발생할 수 있습니다.

eventlet은 비동기 이벤트 루프 라이브러리로, Celery와 함께 사용하여 Windows 환경에서 Celery 작업자(worker)를 실행하는 데 도움이 될 수 있습니다. eventlet을 설치하고 -P eventlet 플래그를 사용하여 Celery 작업자를 시작하면 Windows에서의 Celery 실행 문제를 완화할 수 있습니다.

worker의 작업을 멈추고 싶을 때는 'Ctrl + C'를 하시면 됩니다.

이렇게 하면 회원가입을 진행할 시, 인증 이메일 발송 작업은 백그라운드에서 실행되며, 웹 애플리케이션은 작업이 완료될 때까지 차단되지 않습니다. 백그라운드에서 작업이 완료되면 사용자에게 확인 이메일이 전송됩니다.

0개의 댓글