2017-08-29 49 views
0

Sidekiq建議所有作業都是冪等的(能夠多次運行而不會成爲問題),因爲它無法保證作業只能運行一次。使用Sidekiq Ruby on Rails的冪等設計作業

我無法理解在某些情況下實現該目標的最佳方法。例如,假設你有如下表:

用戶 ID 電子郵件 平衡

是簡單地運行後臺作業增加了一定量的資產負債

def perform(user_id, balance_adjustment) 
user = User.find(user_id) 
user.balance += balance_adjustment 
user.save 
end 

如果作業運行他們的餘額不止一次會不正確。什麼是這樣的最佳做法?

如果我想成爲一個潛在的解決方案,我可以想出是安排這就是說,

PendingBalanceAdjustment USER_ID balance_adjustment

作業運行時,它需要在作業之前創造紀錄爲該用戶獲取鎖定,以便兩個工作人員之間不存在競爭狀況的機會,然後在釋放鎖定之前需要更新餘額並從未決餘額調整中刪除該記錄。

那麼這份工作看起來像這樣?

def perform(user_id, balance_adjustment_id) 
    user = User.find(user_id) 
    pba = PendingBalanceAdjustment.where(:balance_adjustment_id => balance_adjustment_id).take 
    if pba.present? 
    $redis.lock("#{user_id}/balance_adjustment") do 
     user.balance += pba.balance_adjustment 
     user.save 
     pba.delete  
    end 
    end 
end 

這似乎解決兩個

兩名工人正在作業的同時(雖然你會覺得Sidekiq已經可以保證這一點?) B)作業正在運行之間

一)競爭條件成功運行多次後

這種模式是一個很好的解決方案嗎?

回答

1

你在正確的軌道上;你想使用數據庫事務,而不是redis鎖。

+0

嘿!感謝您的即時迴應。我可以將平衡調整和掛起行刪除放入數據庫事務中,但是這並不一定能夠防止多線程併發問題,其中兩名工作人員都嘗試同時運行事務嗎? –

+0

對於每一項與此類似的工作,必須要有某種類型的PendingABC記錄,這看起來似乎有點矯枉過正/很多開銷。這真的是最好的模式嗎? –

+0

通常有一個跟蹤每個貨幣調整歷史的信用/借記表。 「user.balance」列只是成爲sum(PBA.amount)的緩存副本。我會使用SQL而不是Ruby來自動完成這些事情。單個UPDATE語句比Ruby中的多個網絡調用快得多/可靠。 –

0

我認爲你也是在正確的軌道上,但你的解決方案可能是矯枉過正,因爲我沒有你的應用程序的完整知識。

但是,更簡單的解決方案只不過是在你的旗幟User模型如balance_updated:datetime。所以,你可以在更新之前檢查一下。

由於Mike提到使用Transaction塊應該確保它是線程安全的。

在任何情況下,爲了更一般地回答您的問題...擁有updated_列通常已經夠好了,然後如果它變得複雜,您可以將這些東西移動到另一個模型。

+0

感謝您的想法。我認爲這個解決方案的問題雖然是現在你必須引入一個任意時間限制一個列應該被允許更新的頻率?這是這個想法嗎? 因此,工作將檢查餘額是否從現在開始的一小段時間內更新,並保證是否低於該閾值? –

+0

此外,使用此解決方案,您仍然需要一個鎖,因爲在更新時間戳之前,兩個線程仍然可以讀取該行,並且這兩個作業仍將運行 –