Java 数据流DoFn中的数据存储查询在云中运行时会减慢管道速度

Java 数据流DoFn中的数据存储查询在云中运行时会减慢管道速度,java,google-cloud-datastore,google-cloud-dataflow,Java,Google Cloud Datastore,Google Cloud Dataflow,我试图通过在DoFn步骤中查询数据存储来增强管道中的数据。 CustomClass类中对象的字段用于对数据存储表进行查询,返回的值用于增强对象 代码如下所示: public class EnhanceWithDataStore extends DoFn<CustomClass, CustomClass> { private static Datastore datastore = DatastoreOptions.defaultInstance().service(); priva

我试图通过在DoFn步骤中查询数据存储来增强管道中的数据。 CustomClass类中对象的字段用于对数据存储表进行查询,返回的值用于增强对象

代码如下所示:

public class EnhanceWithDataStore extends DoFn<CustomClass, CustomClass> {

private static Datastore datastore = DatastoreOptions.defaultInstance().service();
private static KeyFactory articleKeyFactory = datastore.newKeyFactory().kind("article");

@Override
public void processElement(ProcessContext c) throws Exception {

    CustomClass event = c.element();

    Entity article = datastore.get(articleKeyFactory.newKey(event.getArticleId()));

    String articleName = "";
    try{
        articleName = article.getString("articleName");         
    } catch(Exception e) {}

    CustomClass enhanced = new CustomClass(event);
    enhanced.setArticleName(articleName);

    c.output(enhanced);
}
public类EnhanceWithDataStore扩展了DoFn{
私有静态数据存储Datastore=DatastoreOptions.defaultInstance().service();
私有静态KeyFactory articleKeyFactory=datastore.newKeyFactory().kind(“article”);
@凌驾
public void processElement(ProcessContext c)引发异常{
CustomClass事件=c.element();
实体article=datastore.get(articleKeyFactory.newKey(event.getArticleId());
字符串articleName=“”;
试一试{
articleName=article.getString(“articleName”);
}捕获(例外e){}
CustomClass增强=新CustomClass(事件);
增强的.setArticleName(articleName);
c、 产出(增加);
}
当它在本地运行时,速度很快,但当它在云中运行时,这一步会显著降低管道速度。这是什么原因造成的?有什么解决方法或更好的方法来做到这一点吗

可以在此处找到管道的图片(最后一步是增强步骤):
您在这里所做的是将您的输入
PCollection
与数据存储中的增强功能连接起来

对于
PCollection
的每个分区,对数据存储的调用都将是单线程的,因此会产生大量延迟。我认为
DirectPipelineRunner
InProcessPipelineRunner
中的调用速度会很慢。通过自动缩放和动态工作再平衡,您应该可以在运行时看到并行性在数据流服务上,除非您的数据流结构导致我们对其进行了很差的优化,所以您可以尝试增加
--maxNumWorkers
。但您仍然无法从批量操作中获益

最好在管道中表达这种连接,使用
DatastoreIO.readFrom(…)
然后进行
CoGroupByKey
转换。这样,数据流将对所有增强进行批量并行读取,并使用高效的
GroupByKey
机制将它们与事件对齐

//以下是您要加入的两个集合
PCollection事件=。。。;
PCollection articles=DatastoreIO.readFrom(…);
//通过公共id为它们设置密钥
PCollection键事件=
events.apply(使用keys.of(event->event.getArticleId())
P收集=
articles.apply(withkey.of(article->article.getKey().getId())
//通过为每个集合提供标记来设置联接
TupleTag eventTag=新的TupleTag(){};
TupleTag articleTag=新的TupleTag(){};
KeyedPCollectionTuple cogbk输入=
KeyedPCollectionTuple
.of(事件标签、关键事件)
.和(物品标签、关键物品);
PCollection enhancedEvents=cogbk输入
.apply(CoGroupByKey.create())
.apply(MapElements.via)(CoGbkResult-joinResult->{
for(CustomClass事件:joinResult.getAll(eventTag)){
字符串名称;
试一试{
articleName=joinResult.getOnly(articleTag.getString(“articleName”);
}捕获(例外e){
articleName=“”;
}
CustomClass增强=新CustomClass(事件);
增强的.setArticleName(articleName);
回报增加;
}
});
另一种可能性是,如果内存中存储查找的文章非常少,那么可以使用
DatastoreIO.readFrom(…)
,然后通过
View.asMap()
将它们作为地图端输入读取,并在本地表中查找

//以下是您要加入的两个集合
PCollection事件=。。。;
PCollection articles=DatastoreIO.readFrom(…);
//键入文章并创建地图视图
PCollectionView=articleView
.apply(WithKeys.of(article->article.getKey().getId())
.apply(View.asMap());
//对ParDo进行查找并排连接输入
PCollection增强=事件
.apply(ParDo.WITHIDEINPUTS(条款)of(新DoFn(){
@凌驾
公共void processElement(ProcessContext c){
Map articleLookup=c.sideInput(articleView);
字符串名称;
试一试{
商品名称=
articleLookup.get(event.getArticleId()).getString(“articleName”);
}捕获(例外e){
articleName=“”;
}
CustomClass增强=新CustomClass(事件);
增强的.setArticleName(articleName);
回报增加;
}
});

根据您的数据,这两种方法中的任何一种都可能是更好的选择。

经过一些检查后,我成功地找出了问题:项目位于欧盟(因此,数据存储位于欧盟区域;与AppEningine区域相同),而数据流作业本身(以及工人)默认情况下(不覆盖区域选项时)托管在美国


性能的差异是25-30倍:~40个元素/秒,而15名员工的性能为~1200个元素/秒。

如果您愿意共享工作id,我们可以直接查看。嗨,Kenn,工作id是:2016-10-14_10_34_39-5525093815482139851。感谢您的关注。代码看起来不错(这是从数据流查询数据存储的最佳实践吗)?在再次查看之后,我提供了我认为最好的答案,然后尝试在较低级别进行调试。嘿,Kenn,谢谢你的回答。我很快会尝试一下。我发现这个解决方案的唯一问题是,如果数据存储得到更新(我们可能在运行管道时写入它),这些更改在管道中不可用?有没有办法解决这个问题(如果不重新运行管道,让我们在流模式下运行它)?您是正确的-如果在管道运行时更新它,您将获得特定的快照。