分布式锁


发布于 2019-04-19 / 12 阅读 / 0 评论 /
分布式锁的设计和实现

分布式锁的实现有多种方案,常用的有zookeeper方案、redis方案、mysql数据库方案。不同的方案依赖组件不同,可针对的场景也各异。

1.zookeeper方案分布式锁

该方案通过PERSISTENT_WITH_TTL来实现,PERSISTENT_WITH_TTL是带有过期时间的持久节点,如果该znode在给定的TTL时间内未被修改,且该节点无子节点,则该znode会被删除。

锁的生命周期相关的方法有3个。

1.1.获得锁

获得锁的唯一方法就是创建成功PERSISTENT_WITH_TTL类型的znode,如果znode已经存在,则会抛出异常,提示“Node already exists”,当前session无法获得锁。

1.2.维持锁

锁节点为PERSISTENT_WITH_TTL类型,当达到过期时间,锁被释放,锁节点被删除。为了更好地维护当前会话的锁,我们需要维持与zookeeper server之间的心跳,保证锁锁节点能在过期时间内被更新,心跳主要是更新锁节点的数据,这里需要对锁节点数据进行一定的设计,保证和业务逻辑相一致。

1.3.释放锁

释放锁有两种情况:主动释放锁,即由zookeeper client删除锁节点znode;被动释放锁,即锁节点znode已过期,被zookeeper server自动删除。

2.redis方案分布式锁

redis通过内置的setnx、pexpire(提供基于毫秒的过期时间,expire提供基于秒的过期时间)+ lua脚本(保证脚本中的命令被一起执行,不间断)来实现分布式锁。

setnx 是set if not exists的简写。用法如下:

// 用法一:不带过期时间
SETNX key value
如果不存在set成功返回int的1,这个key存在了返回0。

// 用法一:带过期时间
SETEX key seconds value
将值 value 关联到 key ,并将 key 的生存时间设为 seconds (以秒为单位)。
如果 key 已经存在,setex命令将覆写旧值。
setex是一个原子性(atomic)操作,关联值和设置生存时间两个动作会在同一时间内完成。

2.1.加锁

加锁通过setnx来实现,如果setnx成功,则加锁成功,否则加锁失败。

2.2.解锁

删除锁时,先执行get,如果获取的值是自己设置的,则执行del操作,同时,这两个操作也放在lua脚本中执行,来保证原子性。

3.mysql方案分布式锁

用数据库实现分布式锁的方式和redis分布式锁的实现方式类似,这里采用数据库表的唯一键的形式。如果同一个时刻,多个线程同时向一个表中插入同样的记录,由于唯一键的原因,只能有一个线程插入成功。