写点什么

分布式锁

  • 2022 年 9 月 12 日
    广东
  • 本文字数:1974 字

    阅读完需:约 6 分钟

数据库实现分布式锁


方案一:根据数据库主键的唯一性约束;


获取锁:向数据库表中插入一条特定键(lock)的数据,如果插入成功则获取锁成功,反之则失败;
释放锁:删除这条记录
复制代码


方案二:利用 update 与版本号


先获取锁的信息select id, method_name, state,version from method_lock where state=1 and method_name='methodName';
占有锁 update t_resoure set state=2, version=2, update_time=now() where method_name='methodName' and state=1 and version=2;如果没有更新影响到一行数据,则说明这个资源已经被别人占位了。
复制代码


缺点:


1.强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用


2.锁没有失效时间,一旦解锁失败,就会导致锁一致记录在数据库中,其他线程无法获取到;


3.这把锁是非阻塞的,没有获取到就不会进入排队,要想再次获取锁就要再次触发获取锁的操作。


4.锁是非重入的,同一个线程在没有释放锁之前是无法再次获得锁。


解决方案:


1、数据库集 主备模式


2、没有失效时间,做定时任务,每隔一段时间把数据库中的数据清理一遍


3、非阻塞?搞个循环,知道 insert 成功再返回成功;


4、非重入?在数据库中加个字段,记录当前机器的信息和线程信息,下次在获取时,如果一样,直接分配锁。


redis 实现


watch 利用 watch 实现 redis 乐观锁


乐观锁基于 CAS 思想,是不具有互斥性,不会产生锁等待而消耗资源,但需要反复的重试,但也是因为重试的机制,能编辑哦块的响应,因此可以利用 redis 来实现乐观锁。具体思路:


1、利用 redis 的 watch 功能(),监控 redisKey 的状态


2、获取 redisKey 的值


3、创建 redis 事务


4、给这个 key 的值+1


5、然后执行这个这个事务,如果这个 key 发生变化,则回滚事务


setnx


实现原理,利用redis的setnx命令,如果不存在则设置成功,存在则设置失败。
复制代码


获取锁:


方式一(使用 set 命令)--推荐


/** * 使用redis的set命令实现获取分布式锁 * @param lockKey 可以就是锁 * @param requestId 请求ID,保证同一性 uuid+threadID * @param expireTime 过期时间,避免死锁 * @return */ public boolean getLock(String lockKey,String requestId,int expireTime) {     //NX:保证互斥性 // hset 原子性操作 只要lockKey有效 则说明有进程在使用分布式锁     String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);     if("OK".equals(result)) {         return true; }return false; }
复制代码


方式 2(使用 setnx 命令实现) -- 并发会产生问题


public boolean getLock(String lockKey,String requestId,int expireTime) {     Long result = jedis.setnx(lockKey, requestId);     if(result == 1) {         //成功设置 进程down 永久有效 别的进程就无法获得锁           jedis.expire(lockKey, expireTime);         return true; }return false;}
复制代码


释放锁:


不能直接使用 del 命令;可能会删掉其他线程获取到的锁,比如 A 线程获得锁 S,要删除时,刚好过期了,被 B 线程获取到,则删除时会把 B 线程的锁释放掉,最好的就是使用 lua 脚本,获取 key 与释放 key 作为一个原子操作。


redis 锁存在的问题


0、锁过期了,业务还没执行完,锁可能会被其他的线程获取到


1、单机 无法保证高可用


2、主从模式无法保证数据的强一致性,在主机宕机时会造成锁的重复获得。


3、无法续租 在超过过期时间后无法续期,不能继续使用。


4、redis 集群不能保证强一致性,redis 集群时 AP 模型,所以当业务是强一致性时,不能使用 redis 作为分布式锁,可以使用 CP 模型实现:如 zookeeper


zookeeper 实现分布式锁


要清楚zookeeper的分布式锁就要清楚它的几个节点:
复制代码


  • 持久节点 :默认的节点类型,创建后,创建节点的客户端与 zookeeper 断开后,该节点依旧存在。


zk 获取锁过程


 当第一个客户端请求过来时,zookeeper客户端会创建一个持久节点locks。如果它(client1)想要获取锁,需要在locks下创建一个顺序临时节点lock1.当创建成功后,客户端client1 会查找locks下面所有的临时顺序节点,判断自己的节点是不是排序最小的一个,如果是则获取锁成功, 
这时当第二个客户端client2 来获取锁,创建了lock2,发现不是最小的临时顺序节点,于是获取锁失败,client2它会向排序靠前的节点lock1注册watcher事件,用于监听lock1是否存在,也就是说client2抢锁失败进入等待状态。
复制代码


释放锁:


1、当完成整个业务时手动删除临时节点释放锁


2、出现故障时,客户端与 zk 断开,根据临时节点特性也会删除该节点释放锁


zk 分布式锁的羊群效应


在client1 获取锁lock1之后,当有其他的客户端来获取锁时失败,然后所有的客户端都监听lock1的watcher事件,当lock1释放锁之时,所有的额客户端都去尝试索取锁,虽然最终只有一个获取到锁,但是大量的请求,以及创建节点的操作造成了网络的开销以及服务器的压力影响zk的性能。
解决方案:每个节点都只是监控比自己小的第一个节点。
复制代码


用户头像

还未添加个人签名 2020.06.05 加入

还未添加个人简介

评论

发布
暂无评论
分布式锁_分布式锁_InfoQ_48703db93d9b_InfoQ写作社区