База данных Firebase, как предотвратить одновременное чтение? - PullRequest
0 голосов
/ 01 июня 2018

Вариант использования:

Пользователь получит пароль для двери (например, door2, пароль 222), когда они находятся в определенном месте (например, LocationA),После этого облачная функция удалит дверь из документов empty и добавит ее в документ occupied.

Исходная база данных:

"LocationA" : {
  "empty" : {
    "door2" : {
      "password" : "222"
    },
    "door3" : {
      "password" : "333"
    }
  },
  "occupied" : {
    "door1" : {
      "password" : "111"
    }
  }
}

После того, как пользователь получит пароль для пустой двери:

"LocationA" : {
  "empty" : {
    "door3" : {
      "password" : "333"
    }
  },
  "occupied" : {
    "door1" : {
      "password" : "111"
    },
    "door2" : {
      "password" : "222"
    }
  }
}

Проблема:

Что, если есть 2 пользователя, одновременно получает door2пароль?Будет ли этот сценарий происходить?

Мне бы хотелось, чтобы пользователь 1 получил door2, а пользователь 2 - door3 соответственно.

Это код, который я использую для получениядверь:

// Read Lockers QR User(CRUD)
exports.getQRCode = functions.https.onRequest((req, res) => {
    admin.database().ref('lockers/' + 'LocationA/' + 'empty').limitToFirst(1).once("value",snap=> {
        console.log('QR Code for door:',snap.val());
        var qrCodesForDoor = snap.val();
        res.send(qrCodesForDoor); 
    });
});

Обновлена ​​база на ответ Гримторра

exports.getQRCode = functions.https.onRequest((req, res) => {


admin.database().ref('lockers/LocationA/empty').limitToFirst(1).once("value", snap=> {
  // Get the name of the first available door and use a transaction to ensure it is not occupied
  console.log('QR Code for door:',snap.val());
  var door = Object.keys(snap.val())[0];
  console.log('door:',door);

  // var door = snap.key();
  var occupiedRef = admin.database().ref('lockers/LocationA/occupied/'+door);
  occupiedRef.transaction(currentData=> {
      if (currentData === null) {
          console.log("Door does not already exist under /occupied, so we can use this one.");
          return snap.child(door).val(); // Save the chosen door to /occupied
      } else {
          console.log('The door already exists under /occupied.');
          return nil; // Abort the transaction by returning nothing
      }
  }, (error, committed, snapshot) => {
      console.log('snap.val():',snap.val());
      if (error) {
          console.log('Transaction failed abnormally!', error);
          res.send("Unknown error."); // This handles any abormal error
      } else if (!committed) {
          console.log('We aborted the transaction (because the door is already occupied).');
          res.redirect(req.originalUrl); // Refresh the page so that the request is retried
      } else {
          // The door is not occupied, so can be given to this user
          admin.database().ref('lockers/LocationA/empty/'+door).remove(); // Delete the door from /empty
          console.log('QR Code for door:',snapshot.val());
          var qrCodesForDoor = snapshot.val();
          res.send(qrCodesForDoor); // Send the chosen door as the response
      }
  });
  });
});

1 Ответ

0 голосов
/ 01 июня 2018

То, что вы описываете звуки, похожие на состояние гонки :

поведение программного обеспечения, [...] выход которого зависит от последовательности или временидругие неконтролируемые события.Это становится ошибкой, когда события происходят не в том порядке, в котором рассчитывал программист.

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

Firebase SDK предоставляет операции транзакций , которые можно использовать, чтобы избежать одновременных изменений.Для вашего сценария, используя Admin SDK в Node.js, вы можете выполнить что-то вроде следующего:

// Read Lockers QR User(CRUD)
exports.getQRCode = functions.https.onRequest((req, res) => {
    admin.database().ref('lockers/LocationA/empty').limitToFirst(1).once("value", (snap) => {
        if (!snap.hasChildren()) {
            res.send("No doors available.");
            return;
        }
        // Get the name of the first available door and use a transaction to ensure it is not occupied
        var door = Object.keys(snap.val())[0]; // The limitToFirst always returns a list (even with 1 result), so this will select the first result
        var occupiedRef = admin.database().ref('lockers/LocationA/occupied/'+door);
        occupiedRef.transaction((currentData) => {
            if (currentData === null) {
                console.log("Door does not already exist under /occupied, so we can use this one.");
                return snap.val(); // Save the chosen door to /occupied
            } else {
                console.log('The door already exists under /occupied.');
                return; // Abort the transaction by returning nothing
            }
        }, (error, committed, snapshot) => {
            if (error) {
                console.log('Transaction failed abnormally!', error);
                res.send("Unknown error."); // This handles any abormal error
            } else if (!committed) {
                console.log('We aborted the transaction (because the door is already occupied).');
                res.redirect(req.originalUrl); // Refresh the page so that the request is retried
            } else {
                // The door is not occupied, so can be given to this user
                admin.database().ref('lockers/LocationA/empty/'+door).remove(); // Delete the door from /empty
                console.log('QR Code for door:',snapshot.val());
                var qrCodesForDoor = snapshot.val();
                res.send(qrCodesForDoor); // Send the chosen door as the response
            }
        });
    });
});

Это использует ваш существующий код для получения следующей доступной двери, с той разницей, что он будет тольковыберите эту дверь, если она еще не существует в узле /occupied.Это достигается путем использования транзакции для проверки значения узла /occupied/door# перед его выбором и применяется следующая логика:

  • Если дверь не существует в /occupied мы можем безопасно выбрать эту дверь, сохранить ее в /occupied и удалить ее из /empty.
  • Если дверь существует в /occupied, тогда кто-тоиначе нас опередили, поэтому страница запроса обновляется, чтобы снова вызвать функцию, таким образом (надеюсь) в следующий раз выбрать другую дверь.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...