【21.0】结合celery改造接口

发布时间 2023-08-19 17:20:32作者: Chimengmeng

【一】引入

  • 所有接口都可以改造,尤其是查询所有的这种接口,如果加入缓存,会极大的提高查询速度

  • 首页轮播图接口:

    • 获取轮播图数据,加缓存---》咱们只是以它为例

【二】改造轮播图接口

  • luffyCity\luffyCity\apps\home\views.py
class BannerView(GenericViewSet, CommonListModelMixin):
    # 过滤出没有被删除 + 可以显示 + 优先级排序
    queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
    serializer_class = BannerSerializer

    def list(self, request, *args, **kwargs):
        '''
        (1)在缓存中查询有没有数据
        (2)如果有,直接返回,不走父类的list
        (3)如果没有,走父类的list,查询数据库,返回数据
        (4)返回数据,存入缓存
        '''

        data = cache.get('banner_list')

        # 缓存中没有数据
        if not data:
            # 查询数据库
            res = super().list(request, *args, **kwargs)  # 查询数据库
            # 存入缓存
            data = res.data.get("data")
            cache.set('banner_list', data)
        # 缓存中有数据
        return CommonResponse(data=data)

【三】首页轮播图之双写一致性问题解决

  • celery定时更新轮播图资源
  • luffyCity\celery_task\home_task.py
# -*-coding: Utf-8 -*-
# @File : home_task .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/8/11
from .celery import app
from django.conf import settings
from django.core.cache import cache
from luffyCity.apps.home.models import Banner
from luffyCity.apps.home.serializers.Banner_serializer import BannerSerializer

@app.task
def update_banner():
    # 查询数据库,拿到所有的轮播图资源,放到缓存中
    banner_list = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
    banner_ser = BannerSerializer(instance=banner_list, many=True)
    # 如果在视图类中,因为有request对象,所以会自动帮我们拼接路径
    # 在task中没有request对象,所以需要自己拼接路径
    for item in banner_ser.data:
        item['image'] = settings.BACKEND_URL + item['image']
    # 存到缓存中
    cache.set('banner_list', banner_ser.data)
    return "更新成功"
  • luffyCity\celery_task\celery.py
# -*-coding: Utf-8 -*-
# @File : celery .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/8/11
import os
from datetime import timedelta
from celery import Celery
from celery.schedules import crontab
# celery 需要 注册Django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffyCity.settings.dev")

# 消息中间件
broker = "redis://127.0.0.1:6379/0"
# 存储结果
backend = "redis://127.0.0.1:6379/1"
# 存放需要处理任务的列表
include = ['celery_task.user_task','celery_task.home_task']
# 实例化得到celery对象
app = Celery(__name__, backend=backend, broker=broker, include=include)

# APP 配置 +---+ 定时任务配置
app.conf.beat_schedule = {
    # 定时更新banner资源
    'update_banner': {
        # 执行的任务函数
        'task': 'celery_task.home_task.update_banner',
        # 延迟时间
        # 'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
        # 'schedule': crontab(hour=11, minute=35),  # 每天11点35,执行
        'schedule': timedelta(minutes=5),
        'args': (),
    },
}
  • luffyCity\luffyCity\apps\home\views.py
from django.shortcuts import render, HttpResponse
from django.core.cache import cache
from rest_framework.viewsets import GenericViewSet
from django.conf import settings
from luffyCity.apps.home.models import Banner
from luffyCity.apps.home.serializers.Banner_serializer import BannerSerializer
from luffyCity.utils.common_mixin import CommonListModelMixin
from luffyCity.utils.common_response import CommonResponse


class BannerView(GenericViewSet, CommonListModelMixin):
    # 过滤出没有被删除 + 可以显示 + 优先级排序
    queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
    serializer_class = BannerSerializer

    def list(self, request, *args, **kwargs):
        '''
        (1)在缓存中查询有没有数据
        (2)如果有,直接返回,不走父类的list
        (3)如果没有,走父类的list,查询数据库,返回数据
        (4)返回数据,存入缓存
        '''

        data = cache.get('banner_list')

        # 缓存中没有数据
        if not data:
            # 查询数据库
            res = super().list(request, *args, **kwargs)  # 查询数据库
            # 存入缓存
            data = res.data.get("data")
            cache.set('banner_list', data)
        # 缓存中有数据
        return CommonResponse(data=data)

【四】异步发送短信

  • luffyCity\celery_task\user_task.py
# -*-coding: Utf-8 -*-
# @File : user_task .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/8/11

from .celery import app
import time
from luffyCity.libs.SMS_TencentCloud_Sender import tencent_sms_main


@app.task
def send_sms(mobile, code):
    res = tencent_sms_main(code, mobile)
    if res:
        return f'{mobile}的验证码:>>{code}已发送'
    else:
        return f'{mobile}的验证码:>>{code}发送失败'
  • luffyCity\luffyCity\apps\user\views.py
@action(methods=['GET'], detail=False)
def send_sms(self, request, *args, **kwargs):

    # 前端把需要发送验证码的手机号传入,携带在地址栏中
    mobile = request.query_params.get('mobile', None)
    code = get_verify_code(4)  # 存储验证码,放到缓存内
    cache.set(f'sms_code_{mobile}', code)
    if mobile:
        # # 开启线程处理短信
        # # tencent_sms_main(verify_code=code, tag_phone=mobile)
        # t = Thread(target=tencent_sms_main, args=(code, mobile,))
        # t.start()

        # 异步发送短信 ---- 向管道 提交异步任务
        res = send_sms.delay(code=code, mobile=mobile)
        # 将任务提交到数据库存储,方便后续查询


        return CommonResponse(msg="验证码已发送")
    raise APIException("请输入手机号")

