Firebase并发修改和事务解决方案

时间:2018-02-23 01:57:09

标签: javascript firebase firebase-realtime-database google-cloud-functions

我有一个Android应用程序(游戏),匹配2个人,让他们玩。我正在使用firebase实时数据库,并在Node.js上为此编写了必要的代码。在我的数据库中,当有人点击“在线按钮”时,它触发firebase功能并使玩家状态等待(状态:等待)并将他/她与另一个状态也在“等待”的玩家匹配,如果操作是成功的node.js更新“ rakip“节点与对手的用户名为两个玩家的进一步行动。

然而,我担心的是当2个用户同时搜索对手时(比如说用户A和B)可能会有不好的情况,例如用户A与B匹配而用户B与C匹配。我怎样才能防止这会发生吗?如果他/她的状态发生变化,我怎么能停止B的搜索(node.js动作)?

提前致谢。

我的实时firebase数据库是这样的:

My-project-name
  users
    sinan
      atilansayi: ""
      gal: false
      lose: 0
      rakip: "hakan"
      rakipsallama: ""
      sayilar: "4567"
      sonuc: ""
      status: "on"
      turn: false
      win: 1
    hakan
      atilansayi: 5678
      gal: false
      lose: 1
      rakip: "sinan"
      rakipsallama: "7559|13238436"
      sayilar: 7559
      sonuc: "-"
      status: "on"
      turn: true
      win: 0
    mehmet
      atilansayi: 9000
      gal: "no"
      lose: 0
      rakip: "hakan"
      rakipsallama: 3333
      sayilar: 7000
      sonuc: "+"
      status: "on"
      turn: false
      win: 0

我的Node.js文件是:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.sayi = functions.database.ref("/users/{uid}/status").onWrite(event => {
    var status = event.data.val();
    var user = event.data.ref.parent.key;
    if (status ==="waiting") {
        const events = admin.database().ref('users');
        const query =events.orderByChild('status').equalTo('waiting').limitToFirst(2);
        query.on("value",
         function(data) {
            var waitinggameusers= data.val();
            var keys = Object.keys(waitinggameusers);
            for (var i = 0; i < keys.length; i++) {            
                var key = keys[i];
                if(key!==user){

//////////////这是问题可能发生的部分/////////////////

                    admin.database().ref("/users/"+user+"/rakip").set(key);
                    admin.database().ref("/users/"+key+"/rakip").set(user);
                    admin.database().ref("/users/"+user+"/status").set("on");
                    admin.database().ref("/users/"+key+"/status").set("on");
                    return console.log("bbbb"+" "+key);

//////////////这是问题可能发生的部分/////////////////

                }               
            }
        }, sorunlu);
    }
});
exports.sayiat = functions.database.ref("/users/{uid}/atilansayi").onWrite(event => {
    var sallanansayi ="";
    var rakibinsayisi ="";
    var res = event.data.val();
    if(res===""){
        return true;
    }
    var res2 = res.split("|");
    sallanansayi += res2[0];
    sallanansayi2=sallanansayi;
    var user = event.data.ref.parent.key;
    var sonuc ="";
    var artilar ="";   
    return admin.database().ref('users/'+user+'/rakip').once('value').then(function(snapshot){
       var rakip = snapshot.val();
       console.log("Rakip: "+rakip);
       return admin.database().ref('users/'+rakip+'/sayilar').once('value').then(function(sayisinibul){          
        rakibinsayisi += sayisinibul.val();
            for (i = 0; i < sallanansayi2.length; i++) {
                if(sallanansayi2.charAt(i)===rakibinsayisi.charAt(i)){
                    sonuc +="+";
                    artilar += i;
                }
            }
            console.log("Sonuç(sadece artılar): "+sonuc);
            console.log("Artıların yerleri: "+artilar);
            console.log("Artiların sayısı: "+artilar.length);            
            for (i = 0; i < artilar.length; i++) {
                sallanansayi2 = sallanansayi2.slice(0, artilar.charAt(i)) +"a"+ sallanansayi2.slice(Number(artilar.charAt(i))+1);
                rakibinsayisi = rakibinsayisi.slice(0, artilar.charAt(i)) +"b"+ rakibinsayisi.slice(Number(artilar.charAt(i))+1);
            }
            console.log("Atılan sayı: "+sallanansayi2);
            console.log("Rakibin sayısı: "+rakibinsayisi);           
            for (i = 0; i < sallanansayi2.length; i++) {   
                if(rakibinsayisi.indexOf(sallanansayi2.charAt(i))!==-1){
                    sonuc += "-";
                    var x= rakibinsayisi.indexOf(sallanansayi2.charAt(i));
                    rakibinsayisi= rakibinsayisi.slice(0,x)+"y"+rakibinsayisi.slice(x+1);
                    sallanansayi2= sallanansayi2.slice(0,i)+"x"+sallanansayi2.slice(i+1);
                }
            }
            var randomnumber = getRndInteger(1,100000000);  
            if(sonuc===""){
                sonuc ="x";
            }else if(sonuc==="++++"){
                admin.database().ref('users/'+user+'/win').transaction(count => {
                    if (count === null) {
                        return count = 1
                    } else {
                        return count + 1
                    }
                })
                admin.database().ref('users/'+rakip+'/lose').transaction(count => {
                    if (count === null) {
                        return count = 1
                    } else {
                        return count + 1
                    }
                })
                admin.database().ref("/users/"+user+"/turn").set(false);
                admin.database().ref("/users/"+rakip+"/turn").set(true);
                admin.database().ref("/users/"+user+"/sonuc").set(sonuc+"|"+randomnumber);
                return admin.database().ref("/users/"+rakip+"/rakipsallama").set(sallanansayi+"|"+randomnumber);
                //return admin.database().ref("/users/"+user+"/gal").set(true);
            }
            console.log("Rakibin sayısı(son): "+rakibinsayisi);
            console.log("Sallanan sayı(son): "+sallanansayi2);
            console.log("Sonuç: "+sonuc);                   
            admin.database().ref("/users/"+user+"/turn").set(false);
            admin.database().ref("/users/"+rakip+"/rakipsallama").set(sallanansayi+"|"+randomnumber);
            admin.database().ref("/users/"+rakip+"/turn").set(true);
            return admin.database().ref("/users/"+user+"/sonuc").set(sonuc+"|"+randomnumber);
        });        
    });
});


