我有一段删除顶点并提交事务的代码。由于某些原因,下一个操作仍然可以看到顶点。同样奇怪的是,它只看到它有时可能基于时间等。例如,图形服务——包含-->路由
操作1:删除包含边和删除顶点并提交
操作2:get包含来自服务节点的边缘,它仍然获取在操作1中删除的路由节点
这两个操作是一个接一个的,并且不是并行运行的,所以在第一次提交之前读取它是没有问题的。
另外,如果第一次提交成功完成,那么我的理解是所有其他线程应该立即看到更新。
在cassandra数据库中使用janusgraph api for java
伪代码示例:
synchronized methodA:
do some operations
figure out route X need to be deleted from graph
get all routes using contains edge from service node
// service---contains--> route
get route X from all routes
singlethreadExecutor.submitTask(DeleteRoute X)
update some other DB with service without route X
Task DeleteRoute (route x)
get route X from graph DB
delete route X vertex
commit
Operation1 calls into methodA:
service with 4 routes R1,R2, R3, R4
Expected to delete R3
Works as expected
R3 is deleted from graph as well as other DB
Operation2 calls into methodA:
service expected routes in graph with R1, R2, R4
however, method A still gets all 4 routes including R3 which is deleted in operation 1
请注意方法a是同步的,因此操作1和2不会相互冲突。操作1完成,然后操作2开始
这让我感到困惑,尤其是当我的日志指示操作1的提交已完成,而操作2仍然使用janusgraph api从graph获取路由节点r3时。
我们没有使用线程事务我们没有使用新事务我们依赖tinkerpop在线程的第一个操作中打开新事务。
日志片段:
操作1:
2019-06-17 14:58:25213 |删除节点:route:1560307936368:1683669533 2019-06-17 14:58:25216 |提交2019-06-17 14:58:25350 |提交所用时间=133
操作2:
2019-06-17 14:58:25738 |更新节点2019-06-17 14:58:25739 |更新节点:route:1560307936368:1683669533 2019-06-17 14:58:25740 | updatevertex:已为键更新顶点:route:1560307936368:1683669533 2019-06-17 14:58:25741 | updatenode中的updatenode时间=3
如您所见,操作1删除路由节点并提交,而操作2从图中读取时,仍然获得相同的路由节点并能够更新它。我们的updateapi在更新顶点之前检查顶点是否存在,如果顶点不存在则抛出错误。
所以很明显,即使删除成功并且提交在它之前完成,使用janusgraph getvertex api(基于节点id键)仍然可以从图中返回顶点。
如果两个操作之间的时间差被操纵超过几分钟,则相同的代码将按预期工作。
我们还配置了使用janushgraph缓存。
有了这些付出,我真的很困惑这是怎么发生的。
我可以理解这两个操作是不是以某种方式并行运行,并且相互重叠,竞争条件会给我陈旧的数据,但是操作是同步的,并且一个接一个地发生。
在第一次操作中删除并提交顶点后,预计不会在第二次操作中返回顶点,特别是当两个操作都是同步的并且一个接一个地发生而没有任何故障/异常时。
用例1:
thread-1----调用---->synchronized method-1--->获取边/顶点,更新顶点,提交-----提交---->singlethreadedexecutortask--->删除边/顶点,提交---->调用---->synchronized method-1(对于操作2)--->这里get边/顶点仍然获取旧边/顶点
我可以理解用例2,其中事务作用域是第一个操作的线程,其他线程中提交的任何内容在此事务作用域中都不可见,因此我必须在启动操作2之前提交事务才能看到更改。
我在用例2中尝试了这个方法,它可以像预期的那样工作!!
用例2:
线程-1----调用--->同步方法-1--->获取边/顶点,更新顶点,提交-----提交--->singlethreadedexecutortask--->删除边/顶点,提交---->thread-1完成。
大约一分钟后:
thread-2----调用--->同步方法-1--->获取边/顶点、更新顶点、提交-----提交--->singlethreadedexecutortask--->删除边/顶点、提交---->thread-2完成。
问题thread-2对synchronized method-1的调用仍然获取旧的边/顶点,该边/顶点作为thread-1进程的一部分被删除。
在这种情况下。
线程-1范围的事务是用第一个图形操作打开的,该事务在更新后立即关闭。在singlethreadedexecutor任务在单独的线程中运行之后,它将为第一个操作打开自己的新事务,并在任务完成时使用commit关闭事务。
当线程2在一分钟后启动时,它将使用第一个图形操作打开自己的线程范围事务-此新线程事务范围中的get操作应该能够在不删除线程1的边/顶点的情况下获取正确的数据,特别是考虑到ti几乎在1分钟后启动。这甚至不是群集设置。即使使用集群设置,我认为在提交调用返回之前,必须满足仲裁要求,其余的复制可以独立进行(延迟)
这是我无法理解的部分,当然,如果我添加手动干预与2线程一样,开始线程1可能在2分钟后,它的工作出于某种原因。
在这种情况下,2分钟对于最终的一致性来说似乎真的很长。
那么应用程序处理这个问题的选项是什么呢?
有没有办法强迫图形操作等待最终的一致性?像thread-2一样,我可以指定第一个get操作必须等待,除非它通过解决所有冲突等返回一致的数据。
我不认为在线程2中打开新事务或者尝试做某种全局提交来关闭以前打开的过时事务(如果有的话)是正确的方法,因为这只是新线程的开始。
1条答案
按热度按时间euoag5mw1#
Cassandra的突变不会立即出现
cassandra是一个最终一致的数据库,这意味着写入它的更改不能保证对所有用户都立即可见。它尽了最大的努力让它发生,但这并不总是最终发生。关键是,你不应该期望看到任何突变写在Cassandra写后立即。
当对cassandra的写入完成后,它仍然需要将更改传播到集群的其余部分。完全有可能在突变后立即发生的读取正在获取一些过时的数据。
janusgraph的锁定和同步与cassandra的一致性无关
janusgraph正在确保它一次只对cassandra发出一个调用,但这并没有回避这样一个事实:在janus对cassandra的调用完成之后,cassandra仍有一段时间在传播突变;如果janus下一次给cassandra打电话是在变异完成之前,那么数据就过时了。
一般建议是进行应用程序端检查
使用最终一致的存储后端将导致这些问题;在janusgraph文档的最终一致性后端中,推荐的初始路径是在阅读时解决应用程序中的这种不一致性。构建你的应用程序时,如果你可以的话,它不会假设一个突变调用返回意味着突变是可见的。
在您的示例中,我会在您的两个事务之间插入一些东西,要么等待适当的时间(我想说,即使只有几秒钟的时间也应该足够),要么检查删除是否完成。
但是,如果您需要强大的数据一致性,cassandra并不是一个好的解决方案
我要指出的是,虽然上一段是检查这一点的一种快速而简单的方法,但如果您发现确实需要确认每个upsert和delete操作,那么最好使用不同的存储后端,如hbase或berkeleydb。下面是根据janusgraph手册的存储后端选项列表。
但如果你对缺乏强一致性的问题总体上还可以接受的话,那么Cassandra的优势在于,它倾向于相当容易地横向扩展。最后,这一切都取决于你的应用程序的需求。