Titan Cassandra 多线程事务锁定
Titan Cassandra Multithreaded Transactions Locking
我创建了一个 class,其中包含一个 Graph
。例如:
public class GraphManager(){
Graph graph;
public GraphManager(Graph graph){
this.graph = graph;
}
public void commitGraph(){
graph.commit();
}
}
这 GraphManager
允许我以预定义的方式与图形进行交互。我使用工厂构建这个 GraphManager
:
public class GraphManagerFactory(){
public static GraphManager getGraphManager(){
return new GraphManager(TitanFactory.open("conf/titan-cassandra.properties"));
}
}
这是基础框架。现在解决问题,使用休息控制器我收到一个 JSON 文件。这导致实例化一个 GraphManager
,它将文件转换为图形然后提交它。基本范式如下:
public class Controller(){
public List<String> handleRequest(){
GraphManager manager = GraphManagerFactory.getGraphManager();
//Do some work with graph manager
synchronised(Controller.class){
manager.commitGraph();
}
}
}
通过上面的代码,我保证在任何时候只有一个线程可以提交到图表。但是尽管如此,我仍然得到 PermanentLockingException
:
com.thinkaurelius.titan.diskstorage.locking.PermanentLockingException: Local lock contention
at com.thinkaurelius.titan.diskstorage.locking.AbstractLocker.writeLock(AbstractLocker.java:313) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.diskstorage.locking.consistentkey.ExpectedValueCheckingStore.acquireLock(ExpectedValueCheckingStore.java:89) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.diskstorage.keycolumnvalue.KCVSProxy.acquireLock(KCVSProxy.java:40) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.diskstorage.BackendTransaction.acquireIndexLock(BackendTransaction.java:240) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.graphdb.database.StandardTitanGraph.prepareCommit(StandardTitanGraph.java:554) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.graphdb.database.StandardTitanGraph.commit(StandardTitanGraph.java:683) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.graphdb.transaction.StandardTitanTx.commit(StandardTitanTx.java:1352) [titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.graphdb.tinkerpop.TitanBlueprintsGraph$GraphTransaction.doCommit(TitanBlueprintsGraph.java:263) [titan-core-1.0.0.jar:na]
at org.apache.tinkerpop.gremlin.structure.util.AbstractTransaction.commit(AbstractTransaction.java:94) [gremlin-core-3.0.2-incubating.jar:3.0.2-incubating]
at io.mindmaps.core.accessmanager.GraphAccessManagerImpl.commit(GraphAccessManagerImpl.java:811) [mindmaps-core-0.0.5-SNAPSHOT.jar:na]
at io.mindmaps.graphmanager.listener.TransactionController.commitGraph(TransactionController.java:98) [classes/:na]
at io.mindmaps.graphmanager.listener.TransactionController.validateAndCommit(TransactionController.java:84) [classes/:na]
at io.mindmaps.graphmanager.listener.TransactionController.loadData(TransactionController.java:66) [classes/:na]
at io.mindmaps.graphmanager.listener.TransactionController.lambda$postTransaction[=13=](TransactionController.java:43) [classes/:na]
at io.mindmaps.graphmanager.loader.QueueManager.handleJob(QueueManager.java:76) ~[classes/:na]
at io.mindmaps.graphmanager.loader.QueueManager.lambda$addJob(QueueManager.java:24) ~[classes/:na]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_66]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[na:1.8.0_66]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[na:1.8.0_66]
at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_66]
一次只允许一次提交时怎么会发生这种情况?
首先,我建议您不要为每个请求创建一个 TitanGraph
实例(昂贵)。创建一个 TitanGraph
并跨请求共享。接下来,您需要非常小心 Web 应用程序,确保事务不会在请求之间泄漏(事务绑定到当前线程)。您可以通过在请求完成时始终发出 rollback()
或 commit()
来确保请求自行清理(根据需要错误或成功)来确保这一点。您可以通过在新请求开始时发出 rollback()
来加倍确保这一点。
考虑到所有这些,让我们回答您的问题。仅仅因为您已将 commit()
操作限制为单个线程并不会阻止其他线程打开事务。由不同线程处理的另一个请求可以很容易地尝试 grab a lock 相同的键并在提交时被阻止,以您看到的锁定异常结束。
"Transaction will eventually fail in sufficiently large systems." PermanentLockingException
必须被视为使用锁的预期 side-effect,处理它们的典型方法是在遇到锁时重试整个事务。您应该在此前提下设计您的基础架构。
这方面的一些其他技巧:
- 您应该尽可能限制锁的使用,因为它们可能成为瓶颈。如果有任何方法可以在没有它们的情况下工作 - 那就这样做吧。
- 当您发布一些抢锁的更改时,请保持事务简短。他们保持开放的时间越长,与另一个请求争用的机会就越大。
虽然我接受的答案是 100% 正确的。我想更清楚地强调我为避免锁争用所做的工作(其中大部分是 based/thanks 接受的答案):
第 1 步: 按照建议,我在每个 GraphManager
中都包装了一个新事务,而不是包装图的实例。即我按如下方式制作工厂:
public class GraphManagerFactory(){
TitanGraph instance;
public static GraphManager getGraphManager(){
if(instance = null){
instance = TitanFactory.open("conf/titan-cassandra.properties");
}
return new GraphManager(instance.newTransaction());
}
}
这一步带来了很大的改进。我仍然有锁争用,但它们得到了更快的解决。
第二步:第一次做图的时候我也提前提供了schema。具体来说,我什至在添加第一个顶点之前就明确地构建了它们,而不是让 titan 隐式构建顶点属性和边。这是对边标签使用 management.makeEdgeLabel(label).make();
对顶点属性使用 management.makePropertyKey(label).dataType(String.class).make();
的简单问题。这样做的另一个好处是我可以更轻松地执行批量加载。这意味着再次将 Factory
扩展为:
public class GraphManagerFactory(){
TitanGraph instance;
public static GraphManager getGraphManager(){
if(instance = null){
instance = TitanFactory.open("conf/titan-cassandra.properties");
TitanManagement management = instance.openManagement();
//Check if the labels exist before creating explicitly.
//If they don't exist do the following:
management.makeEdgeLabel("EdgeLabel").make();
management.makePropertyKey("property").dataType(String.class).make();
management.commit();
}
return new GraphManager(instance.newTransaction());
}
}
第 3 步: 几乎完全消除争用的最后一步是将 id 块大小增加到 graph.configuration().setProperty("ids.block-size", 100000);
。这最后一步可能只适用于我,因为我同时执行大量加载操作。
我创建了一个 class,其中包含一个 Graph
。例如:
public class GraphManager(){
Graph graph;
public GraphManager(Graph graph){
this.graph = graph;
}
public void commitGraph(){
graph.commit();
}
}
这 GraphManager
允许我以预定义的方式与图形进行交互。我使用工厂构建这个 GraphManager
:
public class GraphManagerFactory(){
public static GraphManager getGraphManager(){
return new GraphManager(TitanFactory.open("conf/titan-cassandra.properties"));
}
}
这是基础框架。现在解决问题,使用休息控制器我收到一个 JSON 文件。这导致实例化一个 GraphManager
,它将文件转换为图形然后提交它。基本范式如下:
public class Controller(){
public List<String> handleRequest(){
GraphManager manager = GraphManagerFactory.getGraphManager();
//Do some work with graph manager
synchronised(Controller.class){
manager.commitGraph();
}
}
}
通过上面的代码,我保证在任何时候只有一个线程可以提交到图表。但是尽管如此,我仍然得到 PermanentLockingException
:
com.thinkaurelius.titan.diskstorage.locking.PermanentLockingException: Local lock contention
at com.thinkaurelius.titan.diskstorage.locking.AbstractLocker.writeLock(AbstractLocker.java:313) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.diskstorage.locking.consistentkey.ExpectedValueCheckingStore.acquireLock(ExpectedValueCheckingStore.java:89) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.diskstorage.keycolumnvalue.KCVSProxy.acquireLock(KCVSProxy.java:40) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.diskstorage.BackendTransaction.acquireIndexLock(BackendTransaction.java:240) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.graphdb.database.StandardTitanGraph.prepareCommit(StandardTitanGraph.java:554) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.graphdb.database.StandardTitanGraph.commit(StandardTitanGraph.java:683) ~[titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.graphdb.transaction.StandardTitanTx.commit(StandardTitanTx.java:1352) [titan-core-1.0.0.jar:na]
at com.thinkaurelius.titan.graphdb.tinkerpop.TitanBlueprintsGraph$GraphTransaction.doCommit(TitanBlueprintsGraph.java:263) [titan-core-1.0.0.jar:na]
at org.apache.tinkerpop.gremlin.structure.util.AbstractTransaction.commit(AbstractTransaction.java:94) [gremlin-core-3.0.2-incubating.jar:3.0.2-incubating]
at io.mindmaps.core.accessmanager.GraphAccessManagerImpl.commit(GraphAccessManagerImpl.java:811) [mindmaps-core-0.0.5-SNAPSHOT.jar:na]
at io.mindmaps.graphmanager.listener.TransactionController.commitGraph(TransactionController.java:98) [classes/:na]
at io.mindmaps.graphmanager.listener.TransactionController.validateAndCommit(TransactionController.java:84) [classes/:na]
at io.mindmaps.graphmanager.listener.TransactionController.loadData(TransactionController.java:66) [classes/:na]
at io.mindmaps.graphmanager.listener.TransactionController.lambda$postTransaction[=13=](TransactionController.java:43) [classes/:na]
at io.mindmaps.graphmanager.loader.QueueManager.handleJob(QueueManager.java:76) ~[classes/:na]
at io.mindmaps.graphmanager.loader.QueueManager.lambda$addJob(QueueManager.java:24) ~[classes/:na]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_66]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[na:1.8.0_66]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[na:1.8.0_66]
at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_66]
一次只允许一次提交时怎么会发生这种情况?
首先,我建议您不要为每个请求创建一个 TitanGraph
实例(昂贵)。创建一个 TitanGraph
并跨请求共享。接下来,您需要非常小心 Web 应用程序,确保事务不会在请求之间泄漏(事务绑定到当前线程)。您可以通过在请求完成时始终发出 rollback()
或 commit()
来确保请求自行清理(根据需要错误或成功)来确保这一点。您可以通过在新请求开始时发出 rollback()
来加倍确保这一点。
考虑到所有这些,让我们回答您的问题。仅仅因为您已将 commit()
操作限制为单个线程并不会阻止其他线程打开事务。由不同线程处理的另一个请求可以很容易地尝试 grab a lock 相同的键并在提交时被阻止,以您看到的锁定异常结束。
"Transaction will eventually fail in sufficiently large systems." PermanentLockingException
必须被视为使用锁的预期 side-effect,处理它们的典型方法是在遇到锁时重试整个事务。您应该在此前提下设计您的基础架构。
这方面的一些其他技巧:
- 您应该尽可能限制锁的使用,因为它们可能成为瓶颈。如果有任何方法可以在没有它们的情况下工作 - 那就这样做吧。
- 当您发布一些抢锁的更改时,请保持事务简短。他们保持开放的时间越长,与另一个请求争用的机会就越大。
虽然我接受的答案是 100% 正确的。我想更清楚地强调我为避免锁争用所做的工作(其中大部分是 based/thanks 接受的答案):
第 1 步: 按照建议,我在每个 GraphManager
中都包装了一个新事务,而不是包装图的实例。即我按如下方式制作工厂:
public class GraphManagerFactory(){
TitanGraph instance;
public static GraphManager getGraphManager(){
if(instance = null){
instance = TitanFactory.open("conf/titan-cassandra.properties");
}
return new GraphManager(instance.newTransaction());
}
}
这一步带来了很大的改进。我仍然有锁争用,但它们得到了更快的解决。
第二步:第一次做图的时候我也提前提供了schema。具体来说,我什至在添加第一个顶点之前就明确地构建了它们,而不是让 titan 隐式构建顶点属性和边。这是对边标签使用 management.makeEdgeLabel(label).make();
对顶点属性使用 management.makePropertyKey(label).dataType(String.class).make();
的简单问题。这样做的另一个好处是我可以更轻松地执行批量加载。这意味着再次将 Factory
扩展为:
public class GraphManagerFactory(){
TitanGraph instance;
public static GraphManager getGraphManager(){
if(instance = null){
instance = TitanFactory.open("conf/titan-cassandra.properties");
TitanManagement management = instance.openManagement();
//Check if the labels exist before creating explicitly.
//If they don't exist do the following:
management.makeEdgeLabel("EdgeLabel").make();
management.makePropertyKey("property").dataType(String.class).make();
management.commit();
}
return new GraphManager(instance.newTransaction());
}
}
第 3 步: 几乎完全消除争用的最后一步是将 id 块大小增加到 graph.configuration().setProperty("ids.block-size", 100000);
。这最后一步可能只适用于我,因为我同时执行大量加载操作。