V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
nezhaxiaozi1015
V2EX  ›  程序员

百万级定时任务如何设计?有什么好的框架呢?

  •  
  •   nezhaxiaozi1015 · 2019-03-05 00:02:37 +08:00 · 7129 次点击
    这是一个创建于 1872 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我这现在有个需求就是我们的产品可以让我们的用户预约一个时间来执行一次或者周期性的执行。

    在看了 TBSchedule 和 Elastic-job 之后好像他两不适合创建百万用户级的子任务。虽然执行的功能也就两个,但是用的预约量可能是百万级的。

    之后还想过通过 mysql 扫表的方式来实现,但是这种方式太暴力了。有没有什么好的方式呢?

    37 条回复    2019-03-05 16:16:36 +08:00
    binux
        1
    binux  
       2019-03-05 00:06:05 +08:00 via Android
    sorted queue 按最近执行时间排序
    jokerlee
        2
    jokerlee  
       2019-03-05 00:06:36 +08:00 via Android
    mysql 扫表其实挺好的,简单好维护,时间加上索引就行
    jadec0der
        3
    jadec0der  
       2019-03-05 00:10:47 +08:00
    看你这个需求好像执行频率不太频繁,如果是每分钟 N 个任务,可以用
    00:01 - 任务 1, 任务 2, 任务 3
    00:02 - 任务 4, 任务 5, 任务 6
    的结构保存任务,每分钟唤起一次,拿到所有任务然后执行。

    如果很密集,一小时几百万个任务,可以参考游戏常用的时间轮算法。
    nezhaxiaozi1015
        4
    nezhaxiaozi1015  
    OP
       2019-03-05 00:12:27 +08:00
    @binux 主要我的预约任务队列需要频繁的进行增加和删除操作,感觉单纯的有序队列还是不太适合
    jokerlee
        5
    jokerlee  
       2019-03-05 00:12:47 +08:00 via Android
    起一个进程不停查预约时间早于当前时间的行,扫到以后 update 状态然后进程内处理或者扔到消息队列里
    nezhaxiaozi1015
        6
    nezhaxiaozi1015  
    OP
       2019-03-05 00:15:12 +08:00
    @jadec0der 我这个执行频率不算高,主要是用户的预约点可能都会集中在早高峰和晚高峰出行的时间段,早晚高峰的任务量肯定会达到百万级的
    jokerlee
        7
    jokerlee  
       2019-03-05 00:16:18 +08:00 via Android
    如果只是做简单处理,或者转发,单机基本就够了。考虑高可用可以起多个进程并发扫,以 update 状态 sql 的 affected rows=1 来判断抢没抢到
    jokerlee
        8
    jokerlee  
       2019-03-05 00:17:38 +08:00 via Android
    如果在考虑 mysql 扩展性,分库分表就行了
    nezhaxiaozi1015
        9
    nezhaxiaozi1015  
    OP
       2019-03-05 00:17:44 +08:00
    @jokerlee 扫表的话,我这边也是在想怎么尽量把扫表的量压缩到最小,毕竟总的预约量还是很大的,但是在某一个时间段的预约量还是可控的。
    jokerlee
        10
    jokerlee  
       2019-03-05 00:18:40 +08:00 via Android
    @nezhaxiaozi1015 limit 行数就行
    reus
        11
    reus  
       2019-03-05 00:18:51 +08:00
    用 PostgreSQL,时间建索引,然后多个线程
    SELECT ...
    FROM jobs
    WHERE time < now()
    ORDER BY time ASC
    LIMIT 100
    FOR UPDATE SKIP LOCKED
    瓜分任务执行,一点问题都没有。关系数据库发展几十年了,总不至于连这点能力都没有。

    MySQL 要 8.0 才有 SKIP LOCKED。
    nezhaxiaozi1015
        12
    nezhaxiaozi1015  
    OP
       2019-03-05 00:20:15 +08:00
    @jokerlee 嗯嗯,现在的想法也是开个现场任务不断的扫表,但是我不想扫全表
    jokerlee
        13
    jokerlee  
       2019-03-05 00:22:09 +08:00 via Android
    @nezhaxiaozi1015 假如同一秒最多有 n 个预约,一次扫 m 行,每 t 秒扫一次的话,不考虑处理耗时,满足 m/t > n 就不会延迟
    jokerlee
        14
    jokerlee  
       2019-03-05 00:24:45 +08:00 via Android
    @nezhaxiaozi1015 当然不用扫算表 select * from tasks where start_time < current_timestamp() and status=0
    br00k
        15
    br00k  
       2019-03-05 00:27:54 +08:00
    不知道时间范围,用延迟消息不知道能不能满足。😂
    nezhaxiaozi1015
        16
    nezhaxiaozi1015  
    OP
       2019-03-05 00:32:37 +08:00
    @reus 谢谢指点,学习一下
    nezhaxiaozi1015
        17
    nezhaxiaozi1015  
    OP
       2019-03-05 00:35:16 +08:00
    @jokerlee 同一秒的预约量实际上是不可控的,只知道有早晚高峰😂
    nezhaxiaozi1015
        18
    nezhaxiaozi1015  
    OP
       2019-03-05 00:38:38 +08:00
    @br00k 延迟队列,是可以,但是中间要是有用户修改或者删除预约那就麻烦了,还得存一下预约信息,消费的时候判断一下
    j2gg0s
        19
    j2gg0s  
       2019-03-05 00:38:55 +08:00
    mysql 唯一选择,保证任务不丢,轻松扛住你的 QPS,把时间精度控制下
    百万级这个形容词可以具体点
    jokerlee
        20
    jokerlee  
       2019-03-05 00:40:27 +08:00 via Android
    @nezhaxiaozi1015 再多也有个设计上限,一秒百万够不够,如果要一秒百万不 lag,就分一千张表,多起进程分开扫就完事了
    Mirana
        21
    Mirana  
       2019-03-05 00:42:49 +08:00
    单实例 instance 最大负载流量 i1 存储 i2
    总共流量 max1
    总存储能 max2

    然后分片数就是 slices = max(max1/i1,max2/i2)

    然后前端做个 hash,找到对应的 slice 就好,如果考虑高可用可以用主从互备,也可以一致性哈希

    单实例的实现就是 sorted set,redis 里就有,按照预定的 interval 轮询,这样最大的误差就是 interval,自己实现一个也不难
    nezhaxiaozi1015
        22
    nezhaxiaozi1015  
    OP
       2019-03-05 00:49:12 +08:00
    @j2gg0s 👌
    nezhaxiaozi1015
        23
    nezhaxiaozi1015  
    OP
       2019-03-05 00:51:04 +08:00
    @jokerlee 🤣,一秒一百万应该是估计的最大流量了,一般应该是达不到的
    JCZ2MkKb5S8ZX9pq
        24
    JCZ2MkKb5S8ZX9pq  
       2019-03-05 00:55:26 +08:00
    我自己是 mongodb 写的,主要是任务的取出逻辑,成功判断和之后的循环再加入任务池的处理。不过时间是比较久,差不多一天才百万。
    binux
        25
    binux  
       2019-03-05 01:37:23 +08:00
    @nezhaxiaozi1015 #4 队列只增加不删除,执行的时候 check 一下数据库是不是 update to date 的执行时间 /规则就好了。毕竟你任务执行的时候也要更新下次执行时间不是。
    xuanbg
        26
    xuanbg  
       2019-03-05 04:50:39 +08:00
    每秒百万?我想你还是先考虑预约怎么进来的问题吧。。。这个量级不是随便搞搞就能扛得住的。
    sampeng
        27
    sampeng  
       2019-03-05 07:07:13 +08:00 via iPhone
    每秒百万…讲真不是随便搞搞就可以的。光考虑读了。百万任务你还要分发处理,还要写,子任务计算的 cpu 和内存消耗都是海量,系统复杂的还要调用其他接口,还要保证数据一致性…

    别动不动就是每秒百万并发好不好…这是一个及其恐怖的并发…
    sampeng
        28
    sampeng  
       2019-03-05 07:08:33 +08:00 via iPhone
    先随便搞搞就行了。不行再去改。哪有那么多银弹
    nezhaxiaozi1015
        29
    nezhaxiaozi1015  
    OP
       2019-03-05 08:46:43 +08:00 via iPhone
    @sampeng 嗯嗯是啊😂,先堆机器堆服务看看
    123132116558
        30
    123132116558  
       2019-03-05 08:56:20 +08:00
    THaGKI9
        31
    THaGKI9  
       2019-03-05 09:50:50 +08:00 via iPhone
    用 kafka 存任务队列好像适合你这个场景,消费者也充当任务执行者的身份
    zjb861107
        32
    zjb861107  
       2019-03-05 10:12:32 +08:00
    Airflow,听过但没用过
    sujin190
        33
    sujin190  
       2019-03-05 10:37:08 +08:00
    https://github.com/snower/forsun

    推荐下之前设计的吧,使用系统定时器触发定时,长时间运行也不会出现偏差,持久化使用 redis,虽然每秒执行百万定时任务不大可能,但是管理几百万,上千万的定时任务还是很轻松的,执行器支持常用的 shell、http、redis、mysql 外也可以通过扩展自定义执行器,比如压入分布式队列之类的
    bzzhou
        34
    bzzhou  
       2019-03-05 13:26:04 +08:00   ❤️ 3
    之前做过类似的,千万量级也没有压力,基本套路如下:

    1. 直接用 MySQL 存储,超时时间戳加索引
    2. 周期性从 MySQL 中将超时的任务批量取出,缓存在内存中
    3. worker 直接从内存中拿定时器任务,进行处理;处理完毕后更新 mysql 中的状态
    kanepan19
        35
    kanepan19  
       2019-03-05 13:36:25 +08:00
    可以直接程序实现或者用 消息队列的延迟队列实现,并且结合 redis 做持久化来判断是否已经执行的定时任务。
    aleung
        36
    aleung  
       2019-03-05 16:07:38 +08:00 via Android
    @bzzhou 思路跟我做的差不多。我们那个增加了失败重试,多实例分布式协调。
    polythene
        37
    polythene  
       2019-03-05 16:16:36 +08:00
    一个低数据量的定时任务系统,供 LZ 参考 :
    http://blog.betacat.io/post/how-wecron-schedules/
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3213 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 00:04 · PVG 08:04 · LAX 17:04 · JFK 20:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.