luffy-(10)

内容概览

  • celery快速使用
  • celery包结构
  • celery异步任务,延迟任务,定时任务
  • django中使用celery
  • 秒杀逻辑
  • 双写一致性

celery快速使用

# 1 celery官网:http://www.celeryproject.org/
# 2 介绍:Celery is a project with minimal funding, so we don’t support Microsoft Windows. Please don’t open any issues related to that platform

# 3 celery是独立的服务
"""
1)可以不依赖任何服务器,通过自身命令,启动服务
2)celery服务为为其他项目服务提供异步解决任务需求的
注:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求
"""
1. 安装
	pip install celery
	
2. 写一个main.py,实例化得到app对象
	from celery import Celery
	
	backend = 'redis://127.0.0.1:6379/1'  # 完成的任务存放的位置
	broker = 'redis://127.0.0.1:6379/0'  # 提交的任务存放的位置
	app = Celery('test', backend=backend, broker=broker)

3. 写个任务,注册成celery的任务
	# 写个任务(函数),加个装饰器,变成celery的任务
	@app.task
	def add(a, b):
	    time.sleep(2)  # 设置任务执行时长为2秒
	    return a + b

4. 在其他程序中提交任务(创建一个新的py文件)
	from main import add
	
	# 执行同步任务
	res = add(3, 4)
	print(res)
	
	# 执行异步任务,将任务提交到celery中
	res = add.delay(5, 6)
	print(res)  # c6bbe7c0-ef2b-49a8-b560-c2e9d45dfa01 返回一个唯一的UUID

5. 启动worker,从broker中获取任务执行,执行完成后放到backend中
	win:    
		celery worker -A main -l info -P eventlet  # 4.x及之前用这个 
		celery -A main worker -l info -P eventlet  # 5.x及之后用这个
		# 如果没有eventlet模块需要安装:pip install eventlet
	lin,mac: 
		celery worker -A main -l info
		celery -A main worker -l info

6. 在backend中查看任务执行的结果
	- 直接在数据库中查看
	- 通过执行代码查看
		from main import app
		from celery.result import AsyncResult
		
		id='c6bbe7c0-ef2b-49a8-b560-c2e9d45dfa01'  # 执行任务返回的UUID
		res = AsyncResult(id=id, app=app)
		if res.successful():
		    result = res.get()  # 获取任务执行结果
		    print(result)
		elif res.failed():
		    print('任务失败')
		elif res.status == 'PENDING':
		    print('任务等待中被执行')
		elif res.status == 'RETRY':
		    print('任务异常后正在重试')
		elif res.status == 'STARTED':
		    print('任务已经开始被执行')

celery包结构

# 写一个celery的包;以后在任意项目中想用,把包copy进去,导入使用即可
"""
项目:
	celery_task
	    __init__.py
	    celery.py
	    user_task.py
	    home_task.py
	add_task.py
	get_result.py
"""


# 使用步骤
1. 新建包:celery_task
2. 在包里新建一个celery.py
3. 在celery.py里写app的初始化
4. 在包里新建user_task.py 编写用户相关的任务
5. 在包里新建home_task.py 编写首页相关的任务
6. 在其他程序里,提交任务
7. 启动worker(因为是独立的服务,所以也可以先启动);在包所在的目录路径下:celery -A celery_task worker -l info -P eventlet
8. 查看任务执行的结果
celery_task/celery.py
from celery import Celery

backend = 'redis://127.0.0.1:6379/1'
broker = 'redis://127.0.0.1:6379/0'
# 一定不要忘了include
app = Celery(__name__, broker=broker, backend=backend,include=['celery_task.home_task','celery_task.user_task'])
celery_task/home_task.py
from .celery import app
@app.task
def add(a, b):
    time.sleep(3)
    print('计算结果是:%s' % (a + b))
    return a + b
celery_task/user_task.py
import time
from .celery import app
@app.task
def send_sms(mobile, code):
    time.sleep(1)
    print('短信发送成功:%s,验证吗是%s' % (mobile, code))
    return True
add_task.py
from celery_task.user_task import send_sms
# 提交了一个发送短信异步任务
res=send_sms.delay('18723345455','9999')
print(res)  # 672237ce-c941-415e-9145-f31f90b94627

# 任务执行,要启动worker

# 查看任务执行的结果
get_result.py
# 查询执行完的结果
from celery_task.celery import app

from celery.result import AsyncResult

id = '672237ce-c941-415e-9145-f31f90b94627'
if __name__ == '__main__':
    res = AsyncResult(id=id, app=app)
    if res.successful():
        result = res.get()  #7
        print(result)
    elif res.failed():
        print('任务失败')
    elif res.status == 'PENDING':
        print('任务等待中被执行')
    elif res.status == 'RETRY':
        print('任务异常后正在重试')
    elif res.status == 'STARTED':
        print('任务已经开始被执行')

celery异步任务,延迟任务,定时任务

"""异步任务"""
# 任务.delay(参数, 参数)
res=send_sms.delay('18723345455','9999')

"""延迟任务"""
# 任务.apply_async(args=[参数,参数],eta=时间对象)  默认是使用utc时间
res = send_sms.apply_async(args=['18723345455', '9999'], eta=datetime.utcnow() + timedelta(seconds=5))  # 5秒后执行任务

