如何使用Kafka Streams处理乱序的事件

时间:2019-09-03 08:25:15

标签: apache-kafka apache-kafka-streams

我有一个应用程序,其中根据用户操作(如用户登录,用户的中间操作(可选)和用户注销)在Kafka主题上发送事件。每个事件在事件对象中都有一些信息以及userId,例如,登录事件具有loginTime;添加便笺具有便笺(中间操作)。同样,注销事件具有logoutTime。要求是在收到每个用户的注销事件后,将所有这些事件中的信息聚合到一个对象中,并在下游发送它。

由于某些原因(网络延迟,多个事件产生器)事件可能未按顺序发生(用户注销事件可能在中间事件之前发生),那么问题是如何处理这种情况?收到用户注销事件后,我不能等待中间事件,因为中间事件是可选的,具体取决于用户的操作。

我在这里认为的唯一选择是,在接收到用户注销事件之后等待一段时间,如果在该等待时间内接收到中间事件并处理事件,则发送中间事件,但是再次不确定如何实现。

3 个答案:

答案 0 :(得分:1)

Kafka不保证topic的顺序,而是保证partition的顺序。一个主题可以有多个分区,因此,每个使用您主题的使用者都将使用一个分区。这就是kafka如何实现可伸缩性。因此,您遇到的是正常行为(这不是错误,也不与网络延迟等有关)。您可以做的是确保将要继续处理的所有消息都发送到同一分区。您可以通过将分区数设置为1来做到这一点,这是最愚蠢的方法。与生产者发送消息时,默认情况下,kafka查看密钥,对其进行哈希处理,并通过该哈希知道应在哪个分区上发送消息。您可以确保对于所有消息,密钥都是相同的。这样,所有键的哈希将是相同的,并且所有消息都将进入相同的分区。同样,您可以实现自定义分区程序,并覆盖默认方式,kafka如何选择将在哪个分区消息上进行选择。这样,所有消息将按顺序到达。如果您无法执行任何此类操作,那么您将收到乱序的事件,并且您将不得不考虑一种如何乱序使用事件的方法,但这与kafka无关。

答案 1 :(得分:1)

如果您无法保留事件顺序(注销将是最后一个事件), 您可以使用Kafka Streams的ProcesorApi来满足您的要求。 Kafka Streams DSL可以与Processor API结合使用(更多详细信息here)。

您可以有多个分区,但是特定用户的所有事件都必须发送到同一分区。

您必须实现自定义处理器/变压器。 您的处理器会将每个事件/活动放入状态存储中(将来自特定用户的所有事件汇总在同一键下)。 Processor API使您能够创建某种 scheduler Punctuator)。 您可以安排检查特定用户的每个 X 秒事件。如果 注销 很久以前的,您将获得所有事件/活动并进行汇总,并将结果发送到下游。

答案 2 :(得分:1)

如其他答案所述,在Kafka中的顺序是按分区维护的。

既然您在谈论用户事件,为什么不将 UserID 作为您的Kafka主题密钥?因此,与特定用户有关的所有事件将始终被排序(前提是它们是由单个生产者生产的)。

您应确保(通过 design 设计),只有一名Kafka生产者将所有用户更改事件推送到给定主题。这样,您可以避免由于多个生产者而出现的乱序消息。

从流中,您可能还希望查看Kafka流中的Windows。例如Tumbling windows是不重叠且固定大小的。您将汇总一段时间内的记录。

现在,您可能要按其时间戳(或您说您有注销时间,登录时间等)对汇总排序,并采取相应的行动。


简单有效的解决方案

使用同步发送,并将delivery.timeout.msretries设置为最大值。 为确保容错能力,请将acks=all设置为min.insync.replicas=2(主题配置),并使用单个生产者推送到该主题。 您还应该将max.block.ms设置为某个最大值,以便在获取元数据时发生错误(例如,当Kafka关闭时)时,send()不会立即返回。

  

使用您的费率对同步发送进行基准测试,并检查其是否符合您的要求或基准编号。

这可以确保首先发送一条消息到Kafka,然后在成功确认上一条消息之前不发送下一条消息。

  

如果未达到基准数字,请尝试施加背压   内存/持久队列之类的机制。

  1. 将事件添加到线程1中的队列
  2. 从线程2中的队列中窥视(不出队)事件
  3. 在线程2中调用producer.send(...).get()
  4. 使事件在Thread-2中出队