Kafka - 使用高级消费者实现延迟队列

时间:2015-08-02 18:10:07

标签: java scala messaging apache-kafka kafka-consumer-api

想要使用高级消费者api实现延迟消费者

主要想法:

  • 按键生成消息(每个消息包含创建时间戳)这可以确保每个分区按生产时间排序消息。
  • auto.commit.enable = false(将在每个消息进程后显式提交)
  • 使用消息
  • 检查消息时间戳并检查是否已经过了足够的时间
  • 进程消息(此操作永远不会失败)
  • 提交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
    }
    

对此实施的一些担忧:

  1. 提交每个偏移可能会减慢ZK
  2. 可以将consumer.commitOffsets抛出异常吗?如果是的话,我将两次使用相同的消息(可以用幂等消息解决)
  3. 问题等待很长时间没有提交偏移量,例如延迟时间为24小时,将从迭代器获得下一个,休眠24小时,进程和提交(ZK会话超时?)
  4. 如果ZK会话保持活跃状态​​而不提交新的偏移量? (设置一个配置单元zookeeper.session.timeout.ms可以解决死亡的消费者而无需识别它)
  5. 我遗失的任何其他问题?
  6. 谢谢!

4 个答案:

答案 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上。

回答您的具体问题(有些问题已在您的问题的评论中得到解决)

  
      
  1. 提交每个偏移可能会减慢ZK
  2.   

您可以考虑切换到在Kafka中存储偏移量(可从0.8.2获得的功能,在消费者配置中查看offsets.storage属性)

  
      
  1. 可以将consumer.commitOffsets抛出异常吗?如果是的话,我将两次使用相同的消息(可以用幂等消息解决)
  2.   

我相信如果它无法与偏移存储通信,例如。正如你所说,使用幂等消息解决了这个问题。

  
      
  1. 问题等待很长时间没有提交偏移量,例如延迟时间为24小时,将从迭代器获得下一个,休眠24小时,进程和提交(ZK会话超时?)
  2.   

除非处理消息本身的时间超过会话超时,否则上述解决方案不会出现问题。

  
      
  1. 如果ZK会话保持活跃状态​​而不提交新的偏移量? (设置一个配置单元zookeeper.session.timeout.ms可以解决死亡的消费者而无需识别它)
  2.   

再次使用上述内容,您不需要设置长会话超时。

  
      
  1. 我遗失的任何其他问题?
  2.   

总有;)

答案 1 :(得分:2)

我会在你的案例中建议另一条路线。

解决消费者主线程中的等待时间是没有意义的。这将是队列使用方式的反模式。从概念上讲,您需要尽可能快地处理消息,并使队列保持较低的负载因子。

相反,我会使用一个调度程序来为每个需要延迟的消息安排作业。这样,您可以处理队列并创建将在预定义时间点触发的异步作业。

使用此技术的缺点是,对于将预定作业保存在内存中的JVM的状态是明智的。如果该JVM失败,则会丢失计划的作业,并且您不知道该任务是否已执行。

有调度程序实现,但可以配置为在集群环境中运行,从而使您免受JVM崩溃的影响。

看看这个java调度框架:http://www.quartz-scheduler.org/

答案 2 :(得分:1)

使用Tibco EMS或其他JMS队列。他们内置了重试延迟。对于你正在做的事情,卡夫卡可能不是正确的设计选择

答案 3 :(得分:0)

按计划列出的关键列表或其redis替代方案可能是最好的方法。