【五】异步秒杀前后端

【1】思维导图

【2】前端逻辑实现

  • lufycity_web\src\views\SkillView.vue
<script setup>

</script>

<template>
  <div>
    <h2>Go语言从入门到放弃</h2>
    <el-button type="danger" round @click="handleKill">秒杀</el-button>
  </div>
</template>

<script>

export default {
  name: 'Go',
  data() {
    return {
      t: null,
      task_id: null,
    }
  },
  methods: {
    handleKill() {
      this.$axios.post(`${this.$settings.BASE_URL}user/sckill/`, {
        name: "萌萌抱枕",
      }).then(res => {
        if (res.data.code == 100) {
          alert("正在秒杀")
          this.task_id = res.data.task_id
          // 定时任务,定时向后端发送请求,获取结果
          this.t = setInterval(() => {
            this.$axios.post(`${this.$settings.BASE_URL}user/sckill/?task_id=${this.task_id}/`).then(res => {
              if (res.data.code == 100 || res.data.code == 101) {
                this.$message(res.data.msg)
                // 销毁定时器
                clearInterval(this.t)
                this.t = null
              } else {
                this.$message(res.data.msg)
              }
            })
          })
        }
      })
    }
  }
}
</script>
<style scoped>

</style>
  • lufycity_web\src\router\index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import SkillView from "@/views/SkillView.vue";

Vue.use(VueRouter)

const routes = [
    {
        path: '/',
        name: 'home',
        component: HomeView
    },
    {
        path: '/sckill',
        name: 'sckill',
        component: SkillView
    },

]

const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
})

export default router

【3】后端逻辑实现

  • luffyCity\luffyCity\apps\user\views.py
class SkillView(APIView):
    def post(self, request, *args, **kwargs):
        name = request.data.get('name')

        # 提交秒杀异步任务
        res = shill_goods.delay(name)
        return CommonResponse(task_id=str(res))

    def get(self, request, *args, **kwargs):
        back_dict = {"code": 200, "result": "处理完成"}
        # 查询状态
        task_id = request.query_params.get('task_id')
        result = AsyncResult(id=task_id, app=app)
        if result.successful():  # 正常执行完成
            result = result.get()  # 任务返回的结果
            if result:

                return CommonResponse(code=100, msg='秒杀成功')
            else:
                back_dict['code'] = 101
                back_dict['result'] = '秒杀失败'
            return back_dict
        elif result.failed():
            return CommonResponse(code=102, msg='任务失败')
        elif result.status == 'PENDING':
            return CommonResponse(code=201, msg='任务等待被执行')
        elif result.status == 'RETRY':
            return CommonResponse(code=301, msg='任务异常,正在重试')
        elif result.status == 'STARTED':
            return CommonResponse(code=202, msg='任务已开始')
        else:
            return CommonResponse(code=102, msg='秒杀任务异常')
  • luffyCity\luffyCity\apps\user\urls.py
from django.urls import path
from .views import UserView, SkillView
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('userinfo', UserView, 'userinfo')
urlpatterns = [
    # http://127.0.0.1:8000/api/v1/user/sckill/
    path('sckill/', SkillView.as_view())
]
urlpatterns += router.urls
  • luffyCity\celery_task\user_task.py
import random

from .celery import app
import time
from luffyCity.libs.SMS_TencentCloud_Sender import tencent_sms_main



@app.task
def shill_goods(name):
    # 开启事务 ---- 扣减库存 --- 生成订单
    # 模拟排队
    time.sleep(2)
    res = random.choice([100, 102])
    if res == 100:

        print(f'{name}的商品秒杀成功')
        return True
    else:
        print(f'{name}的商品秒杀失败')
        return False

【补充】双写一致性问题

  • 缓存数据和数据库数据不一致了
  • 双写一致性问题是指在使用缓存系统的情况下,当缓存数据和数据库数据发生不一致时,需要采取相应的策略来保证数据的一致性。

【1】写入数据库,删除缓存:

  • 这种策略是在写入数据库之后,主动删除相关缓存数据。
  • 当有需要读取该数据的请求时,缓存系统会重新从数据库中获取最新数据并进行缓存,确保缓存和数据库数据保持一致。
  • 这种策略适用于读取请求频率较高,而更新请求较低的场景。

【2】写入数据库,更新缓存:

  • 这种策略是在写入数据库之后,触发缓存更新操作,将最新的数据存入缓存中,确保缓存与数据库数据保持一致。
  • 当有读取请求时,可以直接从缓存中获取数据,提高读取性能。
  • 这种策略适用于数据的读取请求频率和更新请求频率都较高的场景。

【3】定时更新缓存:

  • 这种策略是通过定时任务或者其他方式,在一定的时间间隔内对缓存进行更新,将数据库中的数据同步到缓存中。
  • 定时更新可以根据数据的更新频率灵活设置更新时间间隔,适用于读取请求相对较低,而更新请求频率较高的场景。

【补充】缓存接口的更多方式

【1】封装成类

  • 继承某个类,自动加缓存

【2】封装成装饰器

  • 利用装饰器自动走,加缓存