分享 Sidekiq 并发控制的一个小 trick

lithium4010 · 2017年02月16日 · 最后由 lithium4010 回复于 2017年02月17日 · 3264 次阅读

场景是 n 个耗时任务 A 需要执行,每个任务 A 之间互不影响,希望在 A 全部执行完成后开始执行某任务 B。

利用 sidekiq 来并行执行 A,当并发数 q << n 时,只需要将 n 个任务 A 和 一个任务 B 先后插入同一个 sidekiq 队列。若单个任务 A 最多耗时 t,则任务 B 执行前 sleep T,使得 T > t 即可。

这里利用了 sidekiq 队列先进先出的特点来进行并发控制。

后来想了一下,其实就是当并发数远小于任务数的时候,可以近似看做是顺序执行。Trick 在需要知道 sidekiq 实现的细节,同一队列任务确实是先进先出。

既然 a 可以并发执行,让 a 并行,每个 a 完成后到 redis 上标志,b 可以随时启动,执行前看标志,等到 n 就执行。这样子行不行?

#1 楼 @gihnius 这样实现起来相对复杂,需要很多额外的操作。而且随时启动,等到 n 执行这个需要 启动了的 B 任务长时间待机。设想待机时进程被杀或者服务器故障。

#2 楼 @lithium4010 你的 t T 怎么保证?

#2 楼 @lithium4010 就多两句 redis 而已

#3 楼 @gihnius 估算。比如上传一张 4M 以下图片到 CDN 的任务 A, 耗时大概 数秒。要求上传完 1w 张后触发任务 B,则 B 启动前 睡 1 分钟 即可。

#4 楼 @gihnius 但是相比来说确实是更复杂

#4 楼 @gihnius sidekiq 有 api 可以查队列中的任务是否已完成。

感觉估算不靠谱

#9 楼 @gihnius 我说的是相对复杂,不是绝对复杂。你能说说 7 楼和你的方法相比复杂在哪里吗?主楼的方法相比你的方法确实更简单。

#8 楼 @wpzero 不要感觉,说说具体为什么不靠谱。

#10 楼 @lithium4010

# at end of A worker
redis.incr "a-workers-done-couter"


# at beginng of B worker
while redis.get("a-workers-done-counter").to_i < N do
  sleep 1
end

# at end of B worker
redis.del "a-workers-done-counter"

#12 楼 @gihnius 请加上启动 n 个 A 任务 和 一个 B 任务的部分。主楼方案在任务中只需要添加一行代码。

# at beginning of task B

sleep 1.minute # depends on time of task A

也许你的是 ok 的。我想,就上传图片吧,你可以很好的预测出时间来?((算上网络情况,服务器的情况),我没太明白“q << n”什么意思?1w 张图片,你就能保证一分钟后执行 B? sidekiq 并行执行任务和执行一个单个任务时间差不多?MRI ruby 是 green thread 吧,而且“队列先进先出”有啥关系?

#13 楼 @lithium4010 别让你老板看到。。。

#12 楼 @gihnius 你的这个方案适合 n 的数量不远大于 q,而且整体 n 个任务完成耗时很短,不会出现进程被杀或服务器故障的情况。假如 n 个任务在数小时后才能全部完成,中间拔一次 sidekiq 机器的电源,就无法恢复了。

#14 楼 @wpzero 就是并发数远小于任务总数。sidekiq 是利用 redis 来做队列的。考虑并发数为 2,顺序插入 a1, a2, a3, b 这四个任务。第一轮拉出 a1, a2,第二轮 拉出 a3, b。b 任务只需要等待 a3 一个任务完成后执行即可。

#15 楼 @gihnius 我不明白为什么讨论技术问题你给人一种高高在上的感觉。好像钦点的你就是对的一样。估计你是个老板吧。而且你几乎没有正面回答过我的问题。

@lithium4010 Ok 我理解你的意思了,确实和队列有关,往一个队列里放就保证前者执行后者后执行?这都是没有任何错误的情况吧,如果发生错误,需要重试呢?怎么布代码?hard code 1.minute ?那就更不靠谱了

#20 楼 @wpzero 对于发生错误的情况,需要保证 A 任务不重试,而是记录错误。sidekiq 使用 lpush 插入任务,brpop 弹出任务,所以一个队列中的任务是相对顺序执行的。当任务数远大于并发数是可以看做是顺序执行。 https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/client.rb#L199

如果有 A 任务耗时多了怎么办 如果有 A 任务失败了怎么办

#23 楼 @angelfan 具体情况具体分析。可以设置 timeout,对于出错的任务进行记录。像我遇到的上传图片的场景,只要大部分的图片上传成功,就可以执行后续任务。对于出错的图片,可以根据错误记录下次执行。

有大量这种需求可以考虑 pro 版的 batch 了

这种思路本质是靠猜测前置任务的执行时间,影响因素太多且有潜在的时间浪费(任务失败,队列拥挤,Sidekiq 挂掉重启……)。

更稳定的做法是用 sidekiq-status 这类记录任务状态的插件,比如把前置任务 A 的 id 全部记下来,在 B 里检查前置任务的状态,再做针对性处理,比如全部完成且正确率到 90% 就处理,否则把自己 enqueue 到 n 秒之后。这个方法也能很容易的扩展成多级的任务链。

Sidekiq Pro 也有 batch 功能解决这类问题,我这种没钱用户没体验过。哪位有经验可以分享一下。

#26 楼 @darkbaby123 说的有道理,我这里给出的只是一个很粗糙的思路。对于具体的场景可以考虑的更精细。这里的本质应该是发现只需要猜测单一前置任务的执行时间,而不是所有前置任务的执行时间。

  • 队列拥挤的情况应该不存在。
  • Sidekiq 挂掉重启不会影响。

”若单个任务 A 最多耗时 t,则任务 B 执行前 sleep T,使得 T > t 即可“, 猜测 + Sleep,一个不靠谱,一个不确定,有的图片 10kb,有的图片 10M,你怎么估算时间

#29 楼 @lithium4010 就算同样两张 10M 的图片,上传时间也是不一样的,你以什么作为依据

#30 楼 @menghuanwd 不如你想一下?估算不是猜测,麻烦不要来抬杠。前提已经说了单个任务最多耗时 t,麻烦读一下。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号