分散式鎖 -- 基於資料庫實現
基於資料庫來做分散式鎖的話,通常有兩種做法:
- 基於資料庫的樂觀鎖
- 基於資料庫的悲觀鎖
樂觀鎖:
樂觀鎖機制其實就是在資料庫表中引入一個版本號(version)欄位來實現的。當我們要從資料庫中讀取資料的時候,同時把這個version欄位也讀出來,如果要對讀出來的資料進行更新後寫回資料庫,則需要將version加1,同時將新的資料與新的version更新到資料表中,且必須在更新的時候同時檢查目前資料庫裡version值是不是之前的那個version,如果是,則正常更新。如果不是,則更新失敗,說明在這個過程中有其它的程序去更新過資料了。
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
如圖,假設同一個賬戶,使用者A和使用者B都要去進行取款操作,賬戶的原始餘額是2000,使用者A要去取1500,使用者B要去取1000,如果沒有鎖機制的話,在併發的情況下,可能會出現餘額同時被扣1500和1000,導致最終餘額的不正確甚至是負數。但如果這裡用到樂觀鎖機制,當兩個使用者去資料庫中讀取餘額的時候,除了讀取到2000餘額以外,還讀取了當前的版本號version=1,等使用者A或使用者B去修改資料庫餘額的時候,無論誰先操作,都會將版本號加1,即version=2,那麼另外一個使用者去更新的時候就發現版本號不對,已經變成2了,不是當初讀出來時候的1,那麼本次更新失敗,就得重新去讀取最新的資料庫餘額。
通過上面這個例子可以看出來,使用「樂觀鎖」機制,必須得滿足:
(1)鎖服務要有遞增的版本號version
(2)每次更新資料的時候都必須先判斷版本號對不對,然後再寫入新的版本號
悲觀鎖:
總是假設最壞的情況,每次取資料時都認為其他執行緒會修改,所以都會加鎖(讀鎖、寫鎖、行鎖等),當其他執行緒想要訪問資料時,都需要阻塞掛起。可以依靠資料庫實現,如行鎖、讀鎖和寫鎖等,都是在操作之前加鎖,在Java中,synchronized的思想也是悲觀鎖。
悲觀鎖也叫作排它鎖,在Mysql中是基於 for update 來實現加鎖的,例如:
//鎖定的方法-虛擬碼 public boolean lock(){ connection.setAutoCommit(false) for(){ result = select * from user where id = 100 for update; if(result){ //結果不為空, //則說明獲取到了鎖 return true; } //沒有獲取到鎖,繼續獲取 sleep(1000); } return false; } //釋放鎖-虛擬碼 connection.commit();
上面的示例中,user表中,id是主鍵,通過 for update 操作,資料庫在查詢的時候就會給這條記錄加上排它鎖。
(需要注意的是,在InnoDB中只有欄位加了索引的,才會是行級鎖,否者是表級鎖,所以這個id欄位要加索引)
當這條記錄加上排它鎖之後,其它執行緒是無法操作這條記錄的。
那麼,這樣的話,我們就可以認為獲得了排它鎖的這個執行緒是擁有了分散式鎖,然後就可以執行我們想要做的業務邏輯,當邏輯完成之後,再呼叫上述釋放鎖的語句即可。