MarkLogic Java API обнаружение взаимоблокировок - PullRequest
0 голосов
/ 24 ноября 2018

В одном из наших приложений только что возникли неприятные тупики.Мне было довольно трудно воссоздать проблему, потому что тупик (или трассировка стека) не обнаруживался сразу в журналах моего Java-приложения.

К моему удивлению, marklogic java api повторяет неудачные запросы (например, из-за тупика).Это может иметь смысл, если ваш запрос не является запросом multi оператор , но в противном случае я не уверен, что это так.

Так что давайте остановимся на этой проблеме взаимоблокировки.Я создал простой фрагмент кода, в котором я специально создал тупик.Фрагмент создает документ 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 секунд и одного из таймаутов обновлений примерно через 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 голосов
/ 25 ноября 2018

Взаимные блокировки обычно разрешаются повторной попыткой.Внутренне сервер выполняет цикл внутренней повторной попытки, потому что обычно взаимоблокировки являются временными и случайными, и длятся очень короткое время.В вашем случае вы создали случай, который никогда не будет успешным с любым временем ожидания, равным для обоих потоков.На прикладном уровне можно избежать взаимных блокировок, избегая транзакций с несколькими операторами при использовании REST API.(это то, что использует Java API).Многопоточные транзакции по REST не могут быть реализованы на 100% безопасно из-за ответственности клиента за управление идентификатором транзакции и неспособности сервера обнаруживать ошибки на стороне клиента или идентификацию на стороне клиента.Очень тонкие проблемы могут возникать и случаются, если вы не проявляете агрессивную активность при обработке ошибок и многопоточности.Если вы «протолкнете» логику на сервер (xquery или javascript), сервер сможет намного лучше управлять вещами.

Что касается того, является ли это «хорошим» или нет для API Java для выполнения повторов в этом случае, это спорно так или иначе.(Компромисс для, казалось бы, простого в использовании интерфейса заключается в том, что многие вещи, которые в противном случае были бы опциями, решаются для вас как соглашение. Как правило, нет универсального ответа. В этом случае я предполагаю, что мысль былачто взаимоблокировка, скорее всего, вызвана независимым кодом / логикой из-за 'аварии', а не идентичного кода, работающего в касательной - повторная попытка в этом случае будет хорошим выбором. В вашем примере это не так, но тогда более ранняя ошибка все равно будетпредсказуемо завершать работу до тех пор, пока вы не измените свой код на «не делать этого»).

Если он еще не существует, запрос функции для настраиваемого времени ожидания и повторных попыток кажется разумным запросом.Однако я бы порекомендовал попытаться избежать любых вызовов REST, которые приводят к открытой транзакции - по сути, это проблематично, особенно если вы не замечаете проблему заранее (тогда она с большей вероятностью укушает вас в работе).В отличие от JDBC, который поддерживает соединение открытым, чтобы сервер мог обнаруживать разъединения клиента, HTTP и ML Rest API этого не делают - что приводит к другой модели программирования, чем традиционное кодирование базы данных в Java.

...