Транзакции базы данных Firebase в реальном времени иногда выполняются слишком долго - PullRequest
0 голосов
/ 20 февраля 2019

У меня есть приложение nodejs, которое работает на одном сервере.Мне нужно запустить приложение на нескольких серверах для балансировки нагрузки.

Это приложение обрабатывает только один запрос от конкретного пользователя за раз.Если одновременные запросы поступают от пользователя, запросы ставятся в очередь и выполняются один за другим.Это сделано для поддержания согласованности некоторых данных в базе данных, которые могут быть повреждены, если приложение обрабатывает одновременные запросы.

Поскольку я собираюсь запустить приложение на нескольких серверах, мне нужен способ предотвратитьсерверы от одновременной обработки запросов от одного пользователя.Для этого я использовал базу данных Firebase в реальном времени для создания распределенной блокировки.Ниже приведена более простая версия моего кода.

function lockUser(user) {
    return firebaseAdmin.database().ref('users/' + user + '/lock').transaction((currentData) => {
        if (currentData === null || currentData.lockTime === 0) {
            return {'lockTime': Date.now()};
        }
    }, null, false).then(async (result) => {
        if (result.committed) {
            return Promise.resolve();
        }
        log.info('failed to lock ' + user + '. retrying.');
        await sleepFor(500);
        return lockUser(user, user, res);
    }).catch(async (reason) => {
        log.info('lock failed. ' + user + '. reason: ' + reason + '. retrying');
        await sleepFor(500);
        return lockUser(user, user, res);
    });
}

function unlockUser(user) {
    log.info('unlocking firebase lock. ' + user);
    firebaseAdmin.database().ref('users/' + user + '/lock').set({'lockTime': 0}, (error) => {
        if (error) {
            log.warn('failed to unlock ' + user + '. error: ' + util.inspect(error));
        } else {
            log.info('unlocked ' + user);
        }
    });
}

При использовании приведенного выше кода блокировка обычно занимает около 100 миллисекунд, и она в основном соответствует.Но редко, иногда я наблюдаю значительную задержку завершения транзакции.Когда это происходит, блокировка может занять около 30 секунд.

В чем может быть причина этой задержки?Есть ли причина, по которой я не должен использовать базу данных Firebase в реальном времени таким образом?

1 Ответ

0 голосов
/ 20 февраля 2019

RTDB-транзакции, по сути, являются операциями сравнения и установки.Если состояние базы данных изменяется во время выполнения транзакции, SDK повторяет транзакцию с новым состоянием базы данных.В вашем случае это может произойти, когда несколько процессов конкурируют за блокировку:

  1. P1 пытается получить блокировку, видит currentData.lockTime = 0 и решает обновить ее.
  2. P2 пытается получить блокировку, видит currentData.lockTime = 0 и решает обновить ее.
  3. P1 фиксирует свою транзакцию.
  4. P2 видит, что данные изменились с момента последнего чтения, поэтому повторяет транзакцию.

Теперь есть патологический случай, когданезадолго до того, как P2 повторяет транзакцию, P1 снимает свою блокировку.Таким образом, P2 снова увидит это currentData.lockTime = 0 и попытается снова захватить блокировку.Но эта попытка также может потерпеть неудачу, если другой процесс P3 захватывает блокировку из-под P2.И так повторение цикла продолжается.

В худшем случае транзакция может быть повторена до 25 раз .

Я не уверен, что это происходит в вашем случае.Но это определенно одно объяснение.Возможно, включите ведение журнала отладки для SDK и попытайтесь получить более полное представление о том, что происходит во время этих длительных задержек.Я также рекомендовал бы попытаться реализовать ваши обычные операции обновления данных с использованием транзакций и вообще устранить распределенную блокировку.

...