分布式锁

284 阅读4分钟

分布式锁一般有以下几种
mysql锁
zk
redis
mysql做锁,首先如果是数据库里面的内容不用麻烦自己做锁,mysql自己有表锁行锁等,当执行sql语句时自动加锁,比如select * from order_table where id = 'xxx' for update,如果不是数据库的内容,可以利用mysql自己做锁,往数据库里面写数据,如果已经存在则有几种不同策略

阻塞锁:在循环语句里面一直查询直到锁释放,自己写数据即加锁
非阻塞获取锁:没有循环语句,查一下没有锁就插入新数据即加锁,已经有数据即有锁就返回
带超时时间的锁:在循环语句中,查询是否已经有锁,没有锁,写数据获得锁返回,有锁不断查询,判断超时就返回

解锁直接在数据库里面删除数据记录就可以了 如果锁超时,即机器挂了没来得及解锁,防止这种情况,可以设置一个定时任务,过了这个时间认定锁没正确释放去释放锁 借鉴文章

zk分布式锁

所以调整后的分布式锁算法流程如下:

客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。
客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听刚好在自己之前一位的子节点删除消息,获得子节点变更通知后重复此步骤直至获得锁;
执行业务代码;
完成业务流程后,删除对应的子节点释放锁

创建的临时节点能够保证在故障的情况下锁也能被释放,考虑这么个场景:假如客户端a当前创建的子节点为序号最小的节点,获得锁之后客户端所在机器宕机了,客户端没有主动删除子节点;如果创建的是永久的节点,那么这个锁永远不会释放,导致死锁;由于创建的是临时节点,客户端宕机后,过了一定时间zookeeper没有收到客户端的心跳包判断会话失效,将临时节点删除从而释放锁 在步骤2中获取子节点列表与设置监听这两步操作的原子性问题,考虑这么个场景:客户端a对应子节点为/lock/lock-0000000000,客户端b对应子节点为/lock/lock-0000000001,客户端b获取子节点列表时发现自己不是序号最小的,但是在设置监听器前客户端a完成业务流程删除了子节点/lock/lock-0000000000,客户端b设置的监听器岂不是丢失了这个事件从而导致永远等待了?这个问题不存在的。因为zookeeper提供的API中设置监听器的操作与读操作是原子执行的,也就是说在读子节点列表时同时设置监听器,保证不会丢失事件 借鉴文章

redis锁

set key val nx ex val 可以设置一个只有该客户端知道的值,如果释放锁的时候和val不符,说明现在已经是别人的锁了,不能释放了

etcd锁 etcd 中没有像 zookeeper 那样的 有序 节点。所以其锁实现和基于 zookeeper 实现的有所不同。github.com/zieckey/etcdsync项目中的 Lock 流程是:

先检查 /lock 路径下是否有值,如果有值,说明锁已经被别人抢了 如果没有值,那么写入自己的值。写入成功返回,说明加锁成功。写入时如果节点被其它节点写入过了,那么会导致加锁失败,这时候到 3 watch /lock 下的事件,此时陷入阻塞 当 /lock 路径下发生事件时,当前进程被唤醒。检查发生的事件是否是删除事件(说明锁被持有者主动 unlock),或者过期事件(说明锁过期失效)。如果是的话,那么回到 1,走抢锁流程。