【21.0】结合celery改造接口
【一】引入
-
所有接口都可以改造,尤其是查询所有的这种接口,如果加入缓存,会极大的提高查询速度
-
首页轮播图接口:
- 获取轮播图数据,加缓存---》咱们只是以它为例
【二】改造轮播图接口
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】封装成装饰器
- 利用装饰器自动走,加缓存
本文来自博客园,作者:Chimengmeng,转载请注明原文链接:https://www.cnblogs.com/dream-ze/p/17642743.html