Diciamo che abbiamo un'app in cui gli utenti ottengono punti e possono scambiarli per premi. La richiesta di scambio , in pseudo-codice, potrebbe avere il seguente aspetto:
function exchangePointsForReward(userId, rewardId){
user = getUser(userId)
reward = getReward(rewardId)
if (user.points >= reward.requiredPoints){
giveRewardToUser(userId, rewardId)
reduceUserPoints(userId, reward.requiredPoints)
}
}
Ma se abbiamo un utente malintenzionato che cosa impedisce loro di creare una richiesta nel loro linguaggio di programmazione preferito e di inviarla 20 volte allo stesso tempo? Prima che la prima richiesta raggiunga reduceUserPoints()
, dieci altri hanno già ottenuto fino a addNewReward()
. Certo, i punti dell'utente alla fine della giornata potrebbero andare in profondità in negativo, ma cosa impedisce all'utente di afferrare rapidamente i premi e utilizzarli? Come posso garantire che sia possibile eseguire una sola operazione per un utente contemporaneamente?
Una soluzione che posso pensare è che l'operazione tenta di acquisire un blocco all'inizio dell'operazione e solo una singola operazione bloccabile può essere eseguita per un utente in qualsiasi momento:
function aquireLock(userId){
lockId = getRandomLockId()
database.query("UPDATE user SET lock={lockId} WHERE user={userId} AND lock IS NULL");
return database.query("SELECT lock WHERE user = {userId}").first === lockId;
}
function exchangePointsForReward(userId, rewardId){
if (!aquireLock(userId)){
throw new Error("Failed to acquire lock");
}
user = getUser(userId)
reward = getReward(rewardId)
if (user.points >= reward.requiredPoints){
giveRewardToUser(userId, rewardId)
reduceUserPoints(userId, reward.requiredPoints)
}
releaseLock(userId);
}
Ma c'è qualche strategia migliore qui? La domanda è indipendente dal database.