Transaction Handling
- Every graph operation in JanusGraph occurs within the context of a transaction. According to the TinkerPop’s transactional specification, each thread opens its own transaction against the graph database with the first operation (i.e. retrieval or mutation) on the graph:
graph = JanusGraphFactory.open("berkeleyje:/tmp/janusgraph")
juno = graph.addVertex() //Automatically opens a new transaction
juno.property("name", "juno")
graph.tx().commit() //Commits transaction
Janusgraph的事务通常不是ACID的,当存储后端是BerkeleyDB时这样配置问题不大。但是当存储后端是Cassandra,hbase时,通常不支持ACID, 因为他们不支持串行隔离或者多行的原子写操作,如果要让janusgraph上层在这种存储后端下支持ACID属性,代价非常昂贵,需要使用分布式锁来实现。
hbase,Cassandra只可以保证行(row)级别的原子性,如果一个操作涉及到多行数据的操作,那么habase/c无法保证操作该操作的原子性。
janusgraph顶点的属性,边信息都存储在要给row中,所以一个顶点的多线程操作在hbase上时可以保证ACID中的C。如果涉及到多个顶点的操作要想保证C那么要用分布式锁,当然同时也保持了A.
Transactional Scope
- 图元素(vertices, edges, and types)具有事务作用域,一个事务被关闭后(commit() or rollback().),图元素就过时和不可以访问了。
- 不过顶点、类型元素会被自动转移到新的事务中。但是边就不会自动转移到新事物,其必须显示转移:对应的操作是g.E(e).next()。
graph = JanusGraphFactory.open("berkeleyje:/tmp/janusgraph")
juno = graph.addVertex() //Automatically opens a new transaction
graph.tx().commit() //Ends transaction
juno.property("name", "juno") //Vertex is automatically transitioned
e = juno.addEdge("knows", graph.addVertex())
graph.tx().commit() //Ends transaction
e = g.E(e).next() //Need to refresh edge
e.property("time", 99)
但是对于在Multi-Threaded Transactions(下面介绍)的元素,一旦事务关闭,都需要显示刷新,对应操作:g.V(existingVertex) 和g.E(existingEdge).
如果A事务中查询一个顶点的边没查到,此时B事务中为该顶点添加了边并提交成功,那么A在没有提交的情况下再次查询顶点的边依旧查不到。所以在一个操作完成后提交事务是很有必要的。
Transaction Failures
对于Potentially temporary failures(一般由IO拥堵、网络超时造成) ,JanusGraph 会在稍后自动重试,重试的次数和稍后时间可通过配置文件配置。
-
Permanent exceptions :造成原因连接断开connection loss 、硬件损坏 hardware failure、锁竞争lock contention。
- 锁竞争: 比如修改在一个开启的事务中修改name为zhouliang,但是在事务提交时,该name的锁被另一个事务获得,此时就会产生锁竞争,导致另一个事务失败。在锁竞争中失败的事务会重新运行整个事务从而从失败中恢复。
-
Permanent exceptions that can fail a transaction include:
- PermanentLockingException(Local lock contention): Another local thread has already been granted a conflicting lock.
- PermanentLockingException(Expected value mismatch for X: expected=Y vs actual=Z): The verification that the value read in this transaction is the same as the one in the datastore after applying for the lock failed. In other words, another transaction modified the value after it had been read and modified.
Multi-Threaded Transactions
- 多线程事务:支持多个线程在同一个事务中并发操作同一个图对象,从而提供事务处理能力和利用多核架构。
threadedGraph = graph.tx().createThreadedTx();
threads = new Thread[10];
for (int i=0; i<threads.length; i++) {
threads[i]=new Thread({
println("Do something with 'threadedGraph''");
});
threads[i].start();
}
for (int i=0; i<threads.length; i++) threads[i].join();
threadedGraph.tx().commit();
- 最后一个线程运行完成后,事务就会提交。
- The method newTransaction is used to start multi-threaded transactions only.
- JanusGraph.buildTransaction() method gives the user the ability to configure and start a new multi-threaded transaction against a JanusGraph
.
Nested Transactions
- 嵌入事务:不同于上面的环绕事务(一个事务环绕多个线程)
- 需求:一个事务中执行时间很长,并且有添加节点with a unique name操作,为了保证name唯一,所以需要通过锁来实现,正如上面的“锁竞争”问题,如果一个事务中执行时间很长,又有添加唯一名称顶点操作,所以在最后提交事务时很容易失败。想法:针对添加唯一名称顶点操作,可以在整个事务中通过一个嵌套事务来完成。
v1 = graph.addVertex()
//Do many other things
tx = graph.tx().createThreadedTx()
v2 = tx.addVertex()
v2.property("uniqueName", "foo")
tx.commit() // Any lock contention will be detected here
v1.addEdge("related", g.V(v2).next()) // Need to load v2 into outer transaction
//Do many other things
graph.tx().commit() // Can't fail due to uniqueName write lock contention involving v2
Transaction Configuration
- 用于调节事务