celery的使用、异步任务、延迟任务、定时任务、django中使用celery秒杀,以及用celery定时任务解决双写一致性

celery介绍

celery是一个分布式的异步任务框架

celery的作用

  • 完成异步任务:可以提交项目的并发量,之前开启线程做,现在使用celery做
  • 完成延迟任务
  • 完成定时任务

架构

消息中间件:broker提交的任务(函数)都放在这里,celery本身不提供消息中间件,需要借助于第三方:redis,rabbitmq

任务执行单元:worker,真正执行任务的地方,worker并发的运行在分布式的系统节点中

任务结果存储:backend,函数return的结果存储在这里,celery本身不提供结果存储,借助于第三方:redis,数据库,rabbitmq

celery快速使用

celery官网:http://www.celeryproject.org/

celery官方文档英文版:http://docs.celeryproject.org/en/latest/index.html

celery官方文档中文版:http://docs.jinkan.org/docs/celery/

celery是一个简单的、灵活且可靠的,处理大量消息的分布式系统,专注于实时处理的异步任务队列,同时也支持任务调度

celery是一个独立的服务

  1. 可以不依赖任何服务器,通过自身命令,启动服务
  2. celery服务为其他项目提供异步解决任务需求的

注:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务提交给celery服务,celery就会在需要时异步完成项目的需求。

安装

pip install celery

使用步骤

  1. 写一个main.py:实例化得到app对象,写一个函数,函数内写任务逻辑,并注册成celery的任务,通过装饰器
  2. 在别的程序中:提交任务,会提交到broker中去了,提交代码是函数名.delay(参数)
  3. 启动worker,从broker中取任务执行,执行完放到backend中
# windows中执行worker的命令:
	celery  worker -A main  -l info -P eventlet    # 4.x及之前用这个,需要下一个eventlet
  celery -A main worker -l info -P eventlet      # 5.x以及之后用这个
  
# linux,mac执行worker的命令:
  celery -A main worker -l info
  celery -A main worker -l info
  1. worker执行完某任务函数,会将执行结果放到backend对应的redis库中,我们可以再该库中查看任务执行的结果
# 也可以通过代码查看打印的结果

    from main import app
        from celery.result import AsyncResult
        id = '7bef14a0-f83e-4901-b4d8-e47aaac09b39'  # 唯一字符串
        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包结构

写一个celery的包,以后,再任意项目中,想用,把包copy进去,导入使用即可。

项目结构

├── add_task.py
├── celery_task
│   ├── __init__.py
│   ├── celery.py
│   ├── home_task.py
│   └── user_task.py
└── get_result.py

使用步骤

  1. 新建包:celery_task,在包里先新建一个celery.py,在里面写app的初始化
  2. 在包里新建user_task.py,编写用户相关任务
  3. 在包里新建home_task.py,编写首页相关任务
  4. 提交任务
  5. 启动worker,它可以先启动,在提交任务之前,需要将当前路径切换到包所在的目录下
  6. 最后就可以查看任务执行的结果了

celery_task/celery.py

from  celery import Celery

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

# 一定不要忘了include,意思就是celery会管理include添加的py文件
app=Celery(__name__,broker=broker,backend=backend,include=['celery_task.home_task','celery_task.user_task'])

celery_task/home_task.py

import time
from .celery import app

