90、celery介绍架构和安装、celery执行异步任务、包结构celery、celery执行延迟任务和定时任务、django中使用celery、接口缓存

261 阅读12分钟

celery介绍架构和安装

介绍

1.celery :分布式的异步任务框架,主要用来做:
	1.异步任务
  2.延迟任务
	3.定时任务--->如果只想做定时任务,可以不使用celery,有别的选择(apscheduler)
    
2.celery 框架,原理
	1.可以不依赖任何服务器,通过自身命令,启动服务(内部支持socket)
	2.celery服务为为其他项目服务提供异步解决任务需求的
 	ps:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求

  """
    人是一个独立运行的服务 | 医院也是一个独立运行的服务
    正常情况下,人可以完成所有健康情况的动作,不需要医院的参与;但当人生病时,就会被医院接收,解决人生病问题
    人生病的处理方案交给医院来解决,所有人不生病时,医院独立运行,人生病时,医院就来解决人生病的需求
  """
3.celery架构
  1.消息中间件(broker):消息队列:可以使用redis,rabbitmq,咱们使用redis
  	-Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等
  2.任务执行单元(worker):真正的执行 提交的任务
  	-Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
  3.任务执行结果存储(banckend):可以使用mysql,redis,咱们使用redis
  	-Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等
    
4.使用场景
	1.异步执行:解决耗时任务
  2.延迟执行:解决延迟任务
	3.定时执行:解决周期(周期)任务

celery.png

安装

1.pip install Celery
	释放出可执行文件:celery,由于 python解释器的script文件夹在环境变量中,任意路径下执行celery都能找到
    
2.celery不支持win,所以想再win上运行,需要额外安装eventlet
	windows系统需要eventlet支持:pip3 install eventlet
  	celery -A 模块名 worker -l info -P eventlet
  	ps:celery -A demo worker -l info -P eventlet
	Linux与MacOS直接执行:
		3.x、4.x版本:celery worker -A demo -l info
    5.x版本:celery -A 模块名 worker -l info 

两种celery任务结构

提倡用包管理,结构更清晰

1.如果 Celery对象:Celery(...) 是放在一个模块下的
	1.终端切换到该模块所在文件夹位置:scripts/t_celery_task

	2.执行启动worker的命令:celery -A 模块名 worker -l info -P eventlet
	ps:windows系统需要eventlet支持,Linux与MacOS直接执行:celery -A 模块名 worker -l info 
	ps:模块名随意


2.如果 Celery对象:Celery(...) 是放在一个包下的
	1.必须在这个包下建一个celery.py的文件,将Celery(...)产生对象的语句放在该文件中
	2.执行启动worker的命令:celery -A 包名 worker -l info -P eventlet
	ps:windows系统需要eventlet支持,Linux与MacOS直接执行:celery -A 包名 worker -l info
	ps:包名随意

celery执行异步任务

基本使用

1.在虚拟环境中装celery和eventlet
	pip install Celery
  pip3 install eventlet
2.写个py文件,实例化得到app对象,注册任务(demo.py)
  from celery import Celery
  import time
  broker = 'redis://127.0.0.1:6379/1'  # 消息中间件 redis
  backend = 'redis://127.0.0.1:6379/2'  # 结果存储 redis
  app = Celery(__name__, broker=broker, backend=backend)
  @app.task # 变成celery的任务了
  def add(a, b):
    print('运算结果是',a + b)
    time.sleep(1)
    return a + b
  
3.启动worker(worker监听消息队列,等待别人提交任务,如果没有就卡再这)
	1.切换终端路径-->demo.py上的文件夹
  	cd scripts/t_celery_task
  2.执行:  
		celery -A demo worker -l info -P eventlet
        
4.别人提交任务,提交完成会返回一个id号,后期使用id号查询,至于这个任务有没有被执行,取决于worker有没有启动
    from demo import add
		res=add.delay(77,66)  # f3c11f0d-874f-45b7-b890-1f769b01504e
    
        
5.提交任务的人,再查看结果
  from demo import app
  # celery的包下
  from celery.result import AsyncResult

  id = '9e893a37-0b1b-43ef-b5a4-4c24e1b53eab'
  if __name__ == '__main__':
      a = AsyncResult(id = id,app=app)
      if a.successful():  # 正常执行完成
          result = a.get()  # 任务返回的结果
          print(result)
      elif a.failed():
          print('任务失败')
      elif a.status == 'PENDING':
          print('任务等待中被执行')
      elif a.status == 'RETRY':
          print('任务异常后正在重试')
      elif a.status == 'STARTED':
          print('任务已经开始被执行')

秒杀.png

代码

scripts
	-t_celery_task
    --add_task.py
    --add_task1.py
    --demo.py
    --get_result.py
  

demo.py

from celery import Celery
import time

