개발 일지

셀러리란? - 비동기설정을 위해서 필요한 기능

만식 2024. 6. 4. 20:05

 

셀러리란? - 비동기설정을 위해서 필요한 기능

  • request요청이 긴 것을 비동기적으로 처리를 위하여 필요 한 기능인 셀러리
  • Django EP66 - Celery란 무엇인가? (youtube.com)
  • Workers - 각각 일을 받아서 할 수 있는 작업군
  • 병렬적으로 작업을 처리할 수 있음

기존 방식 : 작업을 하나씩 처리해 가면서 동작

셀러리 사용 : 작업을 요청하면 broker가 필요한 작업을 분해하여주는 기능을 가지고 있

 

 

elder 음성인식 - Redis: create(1)

문제: Redis를 이용해서 cart에 저장하기 위한 add_to_cart()가 postman상으로 요청이 잘 보내짐에도 불구하고 Redis-cli에서는 해당 key가 조회되지 않음

해결시도:

  1. Redis 연결 확인해봄 → api call 보냄
    1. postman상으로는 연결이 잘 된 것으로 판단됐음
  2. python 코드 내에 cache.set을 이용해서 postman으로 api call 보내봄

하지만 여전히 Redis-cli에서는 해당 key조회가 안됨

def redis_test(request):
    r = redis.StrictRedis.from_url(settings.CACHES['default']['LOCATION'])
    print("\n\n Pinging Redis...")
    try:
        if r.ping():
            print("\n\n Redis connection successful.")
    except redis.ConnectionError:
        print("\n\n Redis connection failed.")

    cache.set("cache", "됐으면 좋겠다")

 

  1. 즉, 문제의 원인이 django와 Redis자체의 연결에 문제인 것으로 파악함
  2. settings.py의 CASHES를 다시 살펴본 결과 location이 로컬 포트>의 /1으로 되어 있어서 발생한 오류인 것으로 확인함
  3. 다시 말해, settings에 Redis Location 1번을 쓴다고 지정해 두고, hset도 잘 된 것이음 → 하지만 우리가 Redis-cli에서 조회하는 것이 default인 0번이어서 계속 조회가 안 됐던 것임

문제의 해결은 settings.py의 CACHESlocaton에서 /1을 지워서 default인 0번으로 지정해 주니 Redis-cli에서도 hset 한 key로 잘 조회됨

 

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",  # Redis 서버의 위치 # 0번. /1하면 1번 ..
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

 

## 기존의 /1 로 저장했을 때
127.0.0.1:6379> keys *
1) "vanilla_latte"
2) "name"
3) "americano"

## /1 제거 후 저장했을 때
127.0.0.1:6379> keys *
1) "vanilla_latte"
2) "name"
3) ":1:cache" ## 잘 저장된 것을 확인
4) "americano"

 

elder 음성인식 - Redis: read (1)

문제 1:

add_to_cart()는 이제 POST요청으로 잘 처리되고 response도 잘 받아와 짐. 하지만 view_cart()는 response를 받아오는 과정에서 계속 오류가 발생함

구체적으로는 view_cart()에서 context에 대한 print문까지는 잘 찍히는데, 그 뒤의 return문에서 계속 오류가 발생함

  • username = request.GET.get("username")
  • print("\\n\\n\\n username 이 잘 들어왔는지: ", username)
  • → 이때의 결과가 아래와 같음

username 이 잘 들어왔는지: None

 

해결:

정상적으로 잘 작동하고 있는 add_to_cart()와 view_cart()의 request를 print문으로 찍어보면서 비교해 봄

  • add_to_cart()의 request:

<WSGIRequest: POST '/orders/add_to_cart/'>

  • view_cart()의 request:

<rest_framework.request.Request: GET '/orders/cart/'>

request방식이 달라서 발생한 문제이므로, 일단은 view_cart()에서 username을 받아오는 방식을 username = request.data.get("username")와 같이 변경해 주니 오류 해결됨

그리고 확인차 찍어본 request.data에 대한 print문의 결과는 아래와 같다

request의 data: <QueryDict: {'username': ['mega']}>

 

@csrf_exempt
def add_to_cart(request):
    print("\n\n add to cart의 request는: ", request)

    if request.method == "POST":
        # username = request.user.username # 나중에 elder_menu에서 연결할 때 다시 구현
        # print("\n\n request user >> ", request.user)
        username = request.POST.get("username")
        item_id = request.POST.get("item_id")
        image = request.POST.get("image")
        name = request.POST.get("name")
        price = int(request.POST.get("price"))
        quantity = int(request.POST.get("quantity"))
        
        cart = Cart(username)
        item = CartItem(item_id, image, name, price, quantity)
        serializer = CartSerializer(item)
        cart.add_to_cart(serializer.data)
        
        return JsonResponse({"message": "Item added to cart"})

