我这现在有个需求就是我们的产品可以让我们的用户预约一个时间来执行一次或者周期性的执行。
在看了 TBSchedule 和 Elastic-job 之后好像他两不适合创建百万用户级的子任务。虽然执行的功能也就两个,但是用的预约量可能是百万级的。
之后还想过通过 mysql 扫表的方式来实现,但是这种方式太暴力了。有没有什么好的方式呢?
1
binux 2019-03-05 00:06:05 +08:00 via Android
sorted queue 按最近执行时间排序
|
2
jokerlee 2019-03-05 00:06:36 +08:00 via Android
mysql 扫表其实挺好的,简单好维护,时间加上索引就行
|
3
jadec0der 2019-03-05 00:10:47 +08:00
看你这个需求好像执行频率不太频繁,如果是每分钟 N 个任务,可以用
00:01 - 任务 1, 任务 2, 任务 3 00:02 - 任务 4, 任务 5, 任务 6 的结构保存任务,每分钟唤起一次,拿到所有任务然后执行。 如果很密集,一小时几百万个任务,可以参考游戏常用的时间轮算法。 |
4
nezhaxiaozi1015 OP @binux 主要我的预约任务队列需要频繁的进行增加和删除操作,感觉单纯的有序队列还是不太适合
|
5
jokerlee 2019-03-05 00:12:47 +08:00 via Android
起一个进程不停查预约时间早于当前时间的行,扫到以后 update 状态然后进程内处理或者扔到消息队列里
|
6
nezhaxiaozi1015 OP @jadec0der 我这个执行频率不算高,主要是用户的预约点可能都会集中在早高峰和晚高峰出行的时间段,早晚高峰的任务量肯定会达到百万级的
|
7
jokerlee 2019-03-05 00:16:18 +08:00 via Android
如果只是做简单处理,或者转发,单机基本就够了。考虑高可用可以起多个进程并发扫,以 update 状态 sql 的 affected rows=1 来判断抢没抢到
|
8
jokerlee 2019-03-05 00:17:38 +08:00 via Android
如果在考虑 mysql 扩展性,分库分表就行了
|
9
nezhaxiaozi1015 OP @jokerlee 扫表的话,我这边也是在想怎么尽量把扫表的量压缩到最小,毕竟总的预约量还是很大的,但是在某一个时间段的预约量还是可控的。
|
10
jokerlee 2019-03-05 00:18:40 +08:00 via Android
@nezhaxiaozi1015 limit 行数就行
|
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。 |
12
nezhaxiaozi1015 OP @jokerlee 嗯嗯,现在的想法也是开个现场任务不断的扫表,但是我不想扫全表
|
13
jokerlee 2019-03-05 00:22:09 +08:00 via Android
@nezhaxiaozi1015 假如同一秒最多有 n 个预约,一次扫 m 行,每 t 秒扫一次的话,不考虑处理耗时,满足 m/t > n 就不会延迟
|
14
jokerlee 2019-03-05 00:24:45 +08:00 via Android
@nezhaxiaozi1015 当然不用扫算表 select * from tasks where start_time < current_timestamp() and status=0
|
15
br00k 2019-03-05 00:27:54 +08:00
不知道时间范围,用延迟消息不知道能不能满足。😂
|
16
nezhaxiaozi1015 OP @reus 谢谢指点,学习一下
|
17
nezhaxiaozi1015 OP @jokerlee 同一秒的预约量实际上是不可控的,只知道有早晚高峰😂
|
18
nezhaxiaozi1015 OP @br00k 延迟队列,是可以,但是中间要是有用户修改或者删除预约那就麻烦了,还得存一下预约信息,消费的时候判断一下
|
19
j2gg0s 2019-03-05 00:38:55 +08:00
mysql 唯一选择,保证任务不丢,轻松扛住你的 QPS,把时间精度控制下
百万级这个形容词可以具体点 |
20
jokerlee 2019-03-05 00:40:27 +08:00 via Android
@nezhaxiaozi1015 再多也有个设计上限,一秒百万够不够,如果要一秒百万不 lag,就分一千张表,多起进程分开扫就完事了
|
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,自己实现一个也不难 |
22
nezhaxiaozi1015 OP @j2gg0s 👌
|
23
nezhaxiaozi1015 OP @jokerlee 🤣,一秒一百万应该是估计的最大流量了,一般应该是达不到的
|
24
JCZ2MkKb5S8ZX9pq 2019-03-05 00:55:26 +08:00
我自己是 mongodb 写的,主要是任务的取出逻辑,成功判断和之后的循环再加入任务池的处理。不过时间是比较久,差不多一天才百万。
|
25
binux 2019-03-05 01:37:23 +08:00
@nezhaxiaozi1015 #4 队列只增加不删除,执行的时候 check 一下数据库是不是 update to date 的执行时间 /规则就好了。毕竟你任务执行的时候也要更新下次执行时间不是。
|
26
xuanbg 2019-03-05 04:50:39 +08:00
每秒百万?我想你还是先考虑预约怎么进来的问题吧。。。这个量级不是随便搞搞就能扛得住的。
|
27
sampeng 2019-03-05 07:07:13 +08:00 via iPhone
每秒百万…讲真不是随便搞搞就可以的。光考虑读了。百万任务你还要分发处理,还要写,子任务计算的 cpu 和内存消耗都是海量,系统复杂的还要调用其他接口,还要保证数据一致性…
别动不动就是每秒百万并发好不好…这是一个及其恐怖的并发… |
28
sampeng 2019-03-05 07:08:33 +08:00 via iPhone
先随便搞搞就行了。不行再去改。哪有那么多银弹
|
29
nezhaxiaozi1015 OP @sampeng 嗯嗯是啊😂,先堆机器堆服务看看
|
30
123132116558 2019-03-05 08:56:20 +08:00
|
31
THaGKI9 2019-03-05 09:50:50 +08:00 via iPhone
用 kafka 存任务队列好像适合你这个场景,消费者也充当任务执行者的身份
|
32
zjb861107 2019-03-05 10:12:32 +08:00
Airflow,听过但没用过
|
33
sujin190 2019-03-05 10:37:08 +08:00
https://github.com/snower/forsun
推荐下之前设计的吧,使用系统定时器触发定时,长时间运行也不会出现偏差,持久化使用 redis,虽然每秒执行百万定时任务不大可能,但是管理几百万,上千万的定时任务还是很轻松的,执行器支持常用的 shell、http、redis、mysql 外也可以通过扩展自定义执行器,比如压入分布式队列之类的 |
34
bzzhou 2019-03-05 13:26:04 +08:00 3
之前做过类似的,千万量级也没有压力,基本套路如下:
1. 直接用 MySQL 存储,超时时间戳加索引 2. 周期性从 MySQL 中将超时的任务批量取出,缓存在内存中 3. worker 直接从内存中拿定时器任务,进行处理;处理完毕后更新 mysql 中的状态 |
35
kanepan19 2019-03-05 13:36:25 +08:00
可以直接程序实现或者用 消息队列的延迟队列实现,并且结合 redis 做持久化来判断是否已经执行的定时任务。
|
37
polythene 2019-03-05 16:16:36 +08:00
一个低数据量的定时任务系统,供 LZ 参考 :
http://blog.betacat.io/post/how-wecron-schedules/ |