是否支持具有连接的接收器和源主题相同的Kafka Stream?

时间:2020-04-20 06:28:12

标签: java apache-kafka apache-kafka-streams

我有一个复杂的Kafka Stream应用程序,它在同一流中具有2个完全有状态的流:

  • 它使用Execution主题作为源,增强了消息并重新发布回相同的Execution主题。
  • 它加入另一个主题WorkerTaskResult,将结果添加到Execution并发布回Execution主题。

主要目标是提供工作流程系统。

详细的逻辑是:

  • ExecutionTaskRun的列表
  • Execution查看所有TaskRun的所有当前状态,并找到下一个要执行的状态
  • 如果发现任何内容,执行将更改其TaskRunsList并添加下一个并发布回Kafka,还将要完成的任务发送到另一个队列(WorkerTask
  • WorkerTask在Kafka流之外继续进行,并通过简单的Kafka Consumer&Producer发布回另一个队列(WorkerTaskResult
  • WorkerTaskResult更改当前TaskRun中的当前Execution,并更改状态(主要是RUNNING / SUCCEED / FAILED),并且发布回{{ 1}}队列(使用Kafka Stream)

如您所见,Execution(带有Execution列表)是当前应用程序的状态。

当所有消息都是顺序消息时(没有并发性,我只能同时具有TaskRun列表的一个变更),流可以很好地工作。当工作流变为并行(可以并发TaskRun时),我的执行状态似乎被覆盖并产生某种回滚。

示例日志输出:

WorkerTaskResult

我在控制台上警告2020-04-20 08:05:44,830 INFO reamThread-1 afkaExecutor Stream in with 3264792750: ( state=RUNNING taskRunList= [ TaskRun(id=6FiJ3US6jqZbtU3JL2AZD6, taskId=parent, value=null, state=RUNNING), TaskRun(id=75mtoz5KVRydOo3VJnX68s, taskId=t1, value=null, state=CREATED) # >>>>> t1 is created ] ) 2020-04-20 08:05:44,881 INFO reamThread-1 afkaExecutor WorkerTaskResult: TaskRun(id=75mtoz5KVRydOo3VJnX68s, taskId=t1, value=null, state=RUNNING) # >>>>> worker send running state 2020-04-20 08:05:44,882 INFO reamThread-1 afkaExecutor Stream out with 1805535461 : ( state=RUNNING taskRunList= [ TaskRun(id=6FiJ3US6jqZbtU3JL2AZD6, taskId=parent, value=null, state=RUNNING), TaskRun(id=75mtoz5KVRydOo3VJnX68s, taskId=t1, value=null, state=RUNNING) # >>>>> t1 save the running state ] ) 2020-04-20 08:05:45,047 INFO reamThread-1 afkaExecutor WorkerTaskResult: TaskRun(id=75mtoz5KVRydOo3VJnX68s, taskId=t1, value=null, state=SUCCESS) # >>>>> worker send success 2020-04-20 08:05:45,047 INFO reamThread-1 afkaExecutor Stream out with 578845055 : ( state=RUNNING taskRunList= [ TaskRun(id=6FiJ3US6jqZbtU3JL2AZD6, taskId=parent, value=null, state=RUNNING), TaskRun(id=75mtoz5KVRydOo3VJnX68s, taskId=t1, value=null, state=SUCCESS) ] ) 2020-04-20 08:05:45,153 INFO reamThread-1 afkaExecutor Stream in with 1805535461: ( state=RUNNING taskRunList= [ TaskRun(id=6FiJ3US6jqZbtU3JL2AZD6, taskId=parent, value=null, state=RUNNING), TaskRun(id=75mtoz5KVRydOo3VJnX68s, taskId=t1, value=null, state=RUNNING) # >>>>> OUT OF ORDER AND ROLLBACK TO PREVIOUS VERSION ] ) 2020-04-20 08:05:45,157 INFO reamThread-1 afkaExecutor Stream out with 1889889916 : ( state=RUNNING taskRunList= [ TaskRun(id=6FiJ3US6jqZbtU3JL2AZD6, taskId=parent, value=null, state=RUNNING), TaskRun(id=75mtoz5KVRydOo3VJnX68s, taskId=t1, value=null, state=RUNNING), TaskRun(id=6k23oBXy9cD0uCJeZ20SpB, taskId=t2, value=null, state=CREATED) ] ) 2020-04-20 08:05:45,209 WARN reamThread-1 KTableSource Detected out-of-order KTable update for execution at offset 10, partition 2. 2020-04-20 08:05:45,313 INFO reamThread-1 afkaExecutor Stream in with 1889889916: ( state=RUNNING taskRunList= [ TaskRun(id=6FiJ3US6jqZbtU3JL2AZD6, taskId=parent, value=null, state=RUNNING), TaskRun(id=75mtoz5KVRydOo3VJnX68s, taskId=t1, value=null, state=RUNNING), TaskRun(id=6k23oBXy9cD0uCJeZ20SpB, taskId=t2, value=null, state=CREATED) ] ) 2020-04-20 08:05:45,350 INFO reamThread-1 afkaExecutor WorkerTaskResult: TaskRun(id=6k23oBXy9cD0uCJeZ20SpB, taskId=t2, value=null, state=RUNNING) 2020-04-20 08:05:45,350 INFO reamThread-1 afkaExecutor Stream out with 3651399223 : ( state=RUNNING taskRunList= [ TaskRun(id=6FiJ3US6jqZbtU3JL2AZD6, taskId=parent, value=null, state=RUNNING), TaskRun(id=75mtoz5KVRydOo3VJnX68s, taskId=t1, value=null, state=RUNNING), TaskRun(id=6k23oBXy9cD0uCJeZ20SpB, taskId=t2, value=null, state=RUNNING) ] )

完整的源代码可以找到here

如果也尝试使用许多不同的方法,例如here

  • Detected out-of-order KTable update for execution at offset 10, partition 7.Execution放在同一个主题上,以确保在同一时间只处理同一条消息
  • 并由我自己将最后一个WorkerTaskResult保留在StateStore上(以便加入ExecutionWorkerTaskResult
  • 但这听起来像是我重新使用了KTable,但效果并不佳

或这一个here

  • 与以前几乎相同(我自己将最后一个Execution保留在StateStore上)
  • ,但使用2个KStream到KStream(除去KTable)。

我的问题是:

  • KafkaStreams是否支持这种模式(这不是dag流,因为我们沉浸在同一主题中)?
  • 将这个流设计为并发安全的好方法是什么?

任何线索都非常感谢,因为几天以来完全卡住了,谢谢

编辑1:
这是一些其他信息:

  • 只有KStream应用程序将新事件发布到Execution,没有外部应用程序对此主题进行发布,唯一发布到Execution的外部应用程序是第一个事件(也就是创建执行)。 / li>
  • 有一个Execution(外部应用程序,简单的消费者/生产者)从WorkerApp(要完成的工作)中消费,并在WorkerTask上发布结果(主要是该产品的当前状态)应用程序。)

这是实际流的简化版本:

WorkerTaskResult

KStream主要是一个执行器状态应用程序,可以查找下一个要执行的Builder -> Stream 1 - from KStream<WorkerTaskResult> - join KTable<Execution> - to Execution topic -> Stream 2 - from KTable<Execution> (same than previous) - multiple output - to WorkerTaskResult topic (if found an end) - to Execution & to WorkerTask topic (if found a next task) - to Execution topic (if detect an Execution end) 并评估流程是否结束,因此该应用程序可以:

  • 创建新的WorkerTask
  • 更改当前TaskRun的状态
    • 加入TaskRun
    • 评估整个执行过程,发现任务失败(基于依赖关系)
  • 更改执行状态,并发布最终状态SUCCEED或FAILED,这将打破“无限循环”

在这个实际版本中,我真正不清楚的是WorkerTaskResult在现实世界中的含义是什么? 这是否意味着一个KTable必须在每个分区和每个键上只有一个生产者,才能保持话题的顺序?

编辑2:
同时,我找到了一种新的方式来思考似乎正在运行的流应用程序。单元测试通过了,不再Detected out-of-order KTable update。 这是简化的新流程:

Detected out-of-order

我认为有意义的是:

  • Builder - from KTable<Execution> - leftJoin KTable<WorkerTaskResult> - Branch - If Join > to Execution topic - If not joint > continue the flow - Multiple output (same than previous) - to WorkerTaskResult topic (if found an end) - to Execution & to WorkerTask topic (if found a next task) - to Execution topic (if detect an Execution end) 现在是一个KTable,所以我只保留结果的最新版本
  • 我只有一条路径流(没有更多的2条路径)输出到WorkerTaskResult(我认为这是最重要的部分,已无序解决)< / li>
  • 每个输入似乎只有一个输出(Execution上的1个新值将在Execution主题上产生1个新值)

这是新的拓扑:

Execution

就目前而言,我不清楚该解决方案是否对任何并发都具有弹性,以及我是否可以在其他时间乱序(这意味着执行在上一次回滚并且导致多次执行)执行相同的任务)。

1 个答案:

答案 0 :(得分:1)

KafkaStreams是否支持这种模式(这不是一个陷入困境的流程,因为我们沉浸在同一个主题上)?

通常是。您只需要确保不会以“无限循环”结尾,即在某个时候输入记录应该“终止”,并且不再对输出主题产生任何影响。对于您的情况,Execution最终不应再通过反馈循环再创建新的Tasks

将这种流设计为并发安全的好方法是什么

它总是取决于具体的应用程序...对于您的情况,如果我正确地理解了您的应用程序的设计,那么您基本上有两个输入主题(ExecutionWorkerTaskResult)和两个输出主题(ExecutionWorkerTask)。在处理输入主题时,来自每个输入的消息可能会修改共享状态(即任务状态)。

此外,还有一个“外部应用程序”可以从WorkerTask主题读取并写入WorkerTaskResult主题吗?因此,您的总体数据流中实际上存在第二个循环吗?我假设还有其他上游应用程序实际上也会将新数据推送到Execution主题中?

                             +-----------------+
                             |                 |
                             v                 |
upstream producers ---> "Execution" --+        |
                                      |        |
                                      v        |  
                                      KS-App --+
                                      ^        |
                                      |        |
            +--> "WorkerTaskResult" --+        +--> "WorkerTask" --+
            |                                                      |
            +------------------------ outside app <----------------+

我对atm不清楚的是什么

  • 哪些状态更改从KS-App直接传播回Execution
  • 哪些状态更改是通过WorkerTaskResult从“外部应用”传播的?

也许您可以更新您的问题,我可以尝试相应地更新我的答案。

更新(基于编辑1和2)

执行和WorkerTask主题(如果找到下一个任务)

这一步似乎介绍了比赛条件?当写回Execution主题时,您将在读回状态时更新状态。并行地,任务的执行可能首先完成(即在重新读取和处理Execution更新之前),因此可以编写第二次Execution更新(任务完成时)国家第一?

在这个实际版本上,我真正不清楚的是在现实世界中检测到乱序的KTable更新是什么意思?这是否意味着一个KTable必须在每个分区和每个键上只有一个生产者,才能保持话题的顺序?

你可以这么说。对于每个输入记录,table()运算符将输入的时间戳与表中当前条目的时间戳进行比较。如果输入记录的时间戳较小,则会记录WARN(仍将应用更新):WARN的原因是,该表每个键仅存储一个条目,并且该表希望仅在时间上向前移动。如果存在乱序更新,则可能导致意外结果,从而导致WARN日志。每个分区使用一个生产者,或者每个密钥使用一个生产者,可以避免每个密钥出现乱序的数据(假设生产者仅发送有序数据)。

如果我完全了解您的应用程序的新版本,我不确定100%是否为atm,但总的来说,您要确保避免数据争用,并将更新线性化为Execution