"""定时任务"""
1. app的配置文件中配置
	from celery.schedules import crontab
	from datetime import timedelta
	# 定时任务配置
	app.conf.beat_schedule = {
	    'send_sms_task': {
	        'task': 'celery_task.user_task.send_sms',
	        'schedule': timedelta(seconds=5),  # 每5秒钟执行一次
	        'args': ('1897334444', '7777'),
	    },
	    'add_task': {
	        'task': 'celery_task.home_task.add',
	        'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
	        'args': (10, 20),
	    }
	}
2. 启动worker:执行任务
	celery -A celery_task worker -l info -P eventlet
3. 启动beat:定时提交任务
	celery -A celery_task beat -l info

django中使用celery

1. 把写好的包复制到项目路径下
2. 在包中的celery.py的上面加入代码
	import os
	import django
	
	os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
	django.setup()
3. 在django的视图类中导入,提交任务
4. 启动worker、beat

秒杀逻辑

前端
1. 秒杀按钮
2. 按钮事件:向后端秒杀接口发送请求,发送后生成一个定时任务,每过5秒向后端发送请求查看是否秒杀成功;如果秒杀成功或者失败了,销毁定时任务,并弹窗提示;如果秒杀任务还在执行,不做任何操作,5秒后再次查询

<template>
  <el-button type="primary" @click="handleClick">秒杀</el-button>
</template>

<script>
export default {
  name: "Seckill",
  methods: {
    handleClick() {
      this.$axios.get(this.$settings.BASE_URL + 'userinfo/seckill/').then(res => {
        if (res.data.code === 100) {  // 任务已提交
          this.$message({
            message: res.data.msg,
            type: 'success'
          });
          // 生成一个定时任务
          let t = setInterval(() => {
            this.$axios.get(this.$settings.BASE_URL + 'userinfo/get_result/?uid=' + res.data.uid).then(res => {
              if (res.data.code === 100 || res.data.code === 101) {  // 成功或失败
                this.$message({
                  showClose: true,
                  message: res.data.msg
                });
                // 成功或失败了,都需要销毁定时任务
                clearInterval(t)
              }
            })
          }, 5000)  // 每5秒查询一次
        }
      })
    }
  }
}
</script>

<style scoped>

</style>
后端

user_task

import time
import random
from .celery import app

@app.task
def seckill_task():
    time.sleep(10)
    res = random.choice([True,False])  # 模拟成功或失败
    return res
1. 秒杀接口
	def seckill(request):
		# 提交秒杀任务
	    res = seckill_task.delay()
	    return JsonResponse({'code': 100, 'msg': '正在排队', 'uid': str(res)})

2. 查询是否秒杀成功接口,通过用户传入的uid查询任务是否成功
	def get_result(request):
	    uid = request.GET.get('uid')
	    res = AsyncResult(id=uid, app=app)
	    if res.successful():
	        result = res.get()
	        return JsonResponse({'code':100, 'msg': str(result)})
	    elif res.failed():
	        return JsonResponse({'code': 101, 'msg': '秒杀失败'})
	    elif res.status == 'PENDING':
	        return JsonResponse({'code': 102, 'msg': '正在排队'})

双写一致性

接口加缓存
# 首页轮播图接口,加缓存
# 提交了接口的响应速度
# 提高并发量
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):
        result = cache.get('banner_list')
        if result:  # 缓存里有
            print('走了缓存,速度很快')
            return APIResponse(result=result)
        else:
            # 去数据库拿
            print('走了数据库,速度慢')
            res = super().list(request, *args, **kwargs)
            result = res.data.get('result')  # {code:100,msg:成功,result:[{},{}]}
            cache.set('banner_list', result)  # 添加到缓存
            return res
"""
加了缓存,如果mysql数据变了,由于请求的都是缓存的数据,导致mysql和redis的数据不一致
解决方法
	1. 修改mysql数据库,删除缓存  【缓存的修改是在后】
	2. 修改数据库,修改缓存    【缓存的修改是在后】
	3. 定时更新缓存   ---> 针对于实时性不是很高的接口适合定时更新

# 给首页轮播图接口加入了缓存,出现了双写一致性问题,使用定时更新来解决双写一致性的问题【会存在不一致的情况,我们可以忽略】---> 定时任务,celery的定时任务
"""
使用celery定时任务实现双写一致性

home_task.py

from .celery import app
from django.conf import settings
from home.models import Banner
from home.serializers import BannerSerializer
from django.core.cache import cache

@app.task
def update_banner():
    # 更新缓存
    # 查询出现在轮播图的数据
    queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
    ser = BannerSerializer(instance=queryset, many=True)
    # 数据库中图片的地址不是完整的地址,需要我们添加
    for item in ser.data:
        item['image'] = settings.HOST_URL + item['image']
    cache.set('banner', ser.data)
    return True

celery.py

import os
import django

from datetime import timedelta
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
django.setup()

backend = 'redis://127.0.0.1:6379/1'
broker = 'redis://127.0.0.1:6379/0'

app.conf.beat_schedule = {
    'add_task': {
        'task': 'celery_task.home_task.update_banner',
        'schedule': timedelta(seconds=30),
        'args': (),
    }
}

你可能感兴趣的:(django,python,后端)