Lock 在 Rails 中的實務應用

紅寶鐵軌客
Join to follow...
Follow/Unfollow Writer: 紅寶鐵軌客
By following, you’ll receive notifications when this author publishes new articles.
Don't wait! Sign up to follow this writer.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.
寫程式中、折磨中、享受中 ......
1.18K   0  
·
2020/02/20
·
5 mins read


這篇比較少程式碼,我想要寫的對 rails 的 「Optimistic 樂觀鎖」 與 「Pessimistic 悲觀鎖」的看法。

鎖住(lock)一筆資料(raw),或甚至是鎖住一個資料表(table),都是 Mutex 的一種實用,Mutex 還有一位長得很像的兄弟,叫 semaphore,唸 Sam-mer-for,在英文中原來是指旗號,我在讀書的時候,對這些都很感興趣,這些也都是學電腦的人打屁聊天的題材,難嗎?如果要自己寫低階的 Lock,很難,真的很難,但是要搞懂,用它,倒是還好。

先說笑話:你知道 Mutex 跟 semaphore 的差別嗎?其實很簡單啦,Mutex 就是飯店房間內的廁所,只有一間,一個人進去後,其他人就要等。 semaphore 就像是很多間的公廁,大家排隊,只要有空的就可以進去,所以說,Mutex 就是 semaphore 的一種,就是只有一間的公廁啦。

 

再來讓大家腦筋轉轉彎,下面有兩題:

  1. 只限量:電商發一百張折價卷,要怎麼在 Rails 中實作?
  2. 限量又限人:電商再發一百張折價卷,而且每一個用戶限一張,在 Rails 中又怎麼實作?

先不要看下去,要想想,其實,第二題是真的需要腦筋轉轉彎的。

 

只限數量

好吧,我們先來看第一題,限量,通常,我們會有一個折價卷的資料表,我們就叫他「Coupon」,這次發的 100 張某某折價卷就會是他的一筆資料(raw),限量 100 張就會先寫在這筆資料中的「剩餘」欄位,每發出一張,就扣一張,扣到零就發完了,簡單。

實務應用上,如果程式不鎖住的話,在有很多人搶的情況下,很可能 100 張發到最後變成發出 150 張,但是如果都沒人搶,一點問題都沒有,所以,實務上,很多人沒寫,反正發多了就算了(笑)。

在 Rails 中使用 lock 非常簡單,只是 Rails 有兩種鎖,悲觀鎖與樂觀鎖,你要用那一種?這有點難,我們就不介紹悲觀鎖與樂觀鎖了,網路上很多人寫,這篇不錯,我們要考慮的是上鎖後怎麼改,很多人都說樂觀鎖的效率好,真的嗎?我做了一個簡單的表:

一人上鎖,別人可以看嗎? 一群人一起改,怎麼辦?

Optimistic 樂觀鎖

可以

只有第一個能改,其他人

就出「exception 錯誤」

Pessimistic 悲觀鎖 不行

以這一題來說,我會用悲觀鎖,原因就只有簡單,如果我用樂觀鎖,我需要:

  • 資料表中要加一個 lock_version 的 integer 欄位,Rails 會自動用這個欄位來做鎖,只是為了 100 張,有這個必要嗎?
  • 樂觀鎖可以讀「剩餘」張數,不用等,很棒,但是當你讀到後,去扣一張時,如果正好有別人也在扣,Rails 就會出 exception error,程式的寫法就要 repeat,重來,再來一次,重讀重寫,只有 100 張,可能就不用寫 Timeout 了,但是還是很麻煩啊。

用悲觀鎖就什麼都不用加,一群人來索取折價卷時,就排隊等,實務上這不是很正常嗎?伺服器也不用再一直重讀重寫,不用寫 Timeout。

 

限量又限人

這一題看起來很簡單,就跟上面一樣,上個鎖慢慢排對就好了,可惜不對,這個難在一人限一張,只要有人在瀏覽器上一次開個十個 tab 一起搶,很容易就破功。 所以我們的程式,在同一段上鎖的時間內:

  1. 去排隊領折價卷,
  2. 搶到後,新增得到的折價卷領取紀錄,而且一個人只能新增一筆,

難就難在「只能新增一筆」這點上,很多人想到的都是鎖住整個資料表,也可以啦,只是你會發現原來 Rails 不提供鎖整個資料表,我想也是因為這樣做太不合理了,不是不能做,真的很怪,一人佔住整個大樓,不好,真要......

鎖住整個資料表

可以用這個 gem,我是能不用 gem 就不用的人啦,不想用 gem 就用 raw SQL 吧,如下:

# For Postgres
ActiveRecord::Base.transaction do
  ActiveRecord::Base.connection.execute('LOCK table_name IN ACCESS EXCLUSIVE MODE')
  ...
end
# from https://stackoverflow.com/a/40492581/4080636
Mutex 鎖

鎖住整個資料表真的很糟,那就來個 Mutex 或是 semaphore 如何?也就可以用來控制每個人只能進一次,沒想到 Rails 真有奇人異士,就有人寫了這個 with_advisory_lock gem,這個好,只是好像用的人不多,為什麼?難道限量又限人的應用不多?原來不是,正解是鎖父母,讓我們看下去,不過,我想我以後一定有地方會用到這個好玩的 Mutex gem。

鎖住父母

原來,如果要限制資料庫的新增,最簡單的辦法就是把資料的 parent 鎖住,以這個例子來說,就是將這位使用者的使用者資料用「悲觀鎖」鎖住,當他用其他 tab 來搶折價卷時,因為使用者的資料被鎖住,他就只能等,當第一筆新增完成後,排隊進來的都會發現已經領過了,想搶兩張,門都沒有。

這一定要用悲觀鎖,不然會寫很多碼,沒必要啦.

 

話說

好像我都沒說樂觀鎖的好話呢,well,也不是這樣說,樂觀鎖在有大量讀取、又要能鎖定更新的場景,是有很重要的應用場合的,例如購物商品中的庫存,實務上就絕不能把商品光是有人看就鎖住,畢竟看的人多買的人少,但是實務上,樂觀鎖確實不好寫,很多例外與 timeout 要可慮,用的時候辛苦多了。

 

 

 

 

 


WriterShelf™ is a unique multiple pen name blogging and forum platform. Protect relationships and your privacy. Take your writing in new directions. ** Join WriterShelf**
WriterShelf™ is an open writing platform. The views, information and opinions in this article are those of the author.


Article info

This article is part of:
Categories:
Tags:
Date:
Published: 2020/02/20 - Updated: 2020/03/07
Total: 1473 words


Share this article:
About the Author

很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
就,這會一點點,那會一點點⋯⋯




Join the discussion now!
Don't wait! Sign up to join the discussion.
WriterShelf is a privacy-oriented writing platform. Unleash the power of your voice. It's free!
Sign up. Join WriterShelf now! Already a member. Login to WriterShelf.