前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【翻译】图解Janusgraph系列-事务详解(Janusgraph Transactions)

【翻译】图解Janusgraph系列-事务详解(Janusgraph Transactions)

作者头像
洋仔聊编程
发布2022-05-11 10:04:45
7870
发布2022-05-11 10:04:45
举报

图解Janusgraph系列-事务详解(janusgraph transactions)

大家好,我是洋仔,JanusGraph图解系列文章,`实时更新`~

图数据库文章总目录:

源码分析相关可查看github(求star~~): https://github.com/YYDreamer/janusgraph

下述流程高清大图地址:https://www.processon.com/view/link/5f471b2e7d9c086b9903b629

版本:JanusGraph-0.5.2

转载文章请保留以下声明:  >作者:洋仔聊编程  >微信公众号:匠心Java  >原文地址:[https://liyangyang.blog.csdn.net/](https://liyangyang.blog.csdn.net/)

几乎所有与JanusGraph的交互都与事务相关联。JanusGraph事务对于多个线程并发使用是安全的。JanusGraph实例上的方法,如graph.V(..)和graph.tx().commit()执行ThreadLocal查找以检索或创建与调用线程关联的事务。调用者可以选择放弃ThreadLocal事务管理,转而调用 graph.tx().createThreadedTx(),它返回对事务对象的引用,其中包含读/写图数据和提交或回滚的方法。

JanusGraph交易不一定是ACID。它们可以在BerkeleyDB上进行这样的配置,但在Cassandra或HBase上通常不会这样,因为在这些地方,底层存储系统不提供可序列化的隔离或多行原子写入,并且模拟这些属性的成本会很高。

本节描述了JanusGraph的事务语义和API。

1  Transaction 处理

JanusGraph中的每个图形操作都发生在事务的上下文中。根据TinkerPop的事务规范,每个线程执行图形上的第一个操作(即 retrieval 或 mutation)时便会打开针对图形数据库的事务:

代码语言:javascript
复制
graph = JanusGraphFactory.open("berkeleyje:/tmp/janusgraph")

juno = graph.addVertex() //自定开启一个新的事务

juno.property("name", "juno")

graph.tx().commit() //事务提交

在此示例中,打开了一个本地JanusGraph图数据库。添加顶点“juno”是第一个操作所以自动开启了一个事务(在此线程中)。所有后续操作都在同一事务的上下文中进行,直到事务显式停止或图形数据库关闭为止。如果在close()调用时事务仍处于打开状态,那么未完成事务的行为在技术上是未定义的。实际上,任何非线程绑定事务通常都会被有效回滚,但属于调用shutdown的线程的线程绑定事务将首先被提交。请注意,读取和写入操作都发生在事务的上下文中。

Transactional 范围

所有图形元素(vertices, edges, types)都与检索或创建它们的事务范围相关联。在TinkerPop的默认事务语义下,随着图形上的第一个操作自动创建事务,并使用commit()或rollback()显式的关闭事务。关闭事务后,与该事务关联的所有图形元素都将过时且不可用。但是,JanusGraph会自动将vertices和types转换为新的事务范围,如下例所示:

代码语言:javascript
复制
graph = JanusGraphFactory.open("berkeleyje:/tmp/janusgraph")

juno = graph.addVertex() //自动创建一个事务

graph.tx().commit() //事务结束

juno.property("name", "juno") //节点自动转换为新的事务

另一方面,edge不会自动转换新的事务,也不能在原始事务之外访问。他们必须明确过渡:

代码语言:javascript
复制
e = juno.addEdge("knows", graph.addVertex())

graph.tx().commit() /事务结束

e = g.E(e).next() // 需要去刷新边 , next()为获取e边集合的第一个

e.property("time", 99) // 只有上一步刷新过 接下来才可以使用

3 Transaction 失败

提交事务时,JanusGraph将尝试将所有更改保留到存储后端。由于IO异常,网络错误,计算机崩溃或资源不可用,这可能并不总是成功。因此,交易可能会失败。事实上,在足够大的系统中,事务总会存在失败。因此,我们强烈建议您的代码处理此类失败:

代码语言:javascript
复制
try {

    if (g.V().has("name", name).iterator().hasNext())

        throw new IllegalArgumentException("Username already taken: " + name)

    user = graph.addVertex()

    user.property("name", name)

    graph.tx().commit()

} catch (Exception e) {

    // 恢复、重试、或者抛出错误信息

    println(e.getMessage())

}

上面的示例演示了简化的用户注册实现,其中name是希望注册的用户的名称。首先,检查具有该名称的用户是否已经存在。如果不是,则创建新的用户顶点并分配名称。最后,提交事务。

如果事务失败,则抛出一个JanusGraphException。事务可能失败的原因有很多种。JanusGraph区分潜在的临时故障 和 永久性故障。

潜在的临时故障是与资源不可用和IO超时(例如网络超时)相关的故障。JanusGraph会在一段延迟后重试保持事务状态,自动尝试从临时故障中恢复。重试尝试次数和重试延迟是可配置的(请参阅第15章,配置参考)。

完全连接丢失,硬件故障或锁争用可能导致永久性故障。要了解锁争用的原因,请考虑上面的注册示例,并假设用户尝试使用用户名“juno”进行注册。该用户名可能仍然在事务开始时可用,但是在提交事务时,另一个用户可能同时注册了“juno”,并且该事务保持对用户名的锁定,从而导致另一个事务失败。根据事务语义,可以通过重新运行整个事务从锁争用失败中恢复。

可能导致事务失败的永久性异常包括:

  • PermanentLockingException(本地锁争用):另一个本地线程已被授予冲突锁。
  • PermanentLockingException(X的预期值不匹配:expected = Y vs actual = Z):验证此事务中读取的值与申请锁定后数据存储区中的值相同失败。换句话说,另一个事务在读取和修改后修改了该值。

4 多线程Transactions

JanusGraph通过TinkerPop的线程事务支持多线程事务。因此,为了加速事务处理并利用多核架构,多个线程可以在单个事务中并发运行。

使用TinkerPop的默认事务处理,每个线程都会自动对图形数据库打开自己的事务。要打开与线程无关的事务,请使用该createThreadedTx()方法。

代码语言:javascript
复制
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();

createThreadedTx()方法返回一个新的Graph对象,该对象表示这个新打开的事务。图形对象tx支持原始图形的所有方法,但是不会为每个线程打开新事务。这允许我们启动多个线程,这些线程在同一个事务中同时工作,其中一个线程最终在所有线程完成工作时提交事务。

JanusGraph依靠优化的并发数据结构来支持在单个事务中高效运行的数百个并发线程。

5 并发算法

通过createThreadedTx()启动的事务独立于线程,这在实现并发图形算法时特别有用。大多数遍历或消息传递(以自我为中心)的图形算法都是令人尴尬的并行,这意味着它们可以通过多个线程轻松并行化并执行。这些线程中的每一个都可以在Graph返回的单个对象上操作createThreadedTx()而不会相互阻塞。

6 嵌套 Transactions

线程独立于事务的另一个用例是嵌套事务,它应该独立于周围的事务。

例如,假设一个长时间运行的事务作业必须创建一个具有唯一名称的新顶点。由于强制使用唯一名称需要获取锁(有关更多详细信息请参阅第34章,最终一致存储后端),并且由于事务运行了很长时间,因此可能会出现锁定拥塞和代价高昂的事务性故障。

代码语言:javascript
复制
v1 = graph.addVertex()

//Do many other things

v2 = graph.addVertex()

v2.property("uniqueName", "foo")

v1.addEdge("related", v2)

//Do many other things

graph.tx().commit() // 由于其uniqueName锁争用,这个长时间运行的tx可能会失败

解决此问题的一种方法是在一个简短的独立于事务的嵌套线程中创建顶点,如下面的伪代码所示:

代码语言:javascript
复制
v1 = graph.addVertex()

//Do many other things


tx = graph.tx().createThreadedTx()  // 开启嵌套事务

v2 = tx.addVertex()

v2.property("uniqueName", "foo")

tx.commit() // 此处将检测到任何的锁争用  // 嵌套事务结束


v1.addEdge("related", g.V(v2).next()) //需要将v2加载到外部事务中

//Do many other things

graph.tx().commit() // 不会因为涉及v2的uniqueName写锁争用而失败

7 常见的事务处理问题

通过针对图形执行的第一个操作自动启动事务。不必手动启动事务。方法newTransaction仅用于启动多线程事务

事务在TinkerPop语义下自动启动,但不会自动终止。必须使用commit()或手动终止交易rollback()。如果commit()事务失败,则应rollback()在捕获失败后手动终止。手动终止事务是必要的,因为只有用户知道事务边界。

事务将尝试从事务开始时维护其状态。这可能会导致多线程应用程序中的意外行为,如以下人工示例所示:

代码语言:javascript
复制
v = g.V(4).next() // 第一个图形操作,自动启动事务

g.V(v).bothE()

>> returns nothing, v has no edges

//线程空闲几秒钟,另一个线程向v添加边

g.V(v).bothE()

>> 仍然不返回任何值,因为事务从一开始就维护事务状态

这种意外行为很可能发生在客户端 - 服务器应用程序中,其中服务器维护多个线程来应答客户端请求。因此,在一个工作单元(例如代码片段,查询等)之后终止事务是很重要的。所以,上面的例子应该是:

代码语言:javascript
复制
v = g.V(4).next() // 第一个图形操作,自动启动事务

g.V(v).bothE()

graph.tx().commit() // 提交事务,避免出现上述问题

//线程空闲几秒钟,另一个线程向v添加边


g.V(v).bothE() // 此处也相当于第一个图形操作,自动启动事务

>> 返回了添加的边

graph.tx().commit()

当通过newTransaction在该事务范围内检索或创建的所有顶点和边缘使用多线程事务时,在该事务的范围之外不可用。在事务关闭后访问这些元素将导致异常。如上例所示,必须使用 g.V(existingVertex) 或 g.E(existingEdge) 在新事务中显式刷新此类元素。

Transactions配置

JanusGraph的JanusGraph.buildTransaction()方法使用户能够针对JanusGraph进行配置和启动新的多线程事务。因此,它与带有其他配置选项的JanusGraph.newTransaction()完全相同。

buildTransaction()返回一个TransactionBuilder,其允许配置事务的以下方面:

  • readOnly() - 使事务处于只读状态,任何修改图形的尝试都将导致异常。
  • enableBatchLoading() - 为单个事务启用批量加载。storage.batch-loading 由于禁用一致性检查和其他优化,此设置导致与图表范围设置类似的效率。但是其不与storage.batch-loading选项相同,它不会更改存储后端的行为。
  • setTimestamp(long) - 将此事务的时间戳设置为传递给存储后端以实现持久性。根据存储后端,可以忽略此设置。对于最终一致的后端,这是用于解决写冲突的时间戳。如果未明确指定此设置,JanusGraph将使用当前时间。
  • setVertexCacheSize(long size) - 此事务在内存中缓存的顶点数。此数字越大,事务可能消耗的内存就越多。如果此数字太小,则事务可能必须重新获取数据,这会导致特别是对于长时间运行的事务的延迟。
  • checkExternalVertexExistence(boolean) - 此事务是否应验证用户提供的顶点id的顶点是否存在。这种检查需要访问数据库,这需要时间。只有当用户绝对确定顶点存在时,才应禁用存在检查 - 否则可能会导致数据损坏。
  • checkInternalVertexExistence(boolean) - 此事务是否应在查询执行期间仔细检查顶点是否存在。这对于避免最终一致的存储后端上的幻像顶点非常有用。默认情况下禁用。启用此设置可能会降低查询处理速度。
  • consistencyChecks(boolean) - JanusGraph是否应该强制执行模式级别一致性约束(例如:多重性约束)。禁用一致性检查可以提高性能,但要求用户确保在应用程序级别进行一致性确认以避免不一致。应小心使用!!!

指定了所需的配置选项后,通过调用start()方法启动新的事务,该事务返回 一个 JanusGraphTransaction。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 图解Janusgraph系列-事务详解(janusgraph transactions)
    • 图数据库文章总目录:
      • 1  Transaction 处理
        • 2 Transactional 范围
          • 3 Transaction 失败
            • 4 多线程Transactions
              • 5 并发算法
                • 6 嵌套 Transactions
                  • 7 常见的事务处理问题
                    • 8 Transactions配置
                    相关产品与服务
                    图数据库 KonisGraph
                    图数据库 KonisGraph(TencentDB for KonisGraph)是一种云端图数据库服务,基于腾讯在海量图数据上的实践经验,提供一站式海量图数据存储、管理、实时查询、计算、可视化分析能力;KonisGraph 支持属性图模型和 TinkerPop Gremlin 查询语言,能够帮助用户快速完成对图数据的建模、查询和可视化分析。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档