function sorunlu(error) {
    console.log("Something went wrong.");
    return console.log(error);
}
function getRndInteger(min, max) {
    return Math.floor(Math.random() * (max - min) ) + min;
}

/////

Frank的数据库规则建议与读/写访问相结合

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth.uid == $uid",
        ".write": "auth.uid == $uid",
        "status": {
              ".validate": "newData.val() === 'on' || data.val() === 'off'|| data.val() === 'waiting'"
            },
         "rakip": {
               ".validate": 
        "newData.parent().parent().child(newData.val()).child('rakip').val() === $uid"
        }
      }
    }
  }
}

1 个答案:

答案 0 :(得分:2)

这里强制执行一致性的最佳方法似乎是多位置更新。通过多位置更新,您可以在一次调用中向服务器发送多个更新,并且服务器强制执行所有写入操作,或者不会发生任何更新。

您的代码段:

admin.database().ref("/users/"+user+"/rakip").set(key);
admin.database().ref("/users/"+key+"/rakip").set(user);
admin.database().ref("/users/"+user+"/status").set("on");
admin.database().ref("/users/"+key+"/status").set("on");

可以使用以下内容重写为单个多位置更新:

var updates = {};
updates["/users/"+user+"/rakip"] = key;
updates["/users/"+key+"/rakip"] = user;
updates["/users/"+user+"/status"] = "on";
updates["/users/"+key+"/status"] = "on";
admin.database().ref().update(updates);

现在,您可以在服务器上使用安全规则来确保原始数据未被修改。

如果您只能将状态设置为"on"当前"off",则您需要/users/$user/status验证:

".validate": "newData.val() === 'on' && data.val() === 'off'"

更棘手的是:确保只有在设置了该用户的值时才能设置"rakip"值,验证/users/$user/rakip

".validate": "newData.parent().parent().child(newData.val()).child('rakip').val() === $user"

这些是:

{
  "rules": {
    "users": {
      "$user": {
        "status": {
          ".validate": "newData.val() === 'on' && data.val() === 'off'"
        },
        "rakip": {
           ".validate": "newData.parent().parent().child(newData.val()).child('rakip').val() === $user"
        }
      }
    }
  }
}