我在并发合并操作(REST API)期间遇到了死锁问题。我有一个处理带有一些元数据的文本的函数,对于元数据字典中的每个项目,我正在执行合并以添加节点或将文本节点与元数据[n]节点连接。当消息速率大约为每秒500-1000时出现问题。
在这个特定的函数中,6个查询之间有11个合并,如下所示:
q1 = "MERGE (n:N { id: {id} }) ON CREATE SET ... ON MATCH SET "
"WITH . MERGE (...) "
"WITH ., . MERGE (.)-[:some_rel]->(.)"
params = {'the': 'params'}
cypher.execute(q1, params)
if some_condition:
q2 = "MATCH (n:N { id: {id} }) ... "
"WITH n, . MERGE (n)-[:some_rel]->(.)"
params = {'more': 'params'}
cypher.execute(q2, params)
if some_condition2:
q3
...
if some_condition_n:
qn
我通过Celery运行上面的Python(对于那些不熟悉Celery的人来说,它是一个分布式任务队列)。当问题首次出现时,我在单个事务中执行上述操作,并且由于死锁异常而导致大量失败。我最初的想法只是在Redis的功能级别实现分布式阻塞锁。但是,这会导致我的应用程序出现瓶颈。
接下来,我从单个Cypher事务中切换了几个原子事务,如上所述并删除了锁。这样可以解决瓶颈问题,并大大减少死锁异常的数量,但它们仍在发生,尽管处于降低的水平。
图形数据库并不是我真正的东西,所以我对Neo4j和Cypher的输入和输出没有太多的经验。我在Redis中有一个现有节点的uuid的二级索引,因此在合并之前有一个预处理步骤,试图保持图形访问权限。我应该尝试一些明显的解决方案吗?也许有一些方法可以在图形方面对操作进行排队,或者我可能忽略了一些服务器优化?任何关于在哪里看的建议将不胜感激。谢谢!
答案 0 :(得分:1)
好的,在考虑了这个之后,我意识到执行查询的方式效率低下,可以通过一些重构来实现。由于所有查询都在同一个通用上下文中,因此没有理由单独执行它们,甚至没有理由打开事务并以这种方式执行它们。
相反,我更改了函数以完成条件,并将查询字符串连接成一个长字符串,并将我需要的参数添加到param字典中。所以,现在,最后只有一个执行,只有一个声明。这也取消了一些'匹配'语句。
但是,此修复程序并未完全解决此问题,因为仍有一些死锁异常被抛出。
答案 1 :(得分:1)
我认为我发现了这个问题,主要是因为开始时没有问题。 That is:
Neo4j的企业版有一个替代锁定管理器 社区版本,旨在提供可扩展的锁定 高CPU数的机器。
Enterprise Lock Manager使用死锁检测算法 不需要(很多)同步,这给了它一些非常好的 理想的可伸缩性属性。缺点是它可能 有时会发现误报。这通常不会发生 生产使用,但在压力测试个体中变得明显 操作。这些场景看到CPU缓存的流失率要低得多 失效,企业锁管理器需要进行通信 跨核心。
因为死锁检测错误是一个安全重试错误而用户是 期望在所有应用程序代码中处理这些,因为可能存在 任何时候合法的死锁,这种行为实际上是设计的 获得可扩展性。
我只是抓住了异常,然后在几秒后重试,现在: