V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
lianghui
V2EX  ›  分享创造

分享一个分布式定时任务系统 ( python)

  •  1
     
  •   lianghui ·
    whiteclover · 2014-12-09 10:19:36 +08:00 · 14504 次点击
    这是一个创建于 3640 天前的主题,其中的信息可能已经有所发展或是发生改变。

    分布式任务系统

    github地址 https://github.com/thomashuang/Eden

    这里将介绍Eden的设计架构,首先分布式任务系统的定义是在多台服务器执行定时任务。

    实现技术

    1. 分布式定时调度,可以同时在不同服务执行。
    2. 使用Leader/Follower Pattern 多线程模式。
    3. 只是周期定时,crontab定时,定点任务。
    4. 重试失败任务。
    5. 线程安全db api,支持读写分离模式
    6. 使用data-mapper模式
    7. web ui化管理工具(使用MVC,基于 mako gevent cherrypy)

    task

    定时任务的类型

    一次性定时任务

    在将来某个时间点需要执行一次

    周期性任务

    对于周期性任务最为简单的就是间隔性执行任务,比如每五分钟需要执行一次。

    如何做到分布式任务

    从分布式的理解来看,需要一种机制保证正常给各个任务执行服务准确及时的分派任务,并却要减少各个服务之间竞争。于是最简单的任务执行者向任务管理服务所要当前可以执行服务。回分派相关任务,当执行任务完需要汇报给任务调度管理服务,并筹划任务下一步的状态。但这里设计原理并非如此,对于任务管理器而言,只是作任务的的长久序列化,对于怎么提取,任务的执行状态(完成,失败,重试等),都由任务执行者决定。

    分派流程

    1. 定时服务生成唯一task标识符,依据下次执行时间在db标记任务。
    2. 依据task标识符获取可执行任务。
    3. 执行任务,保存刷新最新状态。

    使用MYSQL如何做到任务的分派

    使用关联数据数据,而非内存数据库比如heaq做任务调度,可能有些开始让人难以理解。现在有很多任务执行服务,在领取任务时,首先生成一个一个具有唯一性标识的(uuid)的对mysql的cron表做更新 UPDATE cron set task_id=$identify WHERE task_id IS NULL AND next_run<now() LIMIT $take_size; 这样标识了当前服务的可执行的任务。然后使用SELECT × FROM cron WHERE task_id=$identify, 于是就能拉当前服务可以执行任务。然后对于任务的是什么,该怎么执行。

    任务线程池的设计与优化

    对于scheduler任务的执行,因为一次认领多个任务,最初是想使用生产消费者模式,如果master线程与工人线程共享队列,会造成任务竞争激烈。于是优化成每个工人线程拥有自己的任务队列,master线程轮询分派任务,但这样,减少了线程对资源的竞争,但在空闲时,如果没有特殊处理可能造成cpu的占用。最后使用的是一种leader follower的变种。master线程仅负责分派任务。 follower将在挂起与任务执行(processing)状态转换。另外这样可能任务众多,如果线程池没有空闲线程怎么办,办法是将这些任务放入一个闲置队列,加入一个心跳(定时器)线程负责闲置任务的再次分配。对于python而言,真正的优化在于减少任务空闲时对cpu的抢占。

    数据层

    最初设计时,考虑到对多数据库的支持。首先使用记录(activerecord)模式,这种模式把模型与数据落地放置与同一个类,随着业务的复杂后,模式优势越来越小,造成数据落地层行为与模型行为的混淆。为了维护性与后期的拓展性。最终采用了data-mapper模式,这样在拓展多数据库支持时,仅仅考虑数据落地层的接口重写,同样也为后期的数据库接口优化提供了空间。

    值得一提的是,在使用mysql作为数据库,因为使用mysqldb所以需要简单的封装一个连接池,并简化接口的使用。db模块有三个重要的接口setup 将帮助设置数据库, query将用于select的查询,execute是用来做增删改的语句,因为这样更方便数据库的读写分离。

    setup

    setup将用来注册数据库,如果指定slave为true,这个数据库将会注册到读数据库
    key用来标示数据库,默认注册到default。
    需要注意的是你必须先注册一个master数据库才能注册从数据库。

    query

    用于select的查询语句,指定many时将会使用mydqldb的fetchmany接口,key用来标示数据库,默认为default。

    execute

    对于insert语句将会返回lastwordid
    而update与delete将会是rowcount。

    web 管理平台

    web管理平台使用的cherrypy与routes mako实现,考虑到cherrypy也许有些慢。于是给cherypy打上猴子补丁,而使用gevent的wsgiserver。 功能基本实现了简单的用户角色和任务管理功能。

    11 条回复    2016-12-06 14:31:59 +08:00
    zzNucker
        1
    zzNucker  
       2014-12-09 10:24:05 +08:00
    用MySQL,比起RabbitMQ这样的有什么优势吗? 所以,这个比celery有什么优势?
    lianghui
        2
    lianghui  
    OP
       2014-12-09 10:30:06 +08:00
    @zzNucker 这个现在只是定时任务调度,还没有实现queued任务分派,(想法是使用zeromq或者rabbitmq实现)。 mysql对于成千上万的定时任务绝对是够用的。为了考虑到更多定时任务和更大负载,将会加入mongodb的适配mapper,以加快优化定时任务调度,加大任务的负载。如果使用mysql做queued调度也是可行的,但是性能会不如使用消息系统。所以在考虑queued任务作为消息系统实现。
    zzNucker
        3
    zzNucker  
       2014-12-09 10:37:58 +08:00
    @lianghui 嗯,再观望吧,看了下API还是比celery复杂了。
    lianghui
        4
    lianghui  
    OP
       2014-12-09 10:41:27 +08:00
    @zzNucker 任务的注册管理App类借鉴的的wsgi类似模式。 就是:


    def app(task): pass # to do something

    # most like wsgi: def app(environ, start_response): pass

    所以理解wsgi应该比较好懂。
    9hills
        5
    9hills  
       2014-12-09 11:08:55 +08:00
    支持一下,原先想写一个基于Redis的分布式任务调度器。。挖了个坑然后没填

    P.S. 分享一下厂内的分布式Crontab的三大要点

    1. 完全兼容crontab,并提供Restful的API供调用。感觉这种比绑定Python要好
    2. 本地cache,即Client和Master网络中断后,Client依然可以执行本地cache的Crontab。避免了因为Master Down掉导致cron任务没有正常执行
    3. 完善的报警体系,任务超时,任务缺失调度等,基于时间和错误次数的报警屏蔽


    做Crontab最重要的一点就是任务不能缺失调度,这点尤其重要。。
    9hills
        6
    9hills  
       2014-12-09 11:11:09 +08:00
    celery/rq 和这种crontab是两回事,crontab是保证所有机器在某个时间点必须执行某个同样job

    而celery和rq本质是生产者-消费者模型,和crontab是两种需求
    lianghui
        7
    lianghui  
    OP
       2014-12-09 11:44:47 +08:00
    @9hills 是的,只是支持crontab的主要时间格式,对于消息系统消费模式,queued任务现在没有实现,主要实现定时任务调度。对于本地cache任务并不好做,因为使用的分布式任务调度,对于超时已经实现处理。

    对于报警的考虑是因为已经有web tool,用户注册时会有email就可以通过email报警,或者使用一个特定邮箱统一报警。另外日志的监控这块也是会考虑。现在主要做的是一个模型。
    c4pt0r
        8
    c4pt0r  
       2014-12-10 09:53:22 +08:00
    如果和 mesos 结合起来就更棒了 :) 。。。话说我前段时间刚好做过这么一个东西~ 不同的是,是底下的 runner 是 mesos slave。

    http://github.com/wandoulabs/tyrant
    lianghui
        9
    lianghui  
    OP
       2014-12-10 10:27:21 +08:00
    @c4pt0r 嗯 mesos 有一个 https://github.com/airbnb/chronos 不过有点负载,java的不知道是否结合应用能否省时间。
    c4pt0r
        10
    c4pt0r  
       2014-12-10 19:58:20 +08:00
    @lianghui 不错哦,我当时调研 chronos 的时候他们还不支持 mesos...
    doenitz
        11
    doenitz  
       2016-12-06 14:31:59 +08:00
    支持动态添加任务吗?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3400 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 11:32 · PVG 19:32 · LAX 03:32 · JFK 06:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.