MarkLogic Java API 死锁检测

Posted

技术标签:

【中文标题】MarkLogic Java API 死锁检测【英文标题】:MarkLogic Java API deadlock detection 【发布时间】:2019-04-26 18:26:21 【问题描述】:

我们的一个应用程序刚刚遇到了一些令人讨厌的死锁。我很难重新创建问题,因为死锁(或堆栈跟踪)没有立即出现在我的 java 应用程序日志中。

令我惊讶的是,marklogic java api 会重试失败的请求(例如因为死锁)。如果您的请求不是 multi statement 请求,这可能是有道理的,否则我不确定是否是。

所以让我们继续解决这个死锁问题。我创建了一个简单的代码 sn-p,我在其中故意创建了一个死锁。 sn-p 创建一个文档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

编辑:解决死锁的一种方法是同步调用函数,这实际上是我在我的情况下解决它的方法(参见 cmets)。但我认为根本问题仍然存在。多语句事务中的死锁不应在 120 秒超时后隐藏。我宁愿有一个立即失败的请求,而不是 120 秒锁定我的一个文档 + 每个线程 64 次失败重试

【问题讨论】:

死锁是否是您必须从概念上克服的问题,工具只能帮助您到此为止。在临界区周围创建一个锁通常是一种简单的方法。 拥有一个锁(在我的 java 应用程序中)是我实际解决它的方式,但我仍然认为默认情况下让一个死锁事务重试 120 秒的请求有点粗鲁。无法解决的死锁不应该引发错误吗?有人可能会将此视为对 marklogic-client-api 的错误/功能请求。 你可以参考***.com/questions/1102359/….. @secretsuperstar 我的问题不是关于 java 死锁,而是关于 MarkLogic 中的死锁。不过还是感谢您的评论! 【参考方案1】:

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

至于 Java API 在这种情况下实现重试是否“好”,无论哪种方式都值得商榷。 (对于一个看似易于使用的界面的折衷方案是,许多本来可以选择的事情都是为您决定的,作为一种惯例。通常没有一刀切的答案。在这种情况下,我假设想法是死锁更可能是由“意外”的独立代码/逻辑引起的,而不是在切线中运行的相同代码 - 在这种情况下重试将是一个不错的选择。在您的示例中不是,但早期的错误仍然会除非您将代码更改为“不要那样做”,否则会失败)。

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

【讨论】:

感谢您的详细回答。添加更多关于我们为什么在我们的应用程序中使用大量多语句事务的上下文:这个应用程序是一个 java Spring Framework 应用程序,因此使用了典型的@Transactional 模型(所以客户端事务模型)。我们从 sql 切换到 marklogic,但显然仍有数千行客户端代码需要这种类型的模型。所以我们到处都有多语句交易。 Sql 回滚事务,如果出现死锁,现在不再是这种情况了。这使得从 sql 到 marklogic 的转换和迁移变得相当困难 :(。除此之外,我真的很喜欢你的回答。它解释了为什么当前行为是这样的背后的原因,并且它确实有意义。你是对的,这个死锁代码应移植到 marklogic 中以实现最佳事务处理。 你不认为marklogic有办法告诉客户端它的多语句事务是死锁的一部分吗?这样,客户端可以为此事务抛出错误并相应地回滚(而不是重试)。它已经告诉客户端它无法执行该请求,为什么不添加“因为死锁”。 Java API 中服务不可用的重试还可以处理林故障转移等高可用性场景。您可以在 GitHub 的 Java API 上提交 RFE,以支持配置死锁情况的重试。 链接到 rfe:github.com/marklogic/java-client-api/issues/1038

以上是关于MarkLogic Java API 死锁检测的主要内容,如果未能解决你的问题,请参考以下文章

java中在linux下利用jstack检测死锁

java 死锁

Oracle死锁检测工具

死锁的预防检测与修复

死锁:操作系统的死锁检测算法,死锁避免算法,死锁预防算法,死锁检测

(王道408考研操作系统)第二章进程管理-第四节3:死锁处理策略之检测和解除