2013-03-28 219 views
2

將NodeJS與MongoDB + Mongoose結合使用。處理NodeJS異步行爲

首先,我知道異步非阻塞代碼的優點。所以我處理回調。但最後我遇到了以下問題。

可以說我有一個功能可以隨時由用戶調用。有可能,一個超級「閃電般」的用戶幾乎在同一時間調用它兩次。

function do_something_with_user(user_id){ 
    User.findOne({_id:user_id}).exec(function(err,user){ // FIND QUERY 
     // Do a lot of different stuff with user 
     // I just cannot update user with a single query 
     // I might need here to execute any other MongoDB queries 
     // So this code is a set of queries-callbacks 
     user.save() // SAVE QUERY 
    }) 
} 

當然它執行是這樣的:查找查詢,查找查詢,保存查詢,保存查詢

這完全破壞的應用程序的邏輯(應該找到QUERY,SAVE QUERY,查找查詢,保存查詢)。所以我決定通過爲特定用戶「鎖定」整個函數來防止異步行爲(因此函數代碼內部仍然是異步的)。

var lock_function_for_user = {} 

function do_something_with_user(user_id){ 
    if(!lock_function_for_user[user_id]){ 
     lock_function_for_user[user_id] = true 
     User.findOne({_id:user_id}).exec(function(err,user){ 
      // Same code as above 
      user.save(function(){ 
       lock_function_for_user[user_id] = false 
      }) 
     }) 
    } else { 
     setTimeout(function(){ 
      do_something_with_user(user_id) 
     },100) // assuming that average function execution time is 100ms in average 
    } 
} 

所以,我的問題是:這是一個很好的做法,好的破解或壞的破解?如果這是一個不好的破解,請提供任何其他解決方案。特別是,我懷疑這個解決方案在我們擴展和啓動多個NodeJS流程時會起作用。

回答

2

這是一個非常糟糕的做法,您不應該使用計時器來控制代碼流。

這裏的問題被稱爲原子性。如果你需要做find-save,find-save,那麼你需要以某種方式打包這些操作(事務)。這取決於您使用的軟件。在redis中,您有多重命令和可執行命令。在mongodb中你有findAndModify()。另一個解決方案是使用索引。當您嘗試兩次保存相同的字段時出現錯誤。使用屬性,「指數:真正的」和「獨特的:真正的」在中的SchemaType貓鼬:

var schema = mongoose.Schema ({ 
    myField: { type: String, index: true, unique: true, required: true }, 
}); 

這是你所需要的:Mongodb - Isolate sequence of operations - Perform Two Phase Commits。但是要考慮到,如果你需要做很多交易,mongodb不可能是最好的選擇。

+0

做你的答案確實有道理。你提到了良好的做法。但是,假設我有FIND-SOMECODE-FIND-SOMECODE-SAVE-SAVE(找到一個文檔,執行某些操作,找到另一個文檔,執行某些操作,同時保存兩個文檔)。看來我不能在這裏使用findAndModify。我想我也不能使用索引。如果Mongo在第二次保存時拋出一個錯誤,我當然可以先回滾一下,但那又怎麼樣?如何嘗試再次調用該函數?所以,即使索引,問題仍然存在。 – igorpavlov

+0

檢查這個網頁:http://docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations/。執行兩階段提交。 –

+0

最後,我需要在這裏。謝謝。我將使用Redis-MongoDB混合。無論如何,你的回答非常有幫助,因爲Redis也是非阻塞的。因此,我將使用多個Redis和Mongo隔離。 – igorpavlov

0

你不想浪費RAM,所以用

delete lock_function_for_user[user_id] 

除了更換

lock_function_for_user[user_id] = false 

:你可以just be optimistic and retry if a conflict happens。只需省略鎖定,並確保數據庫在出現錯誤時通知(並在此情況下重試)。當然,哪種方式更好取決於這種衝突發生的頻率。

+0

謝謝。當然我會使用刪除,這是一個很好的觀點。但是,在我提到的下面的答案中,如果DB注意到某些奇怪的東西並且處理了錯誤,我不知道如何再次啓動該函數。我不想失去用戶請求,我的目標不僅僅是保持數據庫不被垃圾寫入,而且還保留用戶請求並重試。這是一個問題:「如何重試?」。假設,當我們縮放時,這個錯誤可能會經常發生。 – igorpavlov

+0

@igorpavlov如何使用這樣的模式? 'performChange(document_id,function(document){...; return changedDocument;})'然後performChange可以在錯誤發生時再次調用它自己。基本上,我認爲通常應該可以通過對新函數應用相同的更改來重試,所以您只需要能夠將該更改表示爲以舊文檔作爲參數並返回新文檔的JS函數。 – thejh

+0

調用函數self會導致對數據庫的大量查詢。你永遠不知道,當以前的函數調用查詢將結束:10ms,1000ms?它會產生巨大的混亂。順便說一句,它看起來與.save(err,saved_document)一樣{/ *用保存的文檔*/ – igorpavlov