2014-06-10 67 views
1

我想知道什麼是設計這樣一個Web服務的方式:避免競爭條件

說我有一臺服務器監聽請求,它會收到一些重點和檢查,如果它的緩存(例如使用一些數據庫),如果不是它做了一些處理,則生成答案,將其存儲在高速緩存數據庫中並將答案返回給客戶端。

這似乎工作正常,但如果兩個客戶端請求相同的不存在的密鑰會發生什麼?在這種情況下,競爭條件會發生,所以它看起來像

client 1 -> check cache DB -> generate answer -> store in cache -> reply to client 
client 2 -> check cache DB -> generate answer -> store in cache -> reply to client 

的一種方式,以避免此問題將在DB採用了獨特的功能,因此每當產生並寫入到數據庫的第二個答案,發生一些錯誤。這很好,但看起來更像是一個補丁,而不是一個真正的解決方案。特別是,想象一個產生答案需要大量處理的情況,那麼其他事情會更好。

我能想到的一個選擇是使用作業隊列,所以無論何時收到一個密鑰,密鑰要麼附加到現有作業,要麼將新作業添加到隊列中。

我一直在玩node.js幾個星期,我很驚訝我沒有找到顯示這種用例的例子。所以我想知道這是否是一種可接受的解決方案,或者更好的方案?

回答

2

這裏是你如何能做到在一個單進程設置:

var Emitter = require('events').EventEmitter; 

var requests = Object.create(null); 

function getSomething (key, callback) { 

    var request = requests[key]; 

    if (!request) { 
    request = requests[key] = new Emitter; 

    getSomethingActually(key, function (err, result) { 
     delete requests[key]; 
     if (err) return request.emit('error', err); 
     request.emit('result', result); 
    }); 
    } 

    request.once('result', function (result) { 
    callback(null, result); 
    }); 

    request.once('error', function (err) { 
    callback(err); 
    }); 

} 

,如果你想擴展這一點,你需要使用一些外部存儲+事件總線,像Redis的。

0

您應該使用作業隊列(或其他類型的卸載作業)。處理密集型任務應始終從主節點應用程序中取出(通過隊列,將其產生爲單獨的進程等),否則將阻塞事件循環,從而阻止所有其他請求。

這就是說,如果您選擇使用某種可以具有唯一約束的隊列(例如postgres支持的隊列),併爲該鍵設置唯一約束,那麼重複將永遠不會插入到工作隊列中,所以永遠不會被處理兩次。在這種情況下,您可以簡單地忽略唯一的約束錯誤。

注意,它仍然是可能的可能,但可能性非常小,能有像事件的順序:

  1. 要求檢查重點X上的「緩存」,獲取一個小姐
  2. 工人完成回答密鑰x,將其插入「緩存」,從隊列中刪除X
  3. 請求接收到的未命中密鑰x,將其添加到隊列
  4. 工人從隊列拉出密鑰x,開始計算

在此事件(可能不太可能)發生後,第二個工作人員會在插入密鑰時出錯。在我看來,這可能是一個不太可能發生的事情,添加一個唯一的關鍵約束,而忽略第二個工作者的唯一約束違規錯誤可能是一個足夠可行的選擇。