1. 程式人生 > >淺談分散式鎖--基於資料庫實現篇

淺談分散式鎖--基於資料庫實現篇

淺談分散式鎖--基於資料庫實現篇

1、基於資料庫表

    要實現分散式鎖,最簡單的方式可能就是直接建立一張鎖表,然後通過操作該表中的資料來實現了。
    當我們要鎖住某個方法或資源時,我們就在該表中增加一條記錄,想要釋放鎖的時候就刪除這條記錄。

    建立這樣一張資料庫表:

CREATE TABLE `methodLock` (
	`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
	`method_name` VARCHAR (64) NOT NULL DEFAULT '' COMMENT '鎖定的方法名',
	`desc` VARCHAR (1024) NOT NULL DEFAULT '備註資訊',
	`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '儲存資料時間,自動生成',
	PRIMARY KEY (`id`),
	UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE     
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '鎖定中的方法';

    當我們想要鎖住某個方法時,執行以下SQL:

insert into methodLock(method_name,desc) values (‘method_name’,‘desc’);

    因為我們對method_name做了唯一性約束,這裡如果有多個請求同時提交到資料庫的話,資料庫會保證只有一個操作可以成功,那麼我們就可以認為操作成功的那個執行緒獲得了該方法的鎖,可以執行方法體內容。

    當方法執行完畢之後,想要釋放鎖的話,需要執行以下Sql:

delete from methodLock where method_name ='method_name';

    上面這種簡單的實現有以下幾個問題:

    1、這把鎖強依賴資料庫的可用性,資料庫是一個單點,一旦資料庫掛掉,會導致業務系統不可用。
    2、這把鎖沒有失效時間,一旦解鎖操作失敗,就會導致鎖記錄一直在資料庫中,其他執行緒無法再獲得到鎖。
    3、這把鎖只能是非阻塞的,因為資料的insert操作,一旦插入失敗就會直接報錯。沒有獲得鎖的執行緒並不會進入排隊佇列,要想再次獲得鎖就要再次觸發獲得鎖操作。
    4、這把鎖是非重入的,同一個執行緒在沒有釋放鎖之前無法再次獲得該鎖。因為資料中資料已經存在了。

2、基於資料庫排他鎖

    除了可以通過增刪操作資料表中的記錄以外,其實還可以藉助資料中自帶的鎖來實現分散式的鎖。
    我們還用剛剛建立的那張資料庫表。可以通過資料庫的排他鎖來實現分散式鎖。 基於MySql的InnoDB引擎,可以使用以下方法來實現加鎖操作:

    public boolean lock(){
        connection.setAutoCommit(false)
        while(true){
            try{
                result = select * from methodLock where method_name=xxx for update;
                if(result==null){
                    return true;
                }
            }catch(Exception e){
     
            }
            sleep(1000);
        }
        return false;
    }

    在查詢語句後面增加for update,資料庫會在查詢過程中給資料庫表增加排他鎖(InnoDB引擎在加鎖的時候,只有通過索引進行檢索的時候才會使用行級鎖,否則會使用表級鎖。這裡我們希望使用行級鎖,就要給method_name新增索引,值得注意的是,這個索引一定要建立成唯一索引,否則會出現多個過載方法之間無法同時被訪問的問題。過載方法的話建議把引數型別也加上。)。當某條記錄被加上排他鎖之後,其他執行緒無法再在該行記錄上增加排他鎖。

    我們可以認為獲得排它鎖的執行緒即可獲得分散式鎖,當獲取到鎖之後,可以執行方法的業務邏輯,執行完方法之後,再通過以下方法解鎖:

    public void unlock(){
        connection.commit();
    }


    通過connection.commit()操作來釋放鎖。

    這種方法可以有效的解決上面提到的無法釋放鎖和阻塞鎖的問題。

    阻塞鎖? for update語句會在執行成功後立即返回,在執行失敗時一直處於阻塞狀態,直到成功。
    鎖定之後服務宕機,無法釋放?使用這種方式,服務宕機之後資料庫會自己把鎖釋放掉。

    但是還是無法直接解決資料庫單點和可重入問題。

    這裡還可能存在另外一個問題,雖然我們對method_name 使用了唯一索引,並且顯示使用for update來使用行級鎖。
    但是,MySql會對查詢進行優化,即便在條件中使用了索引欄位,但是否使用索引來檢索資料是由 MySQL 通過判斷不同執行計劃的代價來決定的,如果 MySQL 認為全表掃效率更高,比如對一些很小的表,它就不會使用索引,這種情況下 InnoDB 將使用表鎖,而不是行鎖。

    還有一個問題,就是我們要使用排他鎖來進行分散式鎖的lock,那麼一個排他鎖長時間不提交,就會佔用資料庫連線。一旦類似的連線變得多了,就可能把資料庫連線池撐爆。嚴重影響效能。

3、總結:

    使用資料庫來實現分散式鎖的方式,這兩種方式都是依賴資料庫的一張表,一種是通過表中的記錄的存在情況確定當前是否有鎖存在,另外一種是通過資料庫的排他鎖來實現分散式鎖。
    資料庫實現分散式鎖的優點:直接藉助資料庫,容易理解。
    資料庫實現分散式鎖的缺點:會有各種各樣的問題,在解決問題的過程中會使整個方案變得越來越複雜。
    操作資料庫需要一定的開銷,效能問題需要考慮。
    使用資料庫的行級鎖並不一定靠譜,尤其是當我們的鎖表並不大的時候。

分散式效果最差的實現方式,不建議使用。後面介紹的兩種方式,可以根據不同的需求,採用相應的實現。


參考資料:http://www.hollischuang.com/archives/1716

 

每天努力一點,每天都在進步。