我在Google Dataflow上运行了一个Apache Beam管道,其工作非常简单:
该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)
}
}
就数据将其写入API而言,这似乎“很好”地工作。但是我不知道这是否是正确的方法,而且我感觉这很慢。
我认为它很慢的原因是,在进行负载测试(通过向Pub / Sub发送数百万个事件)时,将这些消息转发到API的管道最多需要8倍的时间(具有响应时间少于8毫秒),而不是让我的笔记本电脑将它们输入到Pub / Sub中。
我的实现是否存在任何问题?这是我应该这样做的方式吗?
还...我是否需要等待@FinishBundle
方法中的所有请求完成(即通过让执行者退还期货并等待它们)?
答案 0 :(得分:2)
您在这里有两个相互关联的问题:
spring:
application:
name: grouptype/groupname/DB
cloud:
vault:
authentication: TOKEN
token: sometoken
generic:
enabled: true
backend: group
default-conext: grouptype/groupname/DB
host: 10.20.30.40
port: 8200
scheme: http
#uri: http://10.20.30.40:8200
connection-timeout: 5000
read-timeout: 15000
config:
order: -10
中等待吗?第二个答案:是的。但是实际上,您需要更彻底地冲洗,这将变得很清楚。
一旦您的@FinishBundle
方法成功,Beam流道将假定捆绑包已成功完成。但是您的@FinishBundle
仅发送请求-不能确保请求成功。因此,如果请求随后失败,则可能会丢失数据。您的@FinishBundle
方法实际上应该处于阻塞状态并等待@FinishBundle
的成功确认。顺便说一句,以上所有内容均应是幂等的,因为完成捆绑后,地震可能会发生并引起重试;-)
所以要回答第一个问题:您应该更改任何内容吗?就在上面。只要您确定在提交捆绑包之前就已经提交结果,这种方式的批处理就可以起作用。
您可能会发现这样做会导致您的管道运行变慢,因为TheApi
的发生频率比@FinishBundle
高。要跨捆绑批处理请求,您需要使用状态和计时器的低级功能。我在Query postgres jsonb by value regardless of keys上编写了您的用例的人为设计版本。我会对这对您的工作方式非常感兴趣。
当管道中存在持久的随机播放时,很可能只是您所期望的极低延迟(在低毫秒范围内)不可用。