broker = 'redis://127.0.0.1:6379/1'  # 消息中间件redis
backend ='redis://127.0.0.1:6379/2'  # 消息中间件redis
app = Celery(__name__,broker=broker,backend=backend)


@app.task # 变成celery任务
def add(a,b):
    print('运算结果',a+b)
    time.sleep(1)
    return a+b


@app.task
def send_sms(mobile,code):
    time.sleep(1)
    print('%s手机好码,发送短信成功,验证码是:%s'%(mobile,code))
    return True

add_task.py

from demo import add

# 同步运行,同步调用
# res = add(4,5)
# print(res)

 # 1.一步调用
# 一步调用--->执行下面,实际是把任务提交到消息队列中,返回一个id号,后期通过id查询任务执行结果,并没有执行,需要等待worker执行
res = add.delay(2,11)
print(res)  # 9e893a37-0b1b-43ef-b5a4-4c24e1b53eab

# 2.启动worker
# 启动了celery服务,以后不用启动了,只需要提交任务,workr会自动执行
# celery -A demo worker -l info


# 3.取结果中查看是否执行完成


add_task1.py

from demo import send_sms

res1 = send_sms.delay(18081245209,8888)
print(res1)

get_result.py

from demo import app
# celery的包下
from celery.result import AsyncResult

id = '9e893a37-0b1b-43ef-b5a4-4c24e1b53eab'
if __name__ == '__main__':
    a = AsyncResult(id = id,app=app)
    if a.successful():  # 正常执行完成
        result = a.get()  # 任务返回的结果
        print(result)
    elif a.failed():
        print('任务失败')
    elif a.status == 'PENDING':
        print('任务等待中被执行')
    elif a.status == 'RETRY':
        print('任务异常后正在重试')
    elif a.status == 'STARTED':
        print('任务已经开始被执行')

包结构celery

1.新建包:celery_task

2.在包下新建 celery.py 必须叫它,里面实例化得到app对象
    from celery import Celery
    broker = 'redis://127.0.0.1:6379/1'  # 消息中间件 redis
    backend = 'redis://127.0.0.1:6379/2'  # 结果存储 redis
    app = Celery(__name__, broker=broker, backend=backend, include=['celery_task.course_task','celery_task.home_task','celery_task.user_task'])
    
3.新建任务py文件:user_task.py   course_task.py  home_task.py
	ps:以后跟谁相关的任务,就写在谁里面
    from .celery import app  # 不要用 from celery import app
    """
    from .celery import app 表示从当前路径下的celery.py 中导入app
    from celery import app 表示从celery第三方包包下面导入app,没有
    """
    import time

    @app.task
    def add_course():
        print('课程增加成功')
        time.sleep(5)
        return '课程增加成'
          
4.启动worker,以包启动,来到包所在路径下
				1.切换到包的上一层:eg:cd scripts
        2.执行:celery -A 包名 worker -l info -P eventlet
        	ps:celery -A celery_task worker -l info -P eventlet

5.其它程序,导入任务,提交任务即可(add_task.py)
        from celery_task.user_task import send_sms
        res = send_sms.delay(1999999333, 8888)
        print(res)  # f33ba3c5-9b78-467a-94d6-17b9074e8533
      
6.其它程序,查询结果(get_result.py)
    from celery_task.celery import app
    # celery的包下
    from celery.result import AsyncResult

    id = '51a669a3-c96c-4f8c-a5fc-e1b8e2189ed0'
    if __name__ == '__main__':
        a = AsyncResult(id=id, app=app)
        if a.successful():  # 正常执行完成
            result = a.get()  # 任务返回的结果
            print(result)
        elif a.failed():
            print('任务失败')
        elif a.status == 'PENDING':
            print('任务等待中被执行')
        elif a.status == 'RETRY':
            print('任务异常后正在重试')
        elif a.status == 'STARTED':
            print('任务已经开始被执行')

代码

scripts
	-celery_task
  	--__init__.py
    --celery.py
    --course_task.py
    --home_task.py
    --user_task.py
  -add_task.py
  -get_result.py

celery.py

from celery import Celery


broker = 'redis://127.0.0.1:6379/1'  # 消息中间件 redis
backend = 'redis://127.0.0.1:6379/2'  # 结果存储 redis

app = Celery(__name__,broker=broker,backend=backend,include=["celery_task.course_task",'celery_task.home_task','celery_task.user_task'])

course_task.py

from .celery import app  # 不要用 from celery import app
"""
from .celery import app 表示从当前路径下的celery.py 中导入app
from celery import app 表示从celery第三方包包下面导入app,没有
"""

import time

@app.task
def add_course():
    print('课程增加成功')
    time.sleep(5)
    return '课程增加成'


user_task.py

from .celery import app
import time

