我有一个在单个服务器上运行的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实时数据库吗?
答案 0 :(得分:1)
RTDB事务本质上是比较设置操作。如果在事务进行过程中数据库的状态发生变化,则SDK会使用数据库的新状态重试事务。在您的情况下,当多个进程争用该锁时,可能会发生这种情况:
currentData.lockTime = 0
,并决定对其进行更新。 currentData.lockTime = 0
,并决定对其进行更新。 现在有一种病理情况,在P2重试交易之前,P1放弃了其锁定。因此,P2将再次看到currentData.lockTime = 0
,并尝试再次获取该锁。但是,如果另一个进程P3从P2下获取了锁,则此尝试也可能失败。因此,重试循环继续。
在最坏的情况下,交易可以重试到25 times。
我不确定这是否是您的情况。但这绝对是一种解释。也许启用SDK的调试日志记录,并尝试更深入地了解这些长时间的延迟情况。我还建议您尝试使用事务来实现您的常规数据更新操作,并完全消除分布式锁定。