# 장바구니 페이지 뷰
@api_view(['GET'])
def view_cart(request):
    print("\n\n request 객체라도 나오는지: ", request)
    print("\n\n request의 data: ", request.data)
    # {"username": "mega"}
    username = request.data.get("username")
    print("\n\n\n username 이 잘 들어왔는지: ", username)
    cart = Cart(username)
    context = {"cart_items": cart.get_cart()}
    print("\n\n\n context가 받아와지는지: ", context)
    return Response({"context": context})

 

문제:

TypeError: keys must be str, int, float, bool or None, not bytes 오류가 발생

  • context는 받아와지고 있는데 이후 *return* Response({"context": context}) 에서 오류 발생
context가 받아와지는지:  {'cart_items': {b'americano': b'2', b'Vanilla Ice Blended': b'2'}}

해결: Redis가 데이터를 반환하는 형식은 byte-string이고, Json encoder는 str, int, float, bool or None의 형식을 지원하기 때문에 발생하는 오류라고 판단

→ view_cart()의 return형식을 Json encoder에 맞는 형식으로 수정해 줌

 

def get_cart(self):
        redis_conn = get_redis_connection("default")
        cart_data = redis_conn.hgetall(self.cart_key)
        return {k.decode('utf-8'): v.decode('utf-8') for k, v in cart_data.items()}

그 결과로 print문으로 context도 잘 받아와 지는 걸 확인함

{"context":{"cart_items":{"americano":"2","Vanilla Ice Blended":"2"}}}

 

Docker - compose up build

 

docker image를 이용하여 postgreSQL, redis, celery를 구현하는 단계에서 오류가 발생

web-1            | python: can't open file '/app/manage.py': [Errno 2] No such file or directory-

→ 해당 파일이 분명히 존재하는 것을 확인했으나 실행 시 파일을 확인할 수 없다는 오류가 발생하는 문제가 해결이 되지 않아 다음 단계로 넘어갈 수 없는 상태

  • 일시적인 해결 방안으로 볼륨 코드 주석 처리 하여 실행하면 실행이 되는 것을 확인
services:
  web:
    build: .
    command: python3 manage.py runserver 0.0.0.0:8000
#    volumes:
#      - .:/app
    ports:
      - "8000:8000"
    depends_on:
      - db
      - redis

  db:
    image: postgres:latest
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword

  redis:
    image: redis:latest

  celery_worker:
    build: .
    command: celery -A SilverLining worker -l info
#    volumes:
#      - .:/app
    depends_on:
      - redis

  celery_beat:
    build: .
    command: celery -A SilverLining beat -l info
#    volumes:
#      - .:/app
    depends_on:
      - redis

→ 그러나 이렇게 작성하면 변경된 수정사항을 최신화할 수 없어 항상 up —build를 해야 하는 문제가 발생

  • 오류문제 해결 방안들→ 프로젝트파일 날리고 새로 clone
  • → dockerFile 내용 수정
  • → docker 삭제 후 설치

위의 방법들을 동원하여도 동일한 오류 발생

  • 문제 해결 : 기존 외부 드라이브 E:에 설치되어 있던 프로젝트 파일을 c드라이브로 이동하여 docker를 생성하여 오류를 해결→ 변경 : C:/User/username/SilverLining/
  • → 기존 : E:/python/SilverLining/
  • 신한결튜터님 : docker 생성 시 발생되는 오류를 분석하여 해당 오류원인이 파일위치에 있는 것으로 판단하여 경로를 수정해 보기로 건의
  • sd카드 폴더 밖으로 옮긴 후 실행 → 오류해결
  • 결론 : 주 드라이브가 아닌 외부의 드라이브에서 docker를 생성하게 되면 오류가 발생할 경우가 생기므로 docker생성은 주 드라이브에서 생성하여야 한다는 것을 알게 됨
  • 추가 : 기존 번역을 위해서 필요한 django-translation을 사용하기 위에서 gettext를 docker에도 설치하기 위해서는 아래와 같은 명령어를 추가로 dockerFile에 입력해 주어야 함
FROM python:3.8

# GNU gettext 설치
RUN apt-get update && apt-get install -y gettext

# 필요한 작업을 위한 작업 디렉토리 설정
WORKDIR /app

# 여기에 나머지 Dockerfile 명령어를 추가하세요.



# up 후에 입력 필요
docker-compose run --rm web bash

도커로 서버를 실행하게 되면 마이그레이트 및 번역 등록을 시켜주어야 함

#docker로 실행 후 해주어야 하는 것

#마이그래이션
docker-compose run --rm web python manage.py migrate

# 번역 등록
docker-compose run --rm web python manage.py compilemessgae

# 번역시 필요
docker-compose run --rm web bash

 

