Sidekiq如何跳过重试流程直接将任务发送至Deadset
我检索了相关技术资料,发现不少开发者有任务失败时触发异常处理系统通知的需求,典型场景为任务需要对接可靠性不稳定的第三方API。
我的需求是直接将符合判定条件的失败任务送入Deadset,既不直接丢弃任务,也不需要等待约20天的默认重试周期结束,让运维人员可以在Deadset UI界面中根据失败原因,手动点击Retry或Delete按钮处理对应任务。
现有一个Russian Roulette示例任务:逻辑为满足幸运判定条件时任务走重试流程,不满足条件时直接进入被称为“任务墓地”的Deadset,请问该如何正确实现该逻辑?
我最初考虑在sidekiq_options中设置retry: 0,但该配置无法满足动态分支判断的需求。我曾设想通过retry方法动态传入sidekiq_options retry: 0配置,让任务下次执行时直接进入Deadset,对应示例代码如下:
class RussianRoulette < ActiveJobBase sidekiq_options retry: 10 discard_on SomeException do |job, exception| puts "You are dead but somebody will check your corpse!" # How to send to deadset? end def perform(args) if feeling_lucky? # If this method fails it retries up to 10 times puts "You got another chance!" retry_job else puts "You are dead!" # What to do?: # throw :abort # No, because we want to send it to the deadset so somebody can erase or retry it # raise SomeException # Could be, but then how to send to deadset? # self.class.perform_later(args, {sidekiq_options: {retry: 0}}) # Is this even possible? end end end
目前我尝试过的最接近方案是通过Sidekiq API直接向Deadset推送数据,查阅Sidekiq官方源码可知该接口要求传入message参数,我尝试将任务序列化为JSON后传入,但在WebUI中显示传入的message参数格式不正确,测试代码如下:
Sidekiq::DeadSet.new.kill(job.to_json) # Also tried job.arguments.to_json

问题更新
我参考了WebUI中Kill按钮的实现逻辑,发现逻辑中会调用任务实例的kill方法,但我在控制台手动调用该方法时提示方法不存在,报错信息如下:
(byebug) job.kill *** NoMethodError Exception: undefined method `kill' for #<RussianRoulette:0x00007faf520805a8>
正确实现方案
核心逻辑说明
Sidekiq 5.1及以上版本原生支持直接将任务送入Deadset的能力,不需要手动拼接任务参数调用底层API,也不需要额外开发中间件。
先明确之前方案的问题:
discard_on捕获异常后会直接删除任务,不会送入Deadset,不符合需求- ActiveJob封装的任务实例没有
kill方法,该方法只存在于Sidekiq从Redis读取的原生任务记录对象上,直接在ActiveJob实例上调用必然报方法不存在的错误 - 手动调用
Sidekiq::DeadSet.new.kill时需要传入Sidekiq原生格式的任务载荷(包含jid、任务类、错误栈、创建时间等完整字段),直接传ActiveJob实例或参数的序列化结果会因为格式缺失无法被WebUI识别,显示异常 - 重新入队一个
retry:0的新任务会丢失原任务的执行上下文、错误栈信息,生成全新的任务ID,无法追溯原始失败记录 throw :abort会直接终止任务并丢弃,不会进入Deadset
可直接落地的代码
利用Sidekiq sidekiq_retry_in回调的特殊返回值:kill即可实现需求:当该回调返回:kill时,Sidekiq会跳过所有重试流程,直接将任务送入Deadset,完整保留任务上下文和错误信息,支持在WebUI手动重试或删除。
class RussianRoulette < ActiveJob::Base sidekiq_options retry: 10 # 定义专门的异常标记需要直接入死信的场景,不要用discard_on捕获该异常 class FatalGameOver < StandardError; end def perform(args) if feeling_lucky? puts "You got another chance!" # 正常重试逻辑可自定义等待时间 retry_job wait: 1.minute else puts "You are dead! Sent to deadset for manual processing" # 抛出标记异常,触发直接入死信逻辑 raise FatalGameOver, "Lost russian roulette, pending manual review" end end # 配置Sidekiq重试决策逻辑 sidekiq_retry_in do |count, exception, _job_hash| # 碰到标记的致命异常,直接返回:kill送入Deadset next :kill if exception.is_a?(FatalGameOver) # 其他异常走正常的重试退避逻辑,最多重试10次 (count + 1).minutes end end
该方案完全匹配需求:
- 符合判定条件的任务不会等待长重试周期,直接进入Deadset
- 任务不会被丢弃,完整保留所有执行信息、错误栈
- 运维可以在Deadset UI中正常操作重试或删除
- 动态分支判断完全可控,不会影响其他异常场景的正常重试逻辑
内容的提问来源于stack exchange,提问作者damuz91

