Java apache kafka consumer中避免重复消息的有效策略

Java apache kafka consumer中避免重复消息的有效策略,java,message-queue,apache-kafka,Java,Message Queue,Apache Kafka,我已经学习阿帕奇·卡夫卡一个月了。然而,我现在陷入了困境。我的用例是,我有两个或更多的消费者进程在不同的机器上运行。我在kafka服务器上运行了一些测试,发布了10000条消息。然后,在处理这些消息时,我杀死了一个使用者进程并重新启动了它。消费者正在文件中写入已处理的消息。所以在消费结束后,该文件显示了超过10k条消息。所以有些信息被复制了 在使用者进程中,我已禁用自动提交。消费者手动批量提交补偿。例如,如果100条消息写入文件,消费者提交偏移量。当单个使用者进程正在运行时,可以通过这种方式避免

我已经学习阿帕奇·卡夫卡一个月了。然而,我现在陷入了困境。我的用例是,我有两个或更多的消费者进程在不同的机器上运行。我在kafka服务器上运行了一些测试,发布了10000条消息。然后,在处理这些消息时,我杀死了一个使用者进程并重新启动了它。消费者正在文件中写入已处理的消息。所以在消费结束后,该文件显示了超过10k条消息。所以有些信息被复制了

在使用者进程中,我已禁用自动提交。消费者手动批量提交补偿。例如,如果100条消息写入文件,消费者提交偏移量。当单个使用者进程正在运行时,可以通过这种方式避免它崩溃和恢复重复。但当多个使用者正在运行,其中一个崩溃并恢复时,它会将重复的消息写入文件


是否有任何有效的策略来避免这些重复信息

简而言之,答案是否定的

你要找的就是一次处理。虽然它似乎常常是可行的,但永远不应该依赖它,因为总有一些警告

即使为了防止重复,您也需要使用简单消费者。这种方法的工作原理是,当消息从某个分区消费时,将所消费消息的分区和偏移量写入磁盘。当使用者在发生故障后重新启动时,从磁盘读取每个分区上次使用的偏移量

但即使使用这种模式,消费者也不能保证它不会在失败后重新处理消息。如果使用者使用了一条消息,然后在将偏移量刷新到磁盘之前失败,该怎么办?如果在处理消息之前写入磁盘,那么在实际处理消息之前写入偏移量然后失败会怎么样?即使您在每条消息之后向ZooKeeper提交偏移量,也会存在同样的问题

不过,在某些情况下 精确一次处理更容易实现,但仅适用于某些用例。这只需要将偏移量存储在与单元应用程序输出相同的位置。例如,如果您编写了一个统计消息的消费者,通过将上次统计的偏移量与每次计数一起存储,您可以保证偏移量与消费者的状态同时存储。当然,为了保证只处理一次,这需要您只使用一条消息,并为每条消息更新一次状态,这对于大多数Kafka消费应用程序来说是完全不切实际的。从本质上讲,出于性能原因,Kafka成批使用消息

通常,如果将应用程序设计为幂等函数,您的时间会花得更多,应用程序也会更可靠。

这就是关于一次的主题必须说的:

我如何从卡夫卡那里获得一次准确的信息? 精确一次语义有两个部分:在数据生产期间避免重复和在数据消耗期间避免重复

在数据生成过程中,有两种方法可以精确获取一次语义:

  • 每个分区使用一个writer,每次出现网络错误时,检查该分区中的最后一条消息,查看上次写入是否成功
  • 在消息中包含主键(UUID或其他内容),并在使用者上执行重复数据消除
如果您执行其中一项操作,卡夫卡托管的日志将不会重复。然而,没有副本的阅读也取决于消费者的合作。如果消费者定期检查其位置,那么如果它失败并重新启动,它将从检查点位置重新启动。因此,如果数据输出和检查点不是以原子方式写入的,那么在这里也可能得到重复的数据。此问题是存储系统特有的。例如,如果您使用的是数据库,则可以在事务中将它们一起提交。LinkedIn编写的HDFS加缪加载程序对Hadoop加载执行类似的操作。另一种不需要事务的替代方法是使用加载的数据存储偏移量,并使用主题/分区/偏移量组合进行重复数据消除

我认为有两个改进可以让这变得更容易:

  • 生产者幂等可以通过在服务器上选择性地集成对生产者幂等的支持而自动完成,而且成本更低
  • 现有的高级使用者不会公开很多更细粒度的偏移控制(例如,重置位置)。我们很快就会进行这方面的工作

我同意RaGe在消费者方面的重复数据消除。我们使用Redis消除Kafka消息的重复数据

假设消息类有一个名为“uniqId”的成员,该成员由生产者方填充,并保证是唯一的。我们使用12个长度的随机字符串。(regexp是
'^[A-Za-z0-9]{12}$'

消费者端使用Redis的SETNX进行重复数据消除和过期,以自动清除过期的密钥。示例代码:

Message msg = ... // eg. ConsumerIterator.next().message().fromJson();
Jedis jedis = ... // eg. JedisPool.getResource();
String key = "SPOUT:" + msg.uniqId; // prefix name at will
String val = Long.toString(System.currentTimeMillis());
long rsps = jedis.setnx(key, val);
if (rsps <= 0) {
    log.warn("kafka dup: {}", msg.toJson()); // and other logic
} else {
    jedis.expire(key, 7200); // 2 hours is ok for production environment;
}
消息消息消息=…//例如,ConsumerIterator.next().message().fromJson();
绝地武士=…/例如:JedisPool.getResource();
String key=“喷嘴:”+msg.uniqId;//随意给名字加前缀
字符串val=Long.toString(System.currentTimeMillis());
长rsps=绝地。setnx(键,val);

如果(rsps无论在制作人方面做了什么,我们认为卡夫卡只提供一次的最佳方式仍然是在消费者方面处理:

  • 生成带有uuid的消息,作为主题T1中的卡夫卡消息键
  • 用户端从T1读取消息,将其写入hbase,uuid作为行键
  • 使用相同的行键从hbase读回,然后写入另一个主题T2
  • 您的最终消费者是否实际消费了主题T2

  • 有一个相对较新的'事务API