想要使用高级消费者api实现延迟消费者
主要想法:
提交1个偏移量
while (it.hasNext()) {
val msg = it.next().message()
//checks timestamp in msg to see delay period exceeded
while (!delayedPeriodPassed(msg)) {
waitSomeTime() //Thread.sleep or something....
}
//certain that the msg was delayed and can now be handled
Try { process(msg) } //the msg process will never fail the consumer
consumer.commitOffsets //commit each msg
}
对此实施的一些担忧:
谢谢!
答案 0 :(得分:16)
解决此问题的一种方法是使用另一个主题来推送所有要延迟的邮件。如果所有延迟的消息都应该在相同的时间延迟之后处理,那么这将非常简单:
while(it.hasNext()) {
val message = it.next().message()
if(shouldBeDelayed(message)) {
val delay = 24 hours
val delayTo = getCurrentTime() + delay
putMessageOnDelayedQueue(message, delay, delayTo)
}
else {
process(message)
}
consumer.commitOffset()
}
现在将尽快处理所有常规消息,而那些需要延迟的消息将被放在另一个主题上。
好消息是我们知道延迟主题头部的消息是应该首先处理的消息,因为它的delayTo值将是最小的。因此,我们可以设置另一个读取头消息的消费者,检查时间戳是否在过去,如果是,则处理消息并提交偏移量。如果不是,它不提交偏移量,而是直到那个时间才睡觉:
while(it.hasNext()) {
val delayedMessage = it.peek().message()
if(delayedMessage.delayTo < getCurrentTime()) {
val readMessage = it.next().message
process(readMessage.originalMessage)
consumer.commitOffset()
} else {
delayProcessingUntil(delayedMessage.delayTo)
}
}
如果有不同的延迟时间,您可以对延迟进行分区(例如24小时,12小时,6小时)。如果延迟时间比动态更加动态则变得有点复杂。您可以通过引入两个延迟主题来解决它。阅读延迟主题A
的所有消息,并处理过去delayTo
值的所有消息。在其他人中,您只需找到距离最近delayTo
的人,然后将其放在主题B
上。睡觉直到最近的一个应该被处理并反过来完成所有这一切,即处理来自主题B
的消息,并将那些不应该被回溯到主题A
上。
回答您的具体问题(有些问题已在您的问题的评论中得到解决)
- 提交每个偏移可能会减慢ZK
醇>
您可以考虑切换到在Kafka中存储偏移量(可从0.8.2获得的功能,在消费者配置中查看offsets.storage
属性)
- 可以将consumer.commitOffsets抛出异常吗?如果是的话,我将两次使用相同的消息(可以用幂等消息解决)
醇>
我相信如果它无法与偏移存储通信,例如。正如你所说,使用幂等消息解决了这个问题。
- 问题等待很长时间没有提交偏移量,例如延迟时间为24小时,将从迭代器获得下一个,休眠24小时,进程和提交(ZK会话超时?)
醇>
除非处理消息本身的时间超过会话超时,否则上述解决方案不会出现问题。
- 如果ZK会话保持活跃状态而不提交新的偏移量? (设置一个配置单元zookeeper.session.timeout.ms可以解决死亡的消费者而无需识别它)
醇>
再次使用上述内容,您不需要设置长会话超时。
- 我遗失的任何其他问题?
醇>
总有;)
答案 1 :(得分:2)
我会在你的案例中建议另一条路线。
解决消费者主线程中的等待时间是没有意义的。这将是队列使用方式的反模式。从概念上讲,您需要尽可能快地处理消息,并使队列保持较低的负载因子。
相反,我会使用一个调度程序来为每个需要延迟的消息安排作业。这样,您可以处理队列并创建将在预定义时间点触发的异步作业。
使用此技术的缺点是,对于将预定作业保存在内存中的JVM的状态是明智的。如果该JVM失败,则会丢失计划的作业,并且您不知道该任务是否已执行。
有调度程序实现,但可以配置为在集群环境中运行,从而使您免受JVM崩溃的影响。
看看这个java调度框架:http://www.quartz-scheduler.org/
答案 2 :(得分:1)
使用Tibco EMS或其他JMS队列。他们内置了重试延迟。对于你正在做的事情,卡夫卡可能不是正确的设计选择
答案 3 :(得分:0)
按计划列出的关键列表或其redis替代方案可能是最好的方法。