Google cloud dataflow 如何在Apache Beam步骤中并行HTTP请求?
我有一个在Google Dataflow上运行的Apache Beam管道,其工作相当简单: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<
- 它从Pub/Sub读取单个JSON对象
- 解析它们
- 并通过HTTP将它们发送到某个API
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对“墙时间”的定义。我将给出