@app.task
def send_sms(mobile,code):
    time.sleep(2)
    print('%s手机号,发送短信成功,验证码是:%s' % (mobile, code))
    return True

add_task.py

from celery_task.user_task import send_sms
# 1.异步任务
res = send_sms.delay(18081245209,888)
print(res)  # 34f5e0ec-bb35-49b8-8946-7cec7ad17844

get_result.py

from celery_task.celery import app
# celery的包下
from celery.result import AsyncResult

id = '34f5e0ec-bb35-49b8-8946-7cec7ad17844'
if __name__ == '__main__':
    a = AsyncResult(id=id, app=app)
    if a.successful():  # 正常执行完成
        result = a.get()  # 任务返回的结果
        print(result)
    elif a.failed():
        print('任务失败')
    elif a.status == 'PENDING':
        print('任务等待中被执行')
    elif a.status == 'RETRY':
        print('任务异常后正在重试')
    elif a.status == 'STARTED':
        print('任务已经开始被执行')

celery执行延迟任务和定时任务

1.celery 可以做
	1.异步任务
	2.延迟任务--->延迟多长时间干任务
	3.定时任务:每天12点钟,每隔几秒
	如果只做定时任务,不需要使用celery这么重,apscheduler(自己去研究)

异步任务

1.导入异步任务的函数
	from celery_task.user_task import send_sms
2.函数.delay(参数)
	res = send_sms.delay(18081245209,888)

add_task.py

from celery_task.user_task import send_sms

# 1.异步任务
res = send_sms.delay(18081245209,888)
print(res)  # 34f5e0ec-bb35-49b8-8946-7cec7ad17844

延迟任务

1.导入异步任务的函数
	from celery_task.user_task import send_sms
2.函数.apply_async(kwargs={'mobile':'1896334234','code':8888},eta=时间对象)

add_task.py

# 2.延迟任务,延迟5s后,在发短信
  # send_sms.apply_async(args=['18081245209',8888])
  # res = send_sms.apply_async(kwargs={'mobile':'18081245209','code':8888})  #args或kwargs都可以
from celery_task.user_task import send_sms
from datetime import datetime,timedelta
"""
print(datetime.now())  # 2023-06-29 16:35:51.262542
print(datetime.utcnow())  # 2023-06-29 08:35:51.262555
print(type(datetime.utcnow()))  # <class 'datetime.datetime'>-->它可以跟datetime.datetime 做相加
"""
eta = datetime.utcnow()+timedelta(seconds=5)
res = send_sms.apply_async(kwargs={'mobile':'18081245201','code':8888},eta=eta)
print(res)  # e1eabc1b-6d0c-4fab-a337-7b0e9e349998

定时任务

1.配置,在app所在的文件下配置--(celery.py)
  from datetime import timedelta
  from celery.schedules import crontab
	app.conf.beat_schedule = {
        'send_sms': {
            'task': 'celery_task.user_task.send_sms',
            'schedule': timedelta(seconds=5),
            'args': ('1822344343', 8888),
        },
        'add_course': {
            'task': 'celery_task.course_task.add_course',
            # 'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
            'schedule': crontab(hour=11, minute=38),  # 每天11点35,执行
            'args': (),
        }
    }
2.启动beat,启动worker
	celery -A celery_task worker -l info   # beat帮忙提交了任务
	celery  -A celery_task beat -l info 
3.到了时间,beat进程负责提交任务到消息队列--->worker执行

celery.py

from celery import Celery
broker = 'redis://127.0.0.1:6379/1'  # 消息中间件 redis
backend = 'redis://127.0.0.1:6379/2'  # 结果存储 redis
app = Celery(__name__,broker=broker,backend=backend,include=["celery_task.course_task",'celery_task.home_task','celery_task.user_task'])

# 3 定时任务 1 每隔5s干一次     2 每天的几点几分几秒干一次
# 时区
app.conf.timezone='Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False

# 任务的定时配置
from datetime import timedelta
from celery.schedules import crontab

app.conf.beat_schedule = {
    'send_sms': {
        'task': 'celery_task.user_task.send_sms',
        'schedule': timedelta(seconds=5),  # 每个五秒
        # 'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
        'args': ('1822344343', 8888),
    },
    'add_course': {
        'task': 'celery_task.course_task.add_course',
        # 'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
        'schedule': crontab(hour=16, minute=48),  # 每天11点35,执行
        'args': (),
    }
}

django中使用celery

使用步骤

1.把之前写好的包,copy到项目根路径下

2.在xx_task.py 中写任务
  from .celery import app
  @app.task
  def add_banner():
    from home.models import Banner
    Banner.objects.create(title='测试', image='/1.png', link='/banner', info='xxx',orders=99)
    return 'banner增加成功'
        
3.在celery.py 中加载django配置
	# 一、加载django配置环境
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffy_api.settings.dev")
    

