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

1c7 · 2019年01月24日 · 最后由 yfractal 回复于 2019年02月14日 · 3275 次阅读

业务要求:

用户出价竞拍产品,
比如 用户 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 来设置过期时间

w7938940 回复

没明白具体怎么实现

有个简单的方案:

功能有这几个重点:

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

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

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

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

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

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

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

early 回复

好,非常感谢

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

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 分钟了自动结束。

yakjuly 回复

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

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

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

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

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

anning0322 回复

感谢思路

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

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

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