分布式锁专题

一、定义

在分布式环境下,实现多线程分布式互斥的一种锁机制。说明白了就是多个线程对于临界资源,我们需要一种手段保证一个线程去访问共享资源

二、实现方式

1.数据库

、乐观锁和悲观锁。
1.1、基于表记录
最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们想要获得锁的时候,就可以在该表中增加一条记录,想要释放锁的时候就删除这条记录。

缺点:
这种锁没有失效时间,一旦释放锁的操作失败就会导致锁记录一直在数据库中,其它线程无法获得锁。这个缺陷也很好解决,比如可以做一个定时任务去定时清理。
这种锁的可靠性依赖于数据库。建议设置备库,避免单点,进一步提高可靠性。
这种锁是非阻塞的,因为插入数据失败之后会直接报错,想要获得锁就需要再次操作。如果需要阻塞式的,可以弄个for循环、while循环之类的,直至INSERT成功再返回。

1.2、乐观锁
乐观锁大多数是基于数据版本(version)的记录机制实现的。何谓数据版本号?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个 “version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。在更新过程中,会对版本号进行比较,如果是一致的,没有发生改变,则会成功执行本次操作;如果版本号不一致,则会更新失败。

1.3、悲观锁
除了可以通过增删操作数据库表中的记录以外,我们还可以借助数据库中自带的锁来实现分布式锁。在查询语句后面增加FOR UPDATE,数据库会在查询过程中给数据库表增加悲观锁,也称排他锁。当某条记录被加上悲观锁之后,其它线程也就无法再改行上增加悲观锁。

缺点:对数据库压力大,处理机制不灵活

2.zk

持久化节点(zk断开节点还在)
持久化顺序编号目录节点
临时目录节点(客户端断开后节点就删除了)
临时目录编号目录节点

实现:zk就是基于临时顺序节点去实现各种分布式锁的。因为zk节点是唯一性的,创建临时顺序节点,顺序保证不是舰艇所有。
1)每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能,Zk性能上可能并没有缓存服务那么高。
2)由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。

从锁的粒度和并发,选择redis。

3.实现分布式锁

实现方式
1、setnx+lua脚本

考虑锁续期:
当加锁成功后,同时开启守护线程,默认有效期是30秒,每隔10秒就会给锁续期到30秒,只要持有锁的客户端没有宕机,就能保证一直持有锁,直到业务代码执行完毕由客户端自己解锁,如果宕机了自然就在有效期失效后自动解锁。

2、使用reddison
redisson内部就像reentanlock一样,提供getlock和unlock的api供我们去调用,而这里的设置的key是业务名字,val存放的是一个线程id+uuid,保证每一次只能由当前线程获取释放锁;redission的底层通过hash获取到redis集群的一个节点,也是用lua去实现锁,而且是可重入锁。
另外,还有一个锁续期的轮询机制,默认是30s,每隔10s会进行一次续期;防止业务时间执行大于过期时间

追问:可重入锁意思是在外层使用锁之后,内层仍然可以使用,那么可重入锁的实现思路又是怎么样的呢?

使用Redis的哈希表存储可重入次数,当加锁成功后,使用hset命令,value(重入次数)则是1。

"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; "

释放锁时候,就会进行 -1

追问,锁续期
回答看门狗原理

追问:获取锁失败,一直会尝试加锁,耗性能,如何优化?
这个我们用redis的发布订阅模式来优化,也就说把:
获取锁失败订阅锁释放的信息;
当持有客户端释放锁的时候,发布释放锁的消息,进入阻塞状态;
当持有锁的客户端释放锁时候,发布时锁释放的消息;
进入阻塞状态的,就可以解除阻塞等待。

追问:时间轮算法?
定时器一般有两种方式:阻塞线程,线程消耗大;
另一种任务队列定期扫描;消耗cpu性能;

衍生出:时间轮算法
结构:循环链表数据结构;
原理:我们假设指针没跳动一下需要10秒,然后现在我们有一个50秒后执行的任务A,由此推断在当前指针指向2的时候,任务A会被存放在槽格7中。当指针跳动到7后取出槽格中的任务队列,此时任务A将会被执行。但是当如果有100秒后的任务需要执行,依然超出了槽格数量是该怎么办呢?很简单,我们为任务实例添加一个属性“圈数”就可以解决。就是说每次装载任务时算出此任务需要指针转动几圈才能够被执行。

追问:主从还没同步,主节点挂掉,如何保证锁不受影响?
引入redlock算法,要满足n/2+1节点获取锁后, 客户端才能获取锁。

注意:获取锁时间+时钟漂移,避免释放

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值