一、定义
在分布式环境下,实现多线程分布式互斥的一种锁机制。说明白了就是多个线程对于临界资源,我们需要一种手段保证一个线程去访问共享资源
二、实现方式
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节点获取锁后, 客户端才能获取锁。
注意:获取锁时间+时钟漂移,避免释放