Google bigquery 通过数据流将云存储到BigQuery(upsert)

Google bigquery 通过数据流将云存储到BigQuery(upsert),google-bigquery,google-cloud-functions,google-cloud-dataflow,Google Bigquery,Google Cloud Functions,Google Cloud Dataflow,每当将文件写入云存储时,我希望它触发一个云函数,该函数执行一个数据流模板来转换文件内容并将结果写入BigQuery 我想我在大部分情况下都有一个把手。但问题是,我不需要仅仅插入到BQ表中,我需要upsert(使用合并操作)。这似乎是一个常见的需求,但Apache Beam BQ连接器不提供此选项(仅写、创建和截断/写) 于是我想。。。好的,如果我可以捕获数据流管道何时完成执行,我可以让数据流写入一个临时表,然后调用一个SQL合并查询将数据从临时表合并到目标表。然而,我没有看到在管道执行完成时触发

每当将文件写入云存储时,我希望它触发一个云函数,该函数执行一个数据流模板来转换文件内容并将结果写入BigQuery

我想我在大部分情况下都有一个把手。但问题是,我不需要仅仅插入到BQ表中,我需要upsert(使用合并操作)。这似乎是一个常见的需求,但Apache Beam BQ连接器不提供此选项(仅写、创建和截断/写)

于是我想。。。好的,如果我可以捕获数据流管道何时完成执行,我可以让数据流写入一个临时表,然后调用一个SQL合并查询将数据从临时表合并到目标表。然而,我没有看到在管道执行完成时触发云函数的任何方法

关于如何实现最终目标有什么建议吗


谢谢

在数据流作业结束时,没有本机内置的解决方案来生成事件。但是,由于日志,您可以作弊

为此:

  • 转到日志,选择高级过滤器(过滤器栏右侧的箭头)并粘贴此自定义过滤器:
您应该只看到数据流的末尾。然后,您必须在这个结果的PubSub中创建一个接收器。然后,您必须在这些PubSub消息上插入您的函数,然后您可以做您想要做的事情

为此,在填充自定义过滤器之后

  • 单击创建接收器
  • 设置接收器名称
  • 将目标设置为PubSub
  • 选择你的主题
  • 现在,在此主题上插入一个函数,它将仅在数据流结束时触发
我已经实现了确切的用例,但是您可以创建一个管道,而不是使用两个不同的管道

步骤1:从gcs读取文件并将其转换为TableRow

步骤2:从BigQuery读取整行

第3步:创建1个pardo,在其中您可以进行自定义的upsert操作,如下面的代码所示

PCollection<KV<String,TableRow>> val = p.apply(BigQueryIO.readTableRows().from(""));

PCollection<KV<String,TableRow>> val1 = p.apply(TextIO.read().from("")).apply(Convert to TableRow()));
PCollection val=p.apply(BigQueryIO.readTableRows().from(“”);
PCollection val1=p.apply(TextIO.read().from(“”)。apply(转换为TableRow());
步骤4:执行CoGroupByKey并在该结果上执行pardo以获得更新的结果(相当于合并操作)

步骤5:使用WRITE_TRUNCATE模式将完整的TableRow插入BQ。
这里的代码部分可能有点复杂,但使用单一管道的性能会更好。

有趣的问题,已经有一些好的想法,但我想展示另一种可能性,即仅使用Dataflow和BigQuery。如果这是一个非模板批处理作业,我们可以使用:

等待管道完成并返回最终状态

然后检查是否
完成
,如果需要,继续执行
合并
语句:

PipelineResult res=p.run();
res.waitUntilFinish();
if(res.getState()==PipelineResult.State.DONE){
LOG.info(“数据流作业已完成。合并结果…”);
合并结果();
LOG.info(“全部完成:)”;
}
为了测试这一点,我们可以创建一个BigQuery表(
upsert.full
),该表将包含最终结果并在每次运行时更新:

bq mk upsert
bq mk-t upsert.full name:STRING,总计:INT64
bq查询——使用_legacy_sql=false“插入upsert.full(名称、总数)值('tv',10),('laptop',20)”
首先,我们将用总共10台电视来填充它。但是现在让我们想象一下,我们销售了5台额外的电视机,在我们的数据流作业中,我们将使用新的校正值(15)向临时表(
upsert.temp
)写入一行:

p
.apply(“创建数据”,Create.of(“开始”))
.应用(“写入”,BigQueryIO)
.write()
。至(输出)
.withFormatFunction(
(字符串虚拟)->
新建TableRow().set(“名称”、“电视”).set(“总计”,15))
.withWriteDisposition(BigQueryIO.Write.WriteDisposition.Write\u TRUNCATE)
.withCreateDisposition(BigQueryIO.Write.CreateDisposition.CREATE如果需要)
.使用模式(schema));
现在我们要用以下查询()更新原始表:

MERGE upsert.full F
使用upsert.temp T
在T.name=F.name上
当匹配时
更新集总计=T.total
当不匹配时
插入(姓名、总数)
值(名称、总数)
因此,我们可以在
MergeResults
中使用BigQuery的Java客户端库:

BigQuery BigQuery=BigQueryOptions.getDefaultInstance().getService();
QueryJobConfiguration查询配置=
QueryJobConfiguration.newBuilder(
“合并upsert.full F”
+ ...
+“值(名称、总数)”)
.setUseLegacySql(false)
.build();
JobId JobId=JobId.of(UUID.randomUUID().toString());
Job queryJob=bigquery.create(JobInfo.newBuilder(queryConfig.setJobId(jobId.build());
这是基于此,其中包括一些基本的错误处理。请注意,您需要将其添加到
pom.xml
或等效文件中:


com.google.cloud

另一种方法是不使用模板。相反,您的云函数调用cloudbuild,它以阻塞模式运行管道。然后等待它完成,当它完成后继续你的下一步。当然,不利的一面是,您在等待的时候为云构建付费,但这非常便宜。另外,如果您的管道需要运行很长时间(小时),那么您可能会遇到问题。谢谢Guillaume。我在另一个问题上看到了你类似的答案。很好,很有创意!你好像在和其他人讨论,他们没有得到与你相同的日志输出。所以我有点回避这种做法,因为谷歌似乎可以在没有任何警告的情况下随时更改他们的日志文本。我希望找到一个更强大的解决方案,但如果它是可行的,我一定会记住这一点
PCollection<KV<String,TableRow>> val = p.apply(BigQueryIO.readTableRows().from(""));

PCollection<KV<String,TableRow>> val1 = p.apply(TextIO.read().from("")).apply(Convert to TableRow()));