@app.task
def add(a,b):
    time.sleep(3)
    print(f'计算结果是:{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(5)
    print(f'短信发送成功:{mobile},验证码是{code}')
    return True

add_task.py

from celery_task.user_task import send_sms

# 提交了一个发送短信异步任务
res=send_sms.delay('18015011823','8542')
print(res)  # 唯一字符串,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(*参数)

延迟任务:任务函数.apply_async(args=[参数,参数],eta=时间对象(utc时间))

定时任务:如下(定时任务配置)

1. app的配置文件中配置
  	app.conf.beat_schedule = {
                'send_sms_task': {
                    'task': 'celery_task.user_task.send_sms',
                    'schedule': timedelta(seconds=5),
                    # 'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
                    'args': ('18015011823', '7777'),
                },
                'add_task': {
                    'task': 'celery_task.home_task.add',
                    'schedule': crontab(hour=12, minute=10, day_of_week=3),  # 每周一早八点
                    'args': (10, 20),
                }
            }
 2. 启动worker:真正干货的人
     celery -A celery_task worker -l info [-P eventlet]
 3. 启动beat:提交任务的人
     celery -A celery_task beat -l info  # window 也不需要加-P eventlet

django中使用celery

使用步骤

  1. 把写好的包复制到项目路径下
  2. 在包内的celery.py的上面加入代码
# 不需要启动django,就能在django内执行py文件下的代码
import os
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
        import django
        django.setup()
  1. 在django的视图类中,导入,提交任务
  2. 启动worker,beat

秒杀逻辑

前端

  1. 创建一个秒杀按钮
  2. 通过点击秒杀按钮,向后端秒杀接口发送请求,发送完立马起了一个定时任务,每隔5秒,向后端查看一下是否秒杀成功,定时任务继续执行,如果秒杀成功了,清空定时任务,弹窗告诉他

代码演示

 handleClick() {
            // console.log(123)
            this.$axios.get(this.$settings.BASE_URL + 'home/index/').then(res => {
                let task_id = res.data.result
                this.$message({
                    message: res.data.msg,
                    type: 'success'
                })
                // 起个定时任务,每隔3秒向后端查询一下是否秒杀成功
                let s = setInterval(() => {
                    this.$axios.get(this.$settings.BASE_URL + `home/get_result/?id=${task_id}`).then(
                        res => {
                            if (res.data.code === 100||res.data.code===101||res.data.code===103) {
                                this.$message(res.data.msg)
                                // 销毁掉定时任务
                                clearInterval(s)
                            } else if (res.data.code === 102) {
                                
                            } else {
                            }
                        }
                    )
                }, 3000)
            })
        }

后端

重点:由于采用了django的反射机制,使用celery.py所在的celery_task包必须放置项目的根目录下

1. 点击秒杀接口
def index(request):
    print('访问了')
    res=seckill.delay()
    return JsonResponse(data={'code':100,'result':str(res),'msg':'正在排队中'},safe=False)

2. 查询是否秒杀成功的接口

根据用户传入的id,查询任务是否成功,这个id就是提交任务delay任务返回的唯一字符串
def get_result(request):
    task_id=request.GET.get('id')
    res = AsyncResult(id=task_id, app=app)
    if res.successful():
        result = res.get()  # 7
        print(result)
        if result:
            return JsonResponse(data={'code':100,'result':str(result),'msg':'秒杀成功'})
        else:
            return JsonResponse(data={'code':101,'result':str(result),'msg':'很可惜,这次没抢到'})
    elif res.failed():
        print('任务失败')
        return JsonResponse(data={'code':103,'result':'任务失败','msg':'对不起,服务器出错,秒杀失败'})
    elif res.status == 'PENDING':
        print('任务等待中被执行')
        return JsonResponse(data={'code': 102, 'result': '', 'msg': '正在排队中,请等待'})
    elif res.status == 'RETRY':
        print('任务异常后正在重试')
    elif res.status == 'STARTED':
        print('任务已经开始被执行')

双写一致性

接口加缓存

首页轮播图接口,加缓存,提交了接口的响应速度,提交了并发量

代码演示

class BannerView(GenericViewSet,LuffyListModelMixin):
    serializer_class = BannerModelSerializer
    queryset = Banner.objects.filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]
    def list(self, request, *args, **kwargs):
        queryset = cache.get('banner_list')  # 
        if queryset:
            print('走了缓存')
            return ApiResponse(result=queryset)

        else:
            print('走了数据库')
            res=super(BannerView, self).list(request, *args, **kwargs)
            cache.set('banner_list',res.data)
            return res

如上代码可知:第一次取不到,会走数据库查,查完后放入缓存中,后续就可以走缓存。

但是加了缓存之后,如果mysql数据变了,由于请求的都是缓存的数据,导致mysql和redis的数据不一致,就会导致双鞋不一致问题。

双写不一致问题的解决方案

  • 修改mysql数据库,删除缓存;缓存的修改是在后
  • 修改数据库,修改缓存;缓存的修改是在后
  • 定时更新缓存;针对于实时性不是很高的接口适合定时更新

给轮播图接口加入了缓存,出现嘞双写一致性问题,使用定时更新来解决双写一致性问题,会存在不一致的情况,但是这种情况不会对网站造成影响,可以忽略。使用celery的定时任务

celery定时任务实现双写一致性

home_task.py

@app.task
def update_banner():
    queryset = Banner.objects.filter(is_delete=False, is_show=True).order_by('orders')[:3]
    ser=BannerModelSerializer(queryset,many=True)
    cache.set('banner_list',ser.data)
    return True

celery.py

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

celery中app.task和shared.task区别

他两作用一样,第一个app.task需要导入app,第二个直接导入使用,直接从内存中取出来app对象。

apscheduler

如果公司只是想做定时任务,celery比较大,比较麻烦,一般公司会使用apscheduler

安装

pip install apscheduler

任务函数以及提交任务

每隔5秒提交一次任务

 def my_job(i):
        print (i)
    sched = BlockingScheduler()
    sched.add_job(my_job, 'interval', seconds=5,values=['学会了'])

按年月日提交任务

 import datetime
    from apscheduler.schedulers.blocking import BlockingScheduler
    scheduler = BlockingScheduler()
    def my_job(text):
        print(text)
    # datetime类型(用于精确时间)
    scheduler.add_job(my_job, 'date', run_date=datetime(2022, 4, 25, 17, 30, 5), args=['测试任务'])

按corn提交任务

    import datetime
	from apscheduler.schedulers.background import BackgroundScheduler
    def job_func(text):
        print("当前时间:", datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3])

    scheduler = BackgroundScheduler()
    # 在每年 1-3、7-9 月份中的每个星期一、二中的 00:00, 01:00, 02:00 和 03:00 执行 job_func 任务
    scheduler .add_job(job_func, 'cron', month='1-3,7-9',day='0, tue', hour='0-3')

重点

如果在别的机器上运行worker,需要把项目完整copy过去,连接同一个redis数据库。

posted @ 2022-11-16 23:24  荀飞  阅读(672)  评论(0)    收藏  举报