4.视图函数中,导入任务,提交即可
  class CeleryView(APIView):
    def get(self, request):
      res = add_banner.delay()
      return APIResponse(msg='新增banner的任务已经提交了')
5.启动worker,等待运行即可

解释

1.celery中要使用djagno的东西,才要加这句话    
	import os
  os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffy_api.settings.dev")

代码演示

luffy_api
  -celery_task
    --__init__.py
    --celery.py
    --course_task.py
    --home_task.py
    --user_task.py
  -luffy_api
    --apps
    	---home
      	----views.py
				----urls.py

home_task.py

from .celery import app

@app.task
def add_banner():
    from home.models import Banner
    Banner.objects.create(title='yyyy', image='/1.png', link='/banner', info='xxxx',orders=99)
    return 'banner添加成功'

celery.py

from celery import Celery

# 一、加载django配置环境
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')

# 如果没有此时没有加载django配置环境,在terminal中启动cerely的时候,会报错,ps: from home.models import Banner ,ModuleNotFoundError: No module named 'home'

broker = 'redis://127.0.0.1:6379/1'  # 消息中间件 redis
backend = 'redis://127.0.0.1:6379/2'  # 结果存储 redis

app = Celery(__name__,broker=broker,backend=backend,include=["celery_task.course_task",'celery_task.home_task','celery_task.user_task'])

Views.py

from celery_task.home_task import add_banner
class CeleryView(APIView):
    def get(self,request):
        # 同步用
        # add_banner()

        # 异步用
        res = add_banner.delay()
        print(res)  # 1e5c8b71-acfb-4a84-92be-235d1fffaf6d
        return APIResponse(msg='新增banner的任务已经提交了')

接口缓存

1.所有接口都可以改造,尤其是查询所有的这种接口,如果加入缓存,会极大的提高查询速度
2.首页轮播图接口:获取轮播图数据,加缓存--->咱们只是以它为例
3.公司里可能会这么写
	写一个查询所有带缓存的基类
	写个装饰器,只要一配置,就自动带缓存
4.双写一致性问题:缓存数据和数据库数据不一致了
	1.写入数据库,删除缓存
	2.写入数据库,更新缓存
	3.定时更新缓存

代码演示

from rest_framework.viewsets import GenericViewSet
from .models import Banner
from django.conf import settings
from .serializer import BannerSerializer
from utils.common_mixin import CommonListModelMixin as ListModelMixin
from django.core.cache import cache
class BannerView(GenericViewSet,ListModelMixin):
    queryset = Banner.objects.filter(is_delete=False,is_show=True)[: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')
            cache.set('home_banner_list',data)
        return APIResponse(data=data)

作业

写一个查询所有带缓存的基类

utils/ [common_mixin.py]

from utils.common_mixin import CacheCommonListModelMixin as CacheListMOdelMIxin
class BannerView1(GenericViewSet,CacheListMOdelMIxin):
    queryset = Banner.objects.filter(is_delete=False,is_show=True)[:settings.BANNER_COUNT]
    serializer_class = BannerSerializer
    # 写一个查询所有带缓存的基类
    cache_backends = "home_banner_list"

views.py

class CacheCommonListModelMixin(ListModelMixin):
    cache_backends=None
    def list(self, request, *args, **kwargs):
        if not self.cache_backends:
            raise APIException('视图类中必须有cache_backends类属性')
        data =cache.get(self.cache_backends)
        if not data:
            # 取数据库中查询
            print('数据库----')
            res =super().list(request,*args,**kwargs)
            print(res.data)
            # 返回的数据,放到缓存中
            data = res.data
            cache.set(self.cache_backends,data)
        return APIResponse(data=data)

写个装饰器,只要一配置,就自动带缓存

Utils/ [commin_wrapers.py]

from utils.common_response import APIResponse
from django.core.cache import cache
def outerest(name):
    def cache_show_list(func_name):
        def inner(request,*args,**kwargs):
            # 需要判断cache中有没有

            print(name)
            data = cache.get(name)
            if not data:
                # 如果没有,就执行原来的list函数
                res = func_name(request,*args,**kwargs)
                data = res.data.get('data')
                # 存到缓存中
                cache.set(name,data)
                return res
            return APIResponse(data=data)
        return inner
    return cache_show_list

Views.py

from utils.commin_wrapers import outerest
class BannerView2(GenericViewSet,ListModelMixin):
    queryset = Banner.objects.filter(is_delete=False,is_show=True)[:settings.BANNER_COUNT]
    serializer_class = BannerSerializer
    @outerest('home_banner_list')       # list = cache_show_list(list)
    def list(self, request, *args, **kwargs):
        print(1111)
        res = super().list(request, *args, **kwargs)
        return APIResponse(data=res.data)