天天看點

利用SQL實作簡單的分布式鎖

分布式鎖和普通鎖的主要差別在于參與主體跨不同節點,是以需要考慮到節點失效和網絡故障的問題。搞清楚問題要點,可以用各種不同的東西去實作,比如Redis,ZooKeeper等。但是其實用SQL實作也是非常容易的,下面以PostgreSQL為例進行說明。

利用PostgreSQL中特有的排他會話級别咨詢鎖。

pg_advisory_lock(key bigint)

pg_advisory_unlock(key bigint)

pg_try_advisory_lock(key bigint)

詳細參考: http://www.postgres.cn/docs/9.4/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS-TABLE

這種鎖是會話級的,在釋放鎖之前,鎖的獲得得者必須一直持有這個會話,也就是連接配接,否則鎖就會被釋放。

這個特性自然而然地解決了鎖的獲得者發生故障時鎖的釋放問題。

但是,對于需要長時間持有的鎖,它會産生長連接配接,而資料庫的連接配接是比較耗資源的,往大了配一般也就幾千個,這是需要注意的地方。

另外一個需要考慮的問題是,當網絡或節點發生故障時連接配接的兩端未必能立刻感覺到,是以TCP的KeepAlive是必須的,幸好PostgreSQL的用戶端和服務端都支援這個設定。

下面是服務端的參數:

tcp_keepalives_idle

tcp_keepalives_interval

tcp_keepalives_count

鎖對象是持久的,為防止拿到鎖的用戶端奔潰導緻鎖無法釋放,每個鎖都有一個過期期限。

在PostgreSQL中可以按下面的方式實作

建表

postgres=# create table distlock(id int primary key,expired_time interval,owner text,ts timestamptz);

CREATE TABLE

postgres=# insert into distlock(id) values(1);

INSERT 0 1

加鎖和續期

postgres=# update distlock set owner='node1',ts=now(),expired_time=interval '20 second' where id=1 and (owner='node1' or owner is null or now() > ts + expired_time);

UPDATE 1

獲得鎖的用戶端如果要長時間持有鎖必須定期執行相同的方法對鎖進行續租,否則會丢鎖。

此時,其它用戶端取鎖會失敗

postgres=# update distlock set owner='node2',ts=now(),expired_time=interval '20 second' where id=1 and (owner='node2' or owner is null or now() > ts + expired_time);

UPDATE 0

等鎖過期後取鎖成功

釋放鎖

postgres=# update distlock set owner=null,ts=now() where id=1 and owner='node2';

可以看到用關系資料庫實作分布式鎖并不複雜。尤其上面基于表實作的鎖輔以靠譜的HA部署可以保障鎖資訊的持久性和不丢失,但用表更新實作鎖畢竟比較重,不适合對鎖的性能要求非常高的場景。