about 1 year ago

何謂 race condition ?

競爭危害(race hazard)又名競態條件、竞争条件(race condition),它旨在描述一個系統或者進程的輸出依赖于不受控制的事件出现顺序或者出现时机。 此词源自於兩個訊號試著彼此競爭,來影響誰先輸出。 -- wiki - 競爭危害

AASM 中的 race condition

開發 rails project 很常會使用 AASM 來進行狀態管理,好處是你可以不必寫一堆 if else 來判斷狀態該往何處走。

那麼有一種情況是,當兩個用戶同時更新一筆數據的狀況下,造成 race condition 的情況出現

舉例以交易訂單生成來說

  • order_placed: 是當訂單成立後的狀態
  • notify_paid: 是買家通知付款
  • done: 是完成訂單

那麼有種情況是,買家還沒通知付款,我這邊已經收到帳款,所以可以直接將訂單完成。

但有沒有一種可能是,賣家和買家在同一個時間點點擊狀態變更的按鈕

  • 賣家按下訂單完成
  • 買家通知已經付款

雖然在 AASM 很聰明的幫我們限制了能變更狀態的條件,但是卻會出現在被 object 暫存記憶體的時候,產生 race condition。

class Order < ActiveRecord::Base
  include AASM

  aasm do
    state :order_placed, :initial => true
    state :notify_paid
    state :done
  end
  
  event :notify do
    transitions :from => :order_placed, :to => :notify_paid
  end
  
  event :deliver do
    transitions :from => [:order_placed, :notify_paid], :to => :done
  end
end

這時候我們測試

# 這時候 Order 狀態應該是初始化狀態 order_placed

order_1 = Order.last
order_2 = Order.last

order_1.deliver! # true, 狀態變更為 done
order_2.notify! # true, 狀態變更為 notify_paid

order_2 不是應該被 rollback 拒絕存取才對嗎? order 應該已經變成 done, 沒理由又可以變成 notify_paid 才對呀

但在這個案例中的 object 裡面

order_1, order_2 這兩個 object 已經透過暫存記憶體記住訂單的狀態是 order_placed,所以他們合理的可以轉換狀態,但這樣就不符合我們的邏輯了,難不成在每次 order 執行的時候都要 reload 嗎?

其實不必,我們可以用 AASM 中的 Pessimistic Locking (悲觀鎖定) 來解決。

只需要加上 requires_lock: true,就可以避免 race confidtion

class Job < ActiveRecord::Base
  include AASM

  aasm requires_lock: true do
    ...
  end

  ...
end

讓我們在測試一次

order_1 = Order.last
order_2 = Order.last

order_1.deliver! # true, 狀態變更為 done
order_2.notify! # ROLLBACK, AASM::InvalidTransition: Event 'notify' cannot transition from 'done'.

完美了,可以避免同時間操作產生的 race condition。

小結

不過 Pessimistic Locking(悲觀鎖定)要看情況使用,因為他會影響一些 performance , 他的執行原理是如其名,悲觀的認定每一筆資料存取時,其他的客戶端也會存取同一筆資料,因此對相關的資料進行鎖定,直到自己操作完成才解除鎖定。

這樣的好處是

  • 提高隔離層級
  • 避免 race condition

壞處是

  • 存取效能變差,因為每次都要鎖定

如果你的 project 不會有同時改變狀態的可能發生,可以不用強制一定要在 aasm 上加上 Pessimistic Locking,只需要在適當的時機在用就可以了。

參考資源

悲觀鎖定(Pessimistic Locking)
競爭危害

← Resotre your older version to newer version, iPhone X + iOS 11.2 Beta [Rails] Devise password 密碼設置複雜度 →
 
comments powered by Disqus