elder 음성인식 - Redis: update

  • 중요변화
    • views.py에 add_quantity() 추가
    • Redis에 저장되는 형태를 변화
    • 아래의 key는 cart_key로 불러온 value내의 field값과 같음
      • {key: value} _ ex. Americano: 2
      • {key: value} _ ex. Americano: {"quantity": , "price": , "img": }
    • serializer에서 item_id 삭제
      • Redis에서의 key(=field)를 “name”으로 사용하고 있기 때문에 별도로 item_id가 필요하지 않아서 삭제해 줌

    # 수정 전_cart.py
    def update_quantity(self, menu_name, quantity):
        redis_conn = get_redis_connection("default")
        redis_conn.hincrby(self.cart_key, menu_name, quantity)
    # 수정 후_cart.py
	 def update_quantity(self, item_data):
        redis_conn = get_redis_connection("default")
        # dict 형식으로 받는다 : item_data {}
        # "name"으로 해당 데이터 불러오고 "quantity"로 해당 value를 수정 ? 
        # 이렇게 해서 hset 으로 저장하나?
        print("\n\n item_data 잘 넘어왔나: ", item_data)
        name = item_data["name"]
        print("\n\n name >>> ", name)
        update_data = json.dumps(item_data)
        redis_conn.hset(self.cart_key, name, update_data)
# 장바구니 항목 수량 수정
@csrf_exempt
@api_view(['POST'])
def add_quantity(request):
    if request.method == "POST":
    # username = request.user.username # 나중에 elder_menu에서 연결할 때 다시 구현
    # print("\n\n request user >> ", request.user)
        username = request.POST.get("username") # mega
        print("\n\n add_quantity username: ", username)
        name = request.POST.get("name") # 아메리카노
        print("\n\n name은 잘 들어왔는지: ", name)
        quantity = int(request.POST.get("quantity")) # 3
        print("\n\n quantity 잘 들어왔는지: ", quantity)

    cart = Cart(username)
    # 1. name, quantity, price, img 다 redis에 저장하는 방식 {"name": {"quantity": , "price": , "img": }
    # serializer 는 returnDict --> dict 로 바꿔서 사용
    # 여기서 serializer로 dict 형식으로 보내준다 {"name": , "quantity": }
    # menu = Menu.objects.get(store_id = request.user.id).filter(food_name = name)
    menu = Menu.objects.filter(store_id = 2, food_name = name).first()
    print("\n\n menu가 이렇게 가져오는 게 맞나: ", menu, menu.food_name, menu.img, menu.price)
    image = menu.img
    price = menu.price
    item = CartItem(image, name, price, quantity)
    serializer = CartSerializer(item)
    item_data = serializer.data
    # =============================
    # 2. name, quantity 만 redis에 저장하는 방식 {"name": quantity}
    # name과 quantity만 보내준다
    # update_quantity에서 수정된 값 돌려주면
    # quantity는 수정된 값으로, name 현재 name, 나머지 image, price 를 db에 접근해서 가져오기
    cart.update_quantity(item_data)
    return Response({"message": "장바구니 수량 수정"})

 

elder 음성인식 - Redis: delete

  • 개별메뉴 삭제
    • path("cart/remove/str:menu_name/", views.remove_from_cart, name="remove_from_cart")
    • 단, 주의해야 할 점은 postman에서 post요청 시 menu name은 문자열 그대로 띄어쓰기까지 그냥 써주면 됨
    • 예를 들어 Redis에 menu_name이 “Hot Chocolate”로 저장된 경우
      • cart/remove/Hot_Chocolate (x)
      • ``cart/remove/Hot Chocolate` (o)
      • 즉, 문자열 그대로 요청을 보내야 오류가 나지 안
  • 장바구니 전체삭제
    • path("cart/clear/", views.clear_cart, name="clear_cart")
# views.py

# 장바구니 항목 제거 뷰
@csrf_exempt
@api_view(['POST'])
def remove_from_cart(request, menu_name):
    print("\n\n remove_from_cart() 타는지>>>" )
    # username = request.user.username
    username = request.POST.get("username")
    print("\n\n remove() username: ", username)
    print("\n\n menu_name: ", menu_name)
    cart = Cart(username)
    cart.remove(menu_name)
    return Response({"message": "해당 메뉴 삭제"})

# 장바구니 전체 삭제 뷰
@csrf_exempt
@api_view(['POST'])
def clear_cart(request):
    # user_id = request.user.id
    username = request.POST.get("username")
    cart = Cart(username)
    cart.clear()
    return Response({"message": "장바구니 전체 삭제"})

 

# cart.py

## **Removing an Item**:
    def remove(self, menu_name):
        print("\n\n menu_name: ", menu_name)
        redis_conn = get_redis_connection("default")
        redis_conn.hdel(self.cart_key, menu_name)

## Clear the cart
    def clear(self):
        redis_conn = get_redis_connection("default")
        redis_conn.delete(self.cart_key)

 

 

카트 - 장바구니 수정

  • 기존에 코드를 수정하면서 변수명이 변경되어 데이터 값이 넘어가지 않는 문제 발생
  • 변수명 selectedItems → cart로 변경

문제점 : 기존 selectedItems에 있는 변수 값을 받아오는 부분에서 값을 받아 오지 못하여 빈 값만 받아오게 됨

해당받아오는 부분까지 변수명을 변경하여 오류 해결

const selectedItem = {} # 삭제 맨 위에 let cart로 이용

# selectedItem를 cart로 변경
const selectedItemsArray = Object.entries(cart).map(([name, item]) => {
        return {name: name, count: item.count};
    });