利用SQL实现简单的分布式锁
分布式锁和普通锁的主要区别在于参与主体跨不同节点,因此需要考虑到节点失效和网络故障的问题。搞清楚问题要点,可以用各种不同的东西去实现,比如Redis,ZooKeeper等。但是其实用SQL实现也是非常容易的,下面以PostgreSQL为例进行说明。
1. 方法1:会话锁
利用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
2. 方法2:期限锁
锁对象是持久的,为防止拿到锁的客户端奔溃导致锁无法释放,每个锁都有一个过期期限。
在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='node2',ts=now(),expired_time=interval '20 second' where id=1 and (owner='node2' or owner is null or now() > ts + expired_time);
- UPDATE 1
释放锁
- postgres=# update distlock set owner=null,ts=now() where id=1 and owner='node2';
- UPDATE 1
3. 总结
可以看到用关系数据库实现分布式锁并不复杂。尤其上面基于表实现的锁辅以靠谱的HA部署可以保障锁信息的持久性和不丢失,但用表更新实现锁毕竟比较重,不适合对锁的性能要求非常高的场景。