celery 自定义数据持久化方案

文章主要以自己现在写的项目举例,提供自定义异步任务数据持久化的一个思路,具体如何执行还要参考每个项目的具体情况。

版本

  • Flask==0.10.1
  • celery==3.1.18

celery 自定义数据持久化方案

  • 由于当前使用MySQL存储任务即可完成日常检索需求,故使用MySQL来做任务的持久化。
  • 如果有复杂检索的需求,可以使用 elasticsearch 来存储数据,可以更方便的实现 UI 界面搜索,比如elasticsearch-head,Kibana, ElasticHD 等。


以下是MySQL的表结构:

create table mq_task_log
(
    id             int auto_increment comment '主键id'
        primary key,
    task_id        varchar(36)  default '' not null comment 'mq的task_id',
    task_name      varchar(128) default '' not null comment '调用的task',
    queue          varchar(36)  default '' not null comment '进入的queue',
    payload        text                    null,
    properties     varchar(256) default '' null,
    status         tinyint      default 0  not null comment '任务执行状态,0 初始 1 执行成功',
    is_compensated tinyint      default 0  not null comment '任务是否被补偿执行,0 否 1 是',
    relation_id    varchar(64)  default '' not null comment '关联的relation_id表,便于后期排查问题',
    relation_type  varchar(64)  default '' not null comment 'relation_id的关联关系',
    created_time   int          default 0  not null comment '创建时间',
    updated_time   int          default 0  not null comment '修改时间',
    constraint task_id
        unique (task_id)
)
    comment 'mq任务log表';

create index ix_mq_task_log_created_time
    on mq_task_log (created_time);

create index ix_mq_task_log_relation_id
    on mq_task_log (relation_id);

持久化数据

有了表,那数据何时写入呢?

以下提供两种方案:

  1. 可以自己封装一个新的调用异步任务的方法,以后在项目中使用统一格式的异步任务的调用。代码如下:
def high_priority_task(func, args=None, kwargs=None, queue=None, producer=None, link=None, link_error=None,
custom_relation_id='', custom_relation_type='',
**options):
    task_id = str(uuid.uuid4())
    # 执行已封装的,将任务信息写入数据库的function
    create_mq_task_log(func.__name__, 
        args=args, kwargs=kwargs,
        task_id=task_id, 
        queue=queue,
        custom_relation_id=custom_relation_id, custom_relation_type=custom_relation_type,
        )
    # 调用异步任务
    func.apply_async(
        args=args, kwargs=kwargs, 
        task_id=task_id, 
        producer=producer, 
        link=link, 
        link_error=link_error,
        queue=queue,
        **options)
  1. 若项目中已有大量的delay和apply_async,全部重写调用方式成本较大,此时可以选择重写 celery.app.task.Task.delay 和 celery.app.task.Task.apply_async,或给 delay/apply_async 添加自定义的插入log的装饰器,重写delay的代码参考如下:
class ContextTask(celery.Task):
    def __call__(self, *args, **kwargs):
        with app.app_context():
            return TaskBase.__call__(self, *args, **kwargs)
        
    def delay(self, *args, queue=None, custom_relation_id='', custom_relation_type='', **kwargs):
        task_id = str(uuid.uuid4())
        # 执行已封装的,将任务信息写入数据库的function
        create_mq_task_log(self.__name__, 
            args=args, kwargs=kwargs,
            task_id=task_id, 
            queue=queue,
            custom_relation_id=custom_relation_id, custom_relation_type=custom_relation_type,
            )
        # 调用异步任务
        func.apply_async(
            args=args, kwargs=kwargs, 
            task_id=task_id, 
            queue=queue)

# 实例化 Celery 之后,为实例指定新的Task类
celery = Celery(app.import_name, broker=app.config['CELERY_BROKER_URL'])
celery.Task = ContextTask
# 或 实例化celery的时候直接指定新的Task类
celery = Celery(app.import_name, broker=app.config['CELERY_BROKER_URL'], task_cls=ContextTask)

问:为什么选择在执行 apply_async 之前写入log呢?

答:此时,mq_task_log.is_compensated 字段就显得很重要了,该字段代表了任务是否真正的进入了 RabbitMq ,可以避免由于 RabbitMq 连接异常等原因造成的任务丢失。

如果是正常业务队列,可以实时消费掉,可以写一个定时脚本去校验是否出现任务丢失的情况,并更新is_compensated=1,然后进行任务的补偿执行。

task 执行结果的保存

  • 为了方便统一管理,这里对celery的task进行重写,统一处理任务处理结果的更新和任务异常的重试方案
  • 如果不想重写 task, 可以重写 celery.app.task.Task.on_failure 和 celery.app.task.Task.on_succes 来实现。


