MarkLogic Java API死锁检测

时间:2018-11-24 13:23:00

标签: java marklogic marklogic-9

我们的一个应用程序刚刚遭受了一些讨厌的死锁。我很难解决问题,因为死锁(或堆栈跟踪)没有立即显示在我的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

t1t2都不会被提交。 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$0(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次失败的重试

1 个答案:

答案 0 :(得分:2)

死锁通常可以通过重试来解决。在内部,服务器执行内部重试循环,因为通常死锁是瞬时的和偶然的,持续时间很短。在您的案例中,您构造了一个案例,该案例在两个线程都相等的情况下永远不会成功。 通过使用REST API时避免多语句事务,可以避免在应用程序层出现死锁。 (这是Java api所使用的)。 由于客户端管理事务ID的责任以及服务器无法检测到客户端错误或客户端身份,因此无法100%安全地实现基于REST的多语句事务。除非您积极主动地处理错误和多线程,否则可能会发生非常细微的问题。如果您将逻辑“推送”到服务器(xquery或javascript),则服务器可以更好地管理事务。

对于在这种情况下Java API实现重试是否“好”,这是有争议的。 (对于一个看似易于使用的界面,其折衷之处在于,按照惯例,许多可能会作为选项的事情都是由您决定的。通常没有一个千篇一律的答案。在这种情况下,我认为这种想法是死锁更有可能是由“意外”导致的独立代码/逻辑引起的,而不是相切地运行相同的代码-在这种情况下,重试将是一个不错的选择,在您的示例中,它不是,但更早的错误仍然可以预期地失败,直到将代码更改为“不这样做”为止。

如果尚不存在,则对可配置超时和重试行为的功能请求确实是合理的请求。但是,我建议您尝试避免导致开放交易的任何REST调用-本质上是有问题的,特别是如果您没有事先注意到问题(那么问题很可能会在生产中咬您)。与JDBC保持打开连接以便服务器可以检测到客户端断开连接的JDBC不同,HTTP和ML Rest API不能这样做-导致与Java中的传统数据库编码不同的编程模型。