我有一个包含数亿个节点和关系的非常大的图形,我需要进行遍历以查找特定节点是否与包含特定属性的另一个节点连接。 数据高度互连,对于一对节点,可以有多个关系链接它们。
鉴于此操作需要在实时系统上运行,我有非常严格的时间限制,需要不超过200毫秒才能找到可能的结果。
所以我创建了以下TraversalDescriptor:
TraversalDescription td = graph.traversalDescription()
.depthFirst()
.uniqueness(Uniqueness.NODE_GLOBAL)
.expand(new SpecificRelsPathExpander(requiredEdgeProperty)
.evaluator(new IncludePathWithTargetPropertyEvaluator(targetNodeProperty));
如果结束节点是我的目标,评估者会检查每条路径,包括并修剪路径(如果是这种情况或排除它),如果不是,则继续。 此外,我设置了遍历所花费的时间和要查找的最大结果数量的限制。 所有内容都可以在下面的代码中看到:
private class IncludePathWithTargetPropertyEvaluator implements Evaluator {
private String targetProperty;
private int results;
private long startTime, curTime, elapsed;
public IncludePathWithTargetPropertyEvaluator(String targetProperty) {
this.targetProperty = targetProperty;
this.startTime = System.currentTimeMillis();
this.results = 0;
}
public Evaluation evaluate(Path path) {
curTime = System.currentTimeMillis();
elapsed = curTime - startTime;
if (elapsed >= 200) {
return Evaluation.EXCLUDE_AND_PRUNE;
}
if (results >= 3) {
return Evaluation.EXCLUDE_AND_PRUNE;
}
String property = (String) path.endNode().getProperty("propertyName");
if (property.equals(targetProperty)) {
results = results + 1;
return Evaluation.INCLUDE_AND_PRUNE;
}
return Evaluation.EXCLUDE_AND_CONTINUE;
}
最后我编写了一个自定义PathExpander,因为每次我们只需要遍历具有特定属性值的边:
private class SpecificRelsPathExpander implements PathExpander {
private String requiredProperty;
public SpecificRelsPathExpander(String requiredProperty) {
this.requiredProperty = requiredProperty;
}
public Iterable<Relationship> expand(Path path, BranchState<Object> state) {
Iterable<Relationship> rels = path.endNode().getRelationships(RelTypes.FOO, Direction.BOTH);
if (!rels.iterator().hasNext())
return null;
List<Relationship> validRels = new LinkedList<Relationship>();
for (Relationship rel : rels) {
String property = (String) rel.getProperty("propertyName");
if (property.equals(requiredProperty)) {
validRels.add(rel);
}
}
return validRels;
}
// not used
public PathExpander<Object> reverse() {
return null;
}
问题是,在200ms过去之后,穿越者会继续前进。
根据我的理解,评估程序的行为是为使用EXCLUDE_AND_CONTINUE评估的每个路径排队所有后续分支,并且遍历器本身在访问队列中的所有后续路径之前不会停止。
所以可能发生的情况是:如果我的节点数量非常少,那么将会导致数千条路径被遍历。
在这种情况下,有没有办法让遍历在达到超时时突然停止并返回在while中找到的可能的有效路径?
答案 0 :(得分:0)
我会采用以下思路:
超时结束后,停止展开图表。
private class SpecificRelsPathExpander implements PathExpander {
private String requiredProperty;
private long startTime, curTime, elapsed;
public SpecificRelsPathExpander(String requiredProperty) {
this.requiredProperty = requiredProperty;
this.startTime = System.currentTimeMillis();
}
public Iterable<Relationship> expand(Path path, BranchState<Object> state) {
curTime = System.currentTimeMillis();
elapsed = curTime - startTime;
if (elapsed >= 200) {
return null;
}
Iterable<Relationship> rels = path.endNode().getRelationships(RelTypes.FOO, Direction.BOTH);
if (!rels.iterator().hasNext())
return null;
List<Relationship> validRels = new LinkedList<Relationship>();
for (Relationship rel : rels) {
String property = (String) rel.getProperty("propertyName");
if (property.equals(requiredProperty)) {
validRels.add(rel);
}
}
return validRels;
}
// not used
public PathExpander<Object> reverse() {
return null;
}
我认为看看Neo4J TraversalDescription Definition也可能对你有益。
答案 1 :(得分:0)
我会实现扩展器来保持遍历框架的惰性,也是为了它的简单代码。这样可以防止遍历急切地收集节点的所有关系,如下所示:
public class SpecificRelsPathExpander implements PathExpander, Predicate<Relationship>
{
private final String requiredProperty;
public SpecificRelsPathExpander( String requiredProperty )
{
this.requiredProperty = requiredProperty;
}
@Override
public Iterable<Relationship> expand( Path path, BranchState state )
{
Iterable<Relationship> rels = path.endNode().getRelationships( RelTypes.FOO, Direction.BOTH );
return Iterables.filter( this, rels );
}
@Override
public boolean accept( Relationship relationship )
{
return requiredProperty.equals( relationship.getProperty( "propertyName", null ) );
}
// not used
@Override
public PathExpander<Object> reverse()
{
return null;
}
}
只要客户端,即持有Iterator的人从启动遍历调用hasNext / next开始,遍历将继续。它本身没有遍历,一切都发生在hasNext / next。