MarkLogic Java API 死锁检测
MarkLogic Java API deadlock detection
我们的一个应用程序刚刚遇到了一些严重的死锁。我很难重现问题,因为死锁(或堆栈跟踪)没有立即出现在我的 java 应用程序日志中。
令我惊讶的是,marklogic java api 会重试失败的请求(例如,由于死锁)。如果您的请求不是 多语句 请求,这可能是有道理的,但我不确定它是否是。
所以让我们继续解决这个死锁问题。我创建了一个简单的代码片段,其中我故意创建了一个死锁。该代码段创建了一个文档 test.xml
,然后尝试从两个不同的事务中读取和写入,每个事务都在一个新线程上。
public static void main(String[] args) throws Exception {
final Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
final Logger ok = (Logger) LoggerFactory.getLogger(OkHttpServices.class);
root.setLevel(Level.ALL);
ok.setLevel(Level.ALL);
final DatabaseClient client = DatabaseClientFactory.newClient("localhost", 8000, new DatabaseClientFactory.DigestAuthContext("username", "password"));
final StringHandle handle = new StringHandle("<doc><name>Test</name></doc>")
.withFormat(Format.XML);
client.newTextDocumentManager().write("test.xml", handle);
root.info("t1: opening");
final Transaction t1 = client.openTransaction();
root.info("t1: reading");
client.newXMLDocumentManager()
.read("test.xml", new StringHandle(), t1);
root.info("t2: opening");
final Transaction t2 = client.openTransaction();
root.info("t2: reading");
client.newXMLDocumentManager()
.read("test.xml", new StringHandle(), t2);
new Thread(() -> {
root.info("t1: writing");
client.newXMLDocumentManager().write("test.xml", new StringHandle("<doc><t>t1</t></doc>").withFormat(Format.XML), t1);
t1.commit();
}).start();
new Thread(() -> {
root.info("t2: writing");
client.newXMLDocumentManager().write("test.xml", new StringHandle("<doc><t>t2</t></doc>").withFormat(Format.XML), t2);
t2.commit();
}).start();
TimeUnit.MINUTES.sleep(5);
client.release();
}
此代码将产生以下日志:
14:12:27.437 [main] DEBUG c.m.client.impl.OkHttpServices - Connecting to localhost at 8000 as admin
14:12:27.570 [main] DEBUG c.m.client.impl.OkHttpServices - Sending test.xml document in transaction null
14:12:27.608 [main] INFO ROOT - t1: opening
14:12:27.609 [main] DEBUG c.m.client.impl.OkHttpServices - Opening transaction
14:12:27.962 [main] INFO ROOT - t1: reading
14:12:27.963 [main] DEBUG c.m.client.impl.OkHttpServices - Getting test.xml in transaction 5298588351036278526
14:12:28.283 [main] INFO ROOT - t2: opening
14:12:28.283 [main] DEBUG c.m.client.impl.OkHttpServices - Opening transaction
14:12:28.286 [main] INFO ROOT - t2: reading
14:12:28.286 [main] DEBUG c.m.client.impl.OkHttpServices - Getting test.xml in transaction 8819382734425123844
14:12:28.289 [Thread-1] INFO ROOT - t1: writing
14:12:28.289 [Thread-1] DEBUG c.m.client.impl.OkHttpServices - Sending test.xml document in transaction 5298588351036278526
14:12:28.289 [Thread-2] INFO ROOT - t2: writing
14:12:28.290 [Thread-2] DEBUG c.m.client.impl.OkHttpServices - Sending test.xml document in transaction 8819382734425123844
t1
或 t2
都不会提交。 MarkLogic 日志确认确实存在死锁:
==> /var/opt/MarkLogic/Logs/8000_AccessLog.txt <==
127.0.0.1 - admin [24/Nov/2018:14:12:30 +0000] "PUT /v1/documents?txid=5298588351036278526&category=content&uri=test.xml HTTP/1.1" 503 1034 - "okhttp/3.9.0"
==> /var/opt/MarkLogic/Logs/ErrorLog.txt <==
2018-11-24 14:12:30.719 Info: Deadlock detected locking Documents test.xml
如果其中一个请求失败并抛出异常,这不是问题,但事实并非如此。 MarkLogic Java Api 重试每个请求直到 120 seconds 并且其中一个更新在大约 120 秒后超时:
Exception in thread "Thread-1" com.marklogic.client.FailedRequestException: Service unavailable and maximum retry period elapsed: 121 seconds after 65 retries
at com.marklogic.client.impl.OkHttpServices.putPostDocumentImpl(OkHttpServices.java:1422)
at com.marklogic.client.impl.OkHttpServices.putDocument(OkHttpServices.java:1256)
at com.marklogic.client.impl.DocumentManagerImpl.write(DocumentManagerImpl.java:920)
at com.marklogic.client.impl.DocumentManagerImpl.write(DocumentManagerImpl.java:758)
at com.marklogic.client.impl.DocumentManagerImpl.write(DocumentManagerImpl.java:717)
at Scratch.lambda$main[=15=](scratch.java:40)
at java.lang.Thread.run(Thread.java:748)
有哪些可能的方法可以解决这个问题?一种方法可能是设置事务的最大生存时间(例如 5 秒),但这感觉很老套且不可靠。还有其他想法吗?还有其他我应该检查的设置吗?
我正在使用 MarkLogic 9.0-7.2
并使用 marklogic-client-api:4.0.3
。
编辑:解决死锁的一种方法是同步调用函数,这实际上是我在我的案例中解决它的方法(见评论)。但我认为潜在的问题仍然存在。多语句事务中的死锁不应该隐藏在 120 秒的超时中。我宁愿有一个立即失败的请求,也不愿在我的一个文档上锁定 120 秒 + 每个线程 64 次失败重试 .
死锁通常可以通过重试来解决。在内部,服务器执行内部重试循环,因为 通常 死锁是暂时的和偶然的,持续时间很短。在您的情况下,您构建了一个永远不会成功的案例,任何超时对于两个线程都是相等的。
在使用 REST API 时,通过避免多语句事务可以避免应用层出现死锁。 (这是 Java api 使用的)。
由于客户端负责管理事务 ID 以及服务器无法检测客户端错误或客户端身份,因此无法 100% 安全地实现基于 REST 的多语句事务。非常微妙的问题可能而且确实会发生,除非您积极主动地处理错误和多线程。如果您 'push' 服务器的逻辑(xquery 或 javascript)服务器能够更好地管理事情。
至于 'good' 或 Java API 是否对这种情况实施重试,无论哪种方式都值得商榷。 (一个看似易于使用的界面的妥协是,许多本来可以选择的东西作为约定由你决定。通常没有一刀切的答案。在这种情况下,我假设这个想法是死锁更有可能是由 'accident' 的独立 code/logic 引起的,而不是相同的代码 运行 in tangent —— 在这种情况下重试是一个不错的选择。在你的例子中它不是,但是在您将代码更改为 'not do that' 之前,较早的错误仍然可以预见地失败。
如果不存在,对可配置超时和重试行为的功能请求似乎是一个合理的请求。但是,我建议尝试避免任何导致打开事务的 REST 调用——这本质上是有问题的,特别是如果您没有提前注意到问题(那么它更有可能在生产中引起您的注意)。与保持连接打开以便服务器可以检测客户端断开连接的 JDBC 不同,HTTP 和 ML Rest API 不会——这导致了与 [=27 中的传统数据库编码不同的编程模型=].
我们的一个应用程序刚刚遇到了一些严重的死锁。我很难重现问题,因为死锁(或堆栈跟踪)没有立即出现在我的 java 应用程序日志中。
令我惊讶的是,marklogic java api 会重试失败的请求(例如,由于死锁)。如果您的请求不是 多语句 请求,这可能是有道理的,但我不确定它是否是。
所以让我们继续解决这个死锁问题。我创建了一个简单的代码片段,其中我故意创建了一个死锁。该代码段创建了一个文档 test.xml
,然后尝试从两个不同的事务中读取和写入,每个事务都在一个新线程上。
public static void main(String[] args) throws Exception {
final Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
final Logger ok = (Logger) LoggerFactory.getLogger(OkHttpServices.class);
root.setLevel(Level.ALL);
ok.setLevel(Level.ALL);
final DatabaseClient client = DatabaseClientFactory.newClient("localhost", 8000, new DatabaseClientFactory.DigestAuthContext("username", "password"));
final StringHandle handle = new StringHandle("<doc><name>Test</name></doc>")
.withFormat(Format.XML);
client.newTextDocumentManager().write("test.xml", handle);
root.info("t1: opening");
final Transaction t1 = client.openTransaction();
root.info("t1: reading");
client.newXMLDocumentManager()
.read("test.xml", new StringHandle(), t1);
root.info("t2: opening");
final Transaction t2 = client.openTransaction();
root.info("t2: reading");
client.newXMLDocumentManager()
.read("test.xml", new StringHandle(), t2);
new Thread(() -> {
root.info("t1: writing");
client.newXMLDocumentManager().write("test.xml", new StringHandle("<doc><t>t1</t></doc>").withFormat(Format.XML), t1);
t1.commit();
}).start();
new Thread(() -> {
root.info("t2: writing");
client.newXMLDocumentManager().write("test.xml", new StringHandle("<doc><t>t2</t></doc>").withFormat(Format.XML), t2);
t2.commit();
}).start();
TimeUnit.MINUTES.sleep(5);
client.release();
}
此代码将产生以下日志:
14:12:27.437 [main] DEBUG c.m.client.impl.OkHttpServices - Connecting to localhost at 8000 as admin
14:12:27.570 [main] DEBUG c.m.client.impl.OkHttpServices - Sending test.xml document in transaction null
14:12:27.608 [main] INFO ROOT - t1: opening
14:12:27.609 [main] DEBUG c.m.client.impl.OkHttpServices - Opening transaction
14:12:27.962 [main] INFO ROOT - t1: reading
14:12:27.963 [main] DEBUG c.m.client.impl.OkHttpServices - Getting test.xml in transaction 5298588351036278526
14:12:28.283 [main] INFO ROOT - t2: opening
14:12:28.283 [main] DEBUG c.m.client.impl.OkHttpServices - Opening transaction
14:12:28.286 [main] INFO ROOT - t2: reading
14:12:28.286 [main] DEBUG c.m.client.impl.OkHttpServices - Getting test.xml in transaction 8819382734425123844
14:12:28.289 [Thread-1] INFO ROOT - t1: writing
14:12:28.289 [Thread-1] DEBUG c.m.client.impl.OkHttpServices - Sending test.xml document in transaction 5298588351036278526
14:12:28.289 [Thread-2] INFO ROOT - t2: writing
14:12:28.290 [Thread-2] DEBUG c.m.client.impl.OkHttpServices - Sending test.xml document in transaction 8819382734425123844
t1
或 t2
都不会提交。 MarkLogic 日志确认确实存在死锁:
==> /var/opt/MarkLogic/Logs/8000_AccessLog.txt <==
127.0.0.1 - admin [24/Nov/2018:14:12:30 +0000] "PUT /v1/documents?txid=5298588351036278526&category=content&uri=test.xml HTTP/1.1" 503 1034 - "okhttp/3.9.0"
==> /var/opt/MarkLogic/Logs/ErrorLog.txt <==
2018-11-24 14:12:30.719 Info: Deadlock detected locking Documents test.xml
如果其中一个请求失败并抛出异常,这不是问题,但事实并非如此。 MarkLogic Java Api 重试每个请求直到 120 seconds 并且其中一个更新在大约 120 秒后超时:
Exception in thread "Thread-1" com.marklogic.client.FailedRequestException: Service unavailable and maximum retry period elapsed: 121 seconds after 65 retries
at com.marklogic.client.impl.OkHttpServices.putPostDocumentImpl(OkHttpServices.java:1422)
at com.marklogic.client.impl.OkHttpServices.putDocument(OkHttpServices.java:1256)
at com.marklogic.client.impl.DocumentManagerImpl.write(DocumentManagerImpl.java:920)
at com.marklogic.client.impl.DocumentManagerImpl.write(DocumentManagerImpl.java:758)
at com.marklogic.client.impl.DocumentManagerImpl.write(DocumentManagerImpl.java:717)
at Scratch.lambda$main[=15=](scratch.java:40)
at java.lang.Thread.run(Thread.java:748)
有哪些可能的方法可以解决这个问题?一种方法可能是设置事务的最大生存时间(例如 5 秒),但这感觉很老套且不可靠。还有其他想法吗?还有其他我应该检查的设置吗?
我正在使用 MarkLogic 9.0-7.2
并使用 marklogic-client-api:4.0.3
。
编辑:解决死锁的一种方法是同步调用函数,这实际上是我在我的案例中解决它的方法(见评论)。但我认为潜在的问题仍然存在。多语句事务中的死锁不应该隐藏在 120 秒的超时中。我宁愿有一个立即失败的请求,也不愿在我的一个文档上锁定 120 秒 + 每个线程 64 次失败重试 .
死锁通常可以通过重试来解决。在内部,服务器执行内部重试循环,因为 通常 死锁是暂时的和偶然的,持续时间很短。在您的情况下,您构建了一个永远不会成功的案例,任何超时对于两个线程都是相等的。 在使用 REST API 时,通过避免多语句事务可以避免应用层出现死锁。 (这是 Java api 使用的)。 由于客户端负责管理事务 ID 以及服务器无法检测客户端错误或客户端身份,因此无法 100% 安全地实现基于 REST 的多语句事务。非常微妙的问题可能而且确实会发生,除非您积极主动地处理错误和多线程。如果您 'push' 服务器的逻辑(xquery 或 javascript)服务器能够更好地管理事情。
至于 'good' 或 Java API 是否对这种情况实施重试,无论哪种方式都值得商榷。 (一个看似易于使用的界面的妥协是,许多本来可以选择的东西作为约定由你决定。通常没有一刀切的答案。在这种情况下,我假设这个想法是死锁更有可能是由 'accident' 的独立 code/logic 引起的,而不是相同的代码 运行 in tangent —— 在这种情况下重试是一个不错的选择。在你的例子中它不是,但是在您将代码更改为 'not do that' 之前,较早的错误仍然可以预见地失败。
如果不存在,对可配置超时和重试行为的功能请求似乎是一个合理的请求。但是,我建议尝试避免任何导致打开事务的 REST 调用——这本质上是有问题的,特别是如果您没有提前注意到问题(那么它更有可能在生产中引起您的注意)。与保持连接打开以便服务器可以检测客户端断开连接的 JDBC 不同,HTTP 和 ML Rest API 不会——这导致了与 [=27 中的传统数据库编码不同的编程模型=].