以下是我们项目中对celery.app.base.Celery.task 的重新封装,具体逻辑请看注释

class Celery(_celery.Celery):
    
    # 重写task的执行
    def task(self, *args_task, **opts_task):

        def decorator(func):
            sup = super(Celery, self).task

            @sup(*args_task, **opts_task)
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                from init import redis_store
                from models.mq_task_log_model import MqTaskLog
                try:
                    # 在执行任务之前验证任务的执行结果,并加锁,保障任务只会执行一次
                    task_id = wrapper.request.id
                    with RedLock(task_id, connection_details=[redis_store], ttl=60):
                        mq_task_log = MqTaskLog.find(task_id=task_id).first()
                        # 若任务已经执行,直接结束
                        if mq_task_log and mq_task_log.is_done:
                            return
                        # 执行任务
                        func(*args, **kwargs)
                except RedLockError:
                    return
                except Exception as exc:
                    # 任务重试方案,最多重试3次,每次间隔5秒钟
                    wrapper.retry(exc=exc, args=args, kwargs=kwargs, countdown=5, max_retries=3)
                else:
                    if mq_task_log:
                        # 这里是在model上实现了task_done方法,将status更新为1,表示任务执行成功
                        mq_task_log.task_done()

            return wrapper

        return decorator
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
能够灵活的添加爬虫任务,并且同时运行多站点的爬虫工作,所有组件都能够原生支持规模并发和分布式,加上celery原生的分布式调用,实现大规模并发。 爬虫(Web Crawler)是一种自动化程序,用于从互联网上收集信息。其主要功能是访问网页、提取数据并存储,以便后续分析或展示。爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的工作流程包括以下几个关键步骤: URL收集: 爬虫从一个或多个初始URL开始,递归或迭代地发现新的URL,构建一个URL队列。这些URL可以通过链接分析、站点地图、搜索引擎等方式获取。 请求网页: 爬虫使用HTTP或其他协议向目标URL发起请求,获取网页的HTML内容。这通常通过HTTP请求库实现,如Python中的Requests库。 解析内容: 爬虫对获取的HTML进行解析,提取有用的信息。常用的解析工具有正则表达式、XPath、Beautiful Soup等。这些工具帮助爬虫定位和提取目标数据,如文本、图片、链接等。 数据存储: 爬虫将提取的数据存储到数据库、文件或其他存储介质中,以备后续分析或展示。常用的存储形式包括关系型数据库、NoSQL数据库、JSON文件等。 遵守规则: 为避免对网站造成过大负担或触发反爬虫机制,爬虫需要遵守网站的robots.txt协议,限制访问频率和深度,并模拟人类访问行为,如设置User-Agent。 反爬虫应对: 由于爬虫的存在,一些网站采取了反爬虫措施,如验证码、IP封锁等。爬虫工程师需要设计相应的策略来应对这些挑战。 爬虫在各个领域都有广泛的应用,包括搜索引擎索引、数据挖掘、价格监测、新闻聚合等。然而,使用爬虫需要遵守法律和伦理规范,尊重网站的使用政策,并确保对被访问网站的服务器负责。
Celery使用自定义broker的方法是通过在配置文件中指定自定义broker的相关信息。首先,您需要确保已经安装了Celery的依赖库,可以使用pip命令进行安装。然后,在配置文件中添加自定义broker的配置信息,包括broker的类型、地址、端口等。接下来,您可以使用Celery提供的API来创建自定义broker的连接,并将其作为参数传递给Celery的实例化对象。这样,Celery就会使用您自定义的broker来进行消息的传递和处理。 引用\[1\]中提到了Celery自定义消息消费者的实践方法,可以通过继承Task类来自定义消息消费者。您可以根据自己的需求,实现自定义的消息消费逻辑,并在任务中调用相应的方法来处理消息。此外,引用\[2\]中提到了如果使用Redis作为中间人(Broker),需要安装Celery的依赖库,并在安装时指定redis选项。最后,引用\[3\]中提到了使用django-celery来配置delay异步调用,并且对比了使用rabbitmq和redis作为broker的优劣势。 综上所述,要使用自定义broker,您需要在配置文件中指定自定义broker的相关信息,并使用Celery提供的API来创建自定义broker的连接。同时,您可以根据需要继承Task类来自定义消息消费者,并使用相应的方法来处理消息。 #### 引用[.reference_title] - *1* [Celery 自定义消费器](https://blog.csdn.net/abbyinfo/article/details/120966731)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [celery中间件:broker](https://blog.csdn.net/weixin_44799217/article/details/111880904)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [为什么要使用celery,以及broker的选择标准](https://blog.csdn.net/weixin_45572139/article/details/106469805)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值