Rails 一次性的倒计时任务 (由用户触发) 用 Sidekiq perform_in 是个好方法吗?

1c7 · January 24, 2019 · Last by yfractal replied at February 14, 2019 · 3274 hits

业务要求:

用户出价竞拍产品,
比如 用户 1 竞拍产品 A,开始 6 个小时倒计时。6 个小时过去之后那么用户 1 就获得了产品 A。
用户 2 用户 3 也可以竞拍,新竞拍会重新开始 6 小时倒计时。

举例:

用户 1 花了 100 元竞拍产品 A,6 小时倒计时开始,
在剩下 5 小时(或任何其他时间)的时候,用户 2 花了 150 元竞拍产品 A,倒计时重置 6 小时。
6 小时过后用户 2 获得产品(因为期间没有其他人出价来重置倒计时了)

发文前的功课:

这些讨论多以周期任务为主。一次性任务讨论不多。

问题:

用 sidekiq perform_in 做这种一次性任务是个好的解决方案吗?是否有更方便更可靠的方案?
(perform_in 的文档地址: https://github.com/mperham/sidekiq/wiki/Scheduled-Jobs)
时间到了之后就触发一些代码:发邮件通知。修改 record 状态。等

当新用户出价后

  1. 把之前的 perform_in 任务从 queue 里去掉
    (具体方法: https://stackoverflow.com/questions/21101253/how-to-delete-a-job-in-sidekiq)
  2. 添加新的 perform_in

为什么不是直接使用 redis 来设置过期时间

Reply to w7938940

没明白具体怎么实现

有个简单的方案:

功能有这几个重点:

  • 竞拍,更新状态 (价格/截止时间等)
  • 到点后自动结束竞拍

有两个对象:(名字随意取的)

  • 被竞拍物 (Goods)
  • 竞拍物状态 (GoodsStatus)

每次有人出更高的价格,其实就是在更新 GoodsStatus,可以为每次出价都持久化一条数据,核心字段是价格截止时间竞拍人,并将 Goods 对应到最新的 GoodsStatus。

每次竞拍都往 sidekiq 中插入一条定时任务,任务执行时检查当前 GoodsStatus 是否是最新的,不是的话直接退出。如果是最新的话就结束竞拍。

用 sidekiq 的时候需要注意的是,sidekiq 取定时任务的时间点并不精准,可能会有数秒的误差 (可以配置),sidekiq 里面的任务本质上是一个个被取出处理的,要保证 sidekiq 的处理能力足够强,避免当竞拍任务过多时,任务延迟过多。

竞拍应该还有个大盘,大盘在”轮询“的时候,也可以检查时间点,做结束竞拍的动作。

Reply to early

好,非常感谢

直觉上可以。 如果非要精确到秒或者毫秒的话,那就需要仔细控制 Worker 的执行时间,比如提前个几秒钟开始执行,然后在 Worker 本体内做精确的时间控制。

Reply to blacklee

嗯,感谢~~

用户竞拍的时候,更改 product.current_bidder 用 lock_version,避免多人同时竞拍,每次 after_commit 后 更新 新的竞拍结束时间。 建议 cronjob 查询竞拍结束时间来确认 product 归谁。 因为

  1. Sidekiq API 并不一定是实时的结果,perform_in 也不一定是准时的 2.你忽略了并发的情况。

cronjob 你可以用一个死循环去检查 product 时间更精准一点,查询到了后要 lock,你可以用 sidekiq scheduler,设置一个 cron job 每过 5 分钟去执行一个检查任务,检查任务里不停地 while sleep,超过 5 分钟了自动结束。

Reply to yakjuly

非常感谢详细的回复~ lock_version 没懂是什么我去查查

perform_in 在用户量不大的时候,可能没啥问题,但是由于商品竞拍这种性质,火热商品有可能在一段时间内造成大量的竞拍事件,这样大量的事件堆积在 sidekiq 队列中,终究不是啥好事情,很有可能会影响整个 sidekiq job 的响应时间。即使有逻辑从队列中祛除已经无用的 perform_in,也是一个很繁琐的事情,频繁操作,也会导致各种一致性和响应速度的问题。

为啥不换个想法?为什么每一次竞拍都需要 perform_in?从你的需求上能了解到,你关心的只是这个商品最后的竞拍人,和这个竞拍人竞拍的商品的过期时间。

所以很简单,只要有一个周期性的 Job,统一处理这些已经过期的商品,并生成对应的处理 job。没过期的就让他继续 active,在竞拍时候做个判断,是否是已经到了到期时间,顺便在前端展示时候,给个过期提示之类的。

这样就可以保证,每次生成的 job 一定是可用的,而且不会产生冗余的事件。

Reply to anning0322

感谢思路

就想 #9 说的那样,用一个 job 轮询查有哪些商品到期了,这样有一个 job 做检查就可以了。时间间隔看需求来定。可能要看下对列怎么处理 repeat job 的,假设时间间隔是 200 毫秒。第一个 200 毫秒到了,处理时间,但花了 200 毫秒,还没处理完,那这个时候,是同时做处理,还是说等第一个处理完,然后再做第二个。如果是处理完第一个,再处理第二个,假设第一个花了 405 毫秒,那是马上做第二个处理,还是说,要等第 600 毫秒才处理(这样的话就有延迟,js repeat 是这么处理的)。

应该有两类 job,一类 job 做检查到期,一类 job 做后续操作,至少要用两个独立的 job queue,两者不能相互影响(第二类 job 跑满 cpu 的时候,第一类 job 要能正常工作),优先级应该能解决,但最好还是测一下。

You need to Sign in before reply, if you don't have an account, please Sign up first.