Google cloud dataflow 如何在Apache Beam步骤中并行HTTP请求?

Google cloud dataflow 如何在Apache Beam步骤中并行HTTP请求?,google-cloud-dataflow,apache-beam,Google Cloud Dataflow,Apache Beam,我有一个在Google Dataflow上运行的Apache Beam管道,其工作相当简单: 它从Pub/Sub读取单个JSON对象 解析它们 并通过HTTP将它们发送到某个API 此API要求我以75个批次发送项目。因此,我构建了一个DoFn,它将事件累积到一个列表中,并在得到75个事件后通过这个API发布它们。这会导致速度太慢,因此我认为不应该使用线程池在不同的线程中执行这些HTTP请求 我现在所拥有的功能的实现如下所示: private class WriteFn : DoFn<

我有一个在Google Dataflow上运行的Apache Beam管道,其工作相当简单:

  • 它从Pub/Sub读取单个JSON对象
  • 解析它们
  • 并通过HTTP将它们发送到某个API
此API要求我以75个批次发送项目。因此,我构建了一个
DoFn
,它将事件累积到一个列表中,并在得到75个事件后通过这个API发布它们。这会导致速度太慢,因此我认为不应该使用线程池在不同的线程中执行这些HTTP请求

我现在所拥有的功能的实现如下所示:

private class WriteFn : DoFn<TheEvent, Void>() {
  @Transient var api: TheApi

  @Transient var currentBatch: MutableList<TheEvent>

  @Transient var executor: ExecutorService

  @Setup
  fun setup() {
    api = buildApi()
    executor = Executors.newCachedThreadPool()
  }

  @StartBundle
  fun startBundle() {
    currentBatch = mutableListOf()
  }

  @ProcessElement
  fun processElement(processContext: ProcessContext) {
    val record = processContext.element()

    currentBatch.add(record)

    if (currentBatch.size >= 75) {
      flush()
    }
  }

  private fun flush() {
    val payloadTrack = currentBatch.toList()
    executor.submit {
      api.sendToApi(payloadTrack)
    }
    currentBatch.clear()
  }

  @FinishBundle
  fun finishBundle() {
    if (currentBatch.isNotEmpty()) {
      flush()
    }
  }

  @Teardown
  fun teardown() {
    executor.shutdown()
    executor.awaitTermination(30, TimeUnit.SECONDS)
  }
}
私有类WriteFn:DoFn(){
@瞬态var api:TheApi
@瞬态var currentBatch:可变列表
@临时var执行器:执行器服务
@设置
趣味设置(){
api=buildApi()
executor=Executors.newCachedThreadPool()
}
@起价
有趣的开始包(){
currentBatch=mutableListOf()
}
@过程元素
有趣的processElement(processContext:processContext){
val record=processContext.element()
currentBatch.add(记录)
如果(currentBatch.size>=75){
刷新()
}
}
私人同花顺(){
val payloadTrack=currentBatch.toList()
遗嘱执行人提交{
api.sendToApi(付费跟踪)
}
currentBatch.clear()
}
@完成包
fun finishBundle(){
if(currentBatch.isNotEmpty()){
刷新()
}
}
@拆卸
有趣的撕裂{
执行器关闭()
执行器等待终止(30,时间单位秒)
}
}
这似乎工作得“很好”,因为数据正在进入API。但我不知道这是否是正确的方法,我感觉这是非常缓慢的

我认为它很慢的原因是,在进行负载测试时(通过向发布/订阅发送几百万个事件),管道将这些消息转发到API(响应时间不到8毫秒)所需的时间是我的笔记本电脑将它们发送到发布/订阅所需时间的8倍

我的实现有什么问题吗?我应该这样做吗


还有。。。我是否需要在我的
@FinishBundle
方法中等待所有请求完成(即获取执行人返回的期货并等待它们)

这里有两个相互关联的问题:

  • 你做得对吗/你需要改变什么吗
  • 您需要在
    @FinishBundle
    中等待吗
  • 第二个答案:是的。但事实上,你需要更彻底地冲洗,这一点会变得很清楚

    一旦您的
    @FinishBundle
    方法成功,梁转轮将假定捆绑已成功完成。但是您的
    @FinishBundle
    只发送请求-它不能确保请求成功。因此,如果请求随后失败,您可能会以这种方式丢失数据。您的
    @FinishBundle
    方法实际上应该是阻塞的,并等待来自
    API的成功确认。顺便说一句,上面的所有内容都应该是幂等的,因为在完成束之后,地震可能会袭击并导致重试;-)

    所以要回答第一个问题:你应该改变什么吗?就在上面。只要在提交捆绑包之前确定结果已提交,以这种方式批处理请求的实践就可以工作

    您可能会发现这样做会导致管道速度减慢,因为
    @FinishBundle
    发生的频率高于
    @Setup
    。要跨包批处理请求,您需要使用较低级别的状态和计时器功能。我在上为您的用例编写了一个人工版本。我很想知道这对你是如何起作用的


    这可能只是因为,当您的管道中有一个持久的洗牌时,您期望的极低延迟(在低毫秒范围内)不可用。

    这里有两个相互关联的问题:

  • 你做得对吗/你需要改变什么吗
  • 您需要在
    @FinishBundle
    中等待吗
  • 第二个答案:是的。但事实上,你需要更彻底地冲洗,这一点会变得很清楚

    一旦您的
    @FinishBundle
    方法成功,梁转轮将假定捆绑已成功完成。但是您的
    @FinishBundle
    只发送请求-它不能确保请求成功。因此,如果请求随后失败,您可能会以这种方式丢失数据。您的
    @FinishBundle
    方法实际上应该是阻塞的,并等待来自
    API的成功确认。顺便说一句,上面的所有内容都应该是幂等的,因为在完成束之后,地震可能会袭击并导致重试;-)

    所以要回答第一个问题:你应该改变什么吗?就在上面。只要在提交捆绑包之前确定结果已提交,以这种方式批处理请求的实践就可以工作

    您可能会发现这样做会导致管道速度减慢,因为
    @FinishBundle
    发生的频率高于
    @Setup
    。要跨包批处理请求,您需要使用较低级别的状态和计时器功能。我在上为您的用例编写了一个人工版本。我很想知道这对你是如何起作用的


    这可能只是因为当您的管道中有一个持久的洗牌时,您期望的极低延迟(在低毫秒范围内)不可用。

    感谢您的响应。是的,我现在在
    @FinishBundle
    中阻塞了我的管道(我保存了
    返回的所有未来。提交{}
    ,然后循环通过它们并阻塞它们
    。get()
    )。到现在为止,一直都还不错。有一件事我从UI中不明白,那就是我的管道是什么样子的:“6天3小时”到底意味着什么?我看到数据正常地传送到另一边。我真的不理解Dataflow对“墙时间”的定义。我将给出