接口缓存、定时更新、异步发送短信
目录
一、接口缓存
# 所有接口都可以改造,尤其是查询所有的这种接口,如果加入缓存,会极大的提高查询速度
# 首页轮播图接口:获取轮播图数据,加缓存--->咱们只是以它为例
增加接口缓存
from django.core.cache import cache
class BannerView(GenericViewSet, ListModelMixin):
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了(list在走数据库)
3 如果没有,走父类list,查询数据库
4 把返回的数据,放到缓存中
'''
data = cache.get('home_banner_list')
if not data: # 缓存中没有
print('走了数据库')
res = super().list(request, *args, **kwargs) # 查询数据库
# 返回的数据,放到缓存中
data = res.data.get('data') # {code:100,msg:成功,data:[{},{}]}
cache.set('home_banner_list', data)
return APIResponse(data=data)
# 公司里可能会这么写
-写一个查询所有带缓存的基类
-写个装饰器,只要一配置,就自动带缓存
# 双写一致性问题:缓存数据和数据库数据不一致了
-写入数据库,删除缓存
-写入数据库,更新缓存
-定时更新缓存
二、双写一致性之定时更新
# 一旦加入缓存,就会出现数据不一致的请请求
# 双写一致性问题
-1 改数据,删缓存
-2 改数据,改缓存
-3 定时更新 --- 接口的数据对于实时性要求不高
# 首页轮播图存在双写一致性问题这个问题
-第一种方法,以现在的技术水平,做不到 :改数据删缓存(--后期学习,通过信号机制改动)
-能选择的就是定时更新
-轮播图接口--->实时性要求没有那么高
celery的定时任务
定时更新轮播图任务:
1.查询数据库,拿到所有轮播图数据
2.序列化后,放到缓存中
启动worker、beta两条命令
celery_task/home_task.py
from home.models import Banner
from django.conf import settings
from home.serializer import BannerSerializer
from django.core.cache import cache
@app.task
def update_banner():
# 1.查询数据库,拿到所有轮播图数据
banner_list = Banner.objects.filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
# 2.序列化后
ser = BannerSerializer(instance=banner_list, many=True)
# 前端显示缓存中的路径格式是:/media/banner/banner1.png"
# 如果在视图类中,做序列化,因为视图类中有request对象,所以像图片这种,会自动加前面地址
# 在这里没有request对象,需要手动拼
for item in ser.data: # [{}, {}, {}]
item['image'] = settings.BACKEND_URL + item['image']
# 3.放到缓存中
cache.set('home_banner_list', ser.data)
return True
celery_task/celery.py
# 定时配置
from datetime import timedelta
app.conf.beat_schedule = {
'update_banner': {
'task': 'celery_task.home_task.update_banner', # 任务关联的函数
'schedule': timedelta(seconds=5), # 时间间隔是由公司来定制的可以,间隔一天,或间隔七天
'args': (),
},
}
启动worker、beta
(luffy) E:\python project\luffy_api>celery -A celery worker -l info -P eventlet
(luffy) E:\python project\luffy_api>celery -A celery_task beat -l info
三、异步发送短信
1.在celery_task中写发送短信的任务
2.需要导入短信模块,要记得导入django配置
3.在视图函数中提交异步任务delay(参数)
视图函数user/views.py
@action(methods=['GET'], detail=False)
# 发送短信接口
def send_sms(self, request, *args, **kwargs):
# 前端需要把要发送的手机号传入 在地址栏中
mobile = request.query_params.get('mobile', None)
# 判断手机号是否合法
if not re.match(r'^1[3-9][0-9]{9}$', mobile):
raise APIException('手机号不合法')
code = get_code() # 把code存起来,放到缓存中,目前在内存,后期换别的
cache.set('send_sms_code_%s' % mobile, code)
# 想要从缓存中取
# cache.get('send_sms_code_%s' % mobile)
if mobile:
# 手机号存在,开启线程(target是线程执行任务),异步发送信息
# thread = Thread(target=send_sms_by_mobile, args=[mobile, code])
# thread.start()
# 使用celery异步发送短信
res = send_sms_task.delay(mobile, code)
print(res) # res是id号,要做个任务表记录下id号
return APIResponse(msg='短信已发送')
raise APIException('手机号没有携带')
任务celery_task/user_task.py
from .celery import app
from libs.send_tx_sms import send_sms_by_mobile
@app.task
def send_sms_task(mobile, code):
res = send_sms_by_mobile(mobile, code)
if res:
return '%s的短信发送成功' % mobile
else:
return '%s的短信发送失败' % mobile
四、异步秒杀逻辑前后端
4.1 前端 Sckill.vue
<template>
<div class="skill">
<el-col :span="24">
<div class="grid-content bg-purple"><h2>go语言从入门到放弃</h2></div>
</el-col>
<el-button type="danger" @click="handleSkill">秒杀</el-button>
</div>
</template>
<script>
export default {
name: "Skill",
data() {
return {
task_id: '',
t: null,
}
},
methods: {
handleSkill() {
this.$axios.post(`${this.$settings.BASE_URL}`, {
name: "性感帽子", // 向后端发送商品信息跟用户信息
}).then(res => {
if (res.data.code === 100) {
alert('您正在排队')
// 加载图标
const loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
setTimeout(() => {
loading.close();
}, 2000);
// 获取id号
this.task_id = res.data.task_id
// 起定时任务
this.t = setInterval(() => {
this.$axios.get(`${this.$settings.BASE_URL}user/skill/?task_id=${this.task_id}`).then(res => {
// 100是成功,102是失败
if (res.data.code === 100 || res.data.code === 101) {
this.$message(res.data.msg)
// 销毁定时器
clearInterval(this.t)
this.t = null
} else {
console.log('过会再查')
}
})
}, 3000)
}
}
)
}
},
}
</script>
<style scoped>
.bg-purple {
background: #d3dce6;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
</style>
4.2 后端
视图类
from celery_task.user_task import skill_goods
from celery_task.celery import app
from celery.result import AsyncResult
class Skill(APIView):
def post(self, request, *args, **kwargs):
name = request.data.get('name')
# 提交秒杀异步任务
res = skill_goods.delay(name)
return APIResponse(task_id=str(res))
def get(self, request, *args, **kwargs):
task_id = request.query_params.get('task_id')
asy = AsyncResult(id=task_id, app=app)
if asy.successful(): # 正常执行完成
result = asy.get() # 任务返回的结果
if result:
return APIResponse(code=100, msg=('秒杀成功'))
else:
return APIResponse(code=101, msg=('秒杀失败'))
elif asy.status == 'STARTED':
print('任务已经开始被执行')
return APIResponse(code=103, msg='还在排队')
else:
return APIResponse(code=102, msg='没成功')
路由
urlpatterns = [
path('sckill/', SckillView.as_view()),
]
任务:celery_task/user_task.py
@app.task
def skill_goods(name):
# 逻辑是:开启事务 ---> 扣减内存 ---> 生成订单
import time
time.sleep(6)
res = random.choice([100, 102])
if res == 100:
print('%s被秒杀成功了' % name)
return True
else:
print('%s被秒杀失败了' % name)
return False
# 在celery_task的路径下,启动worker
celery -A celery_task worker -l info -P eventlet