老规矩福利一波!!!!!!!
面试过程中,你说你做过微服务,那么分布式事务锁的实现是必问套餐,看到一篇文章总结得比较好,思路很清晰,这里记录一下方便日后查阅和跟踪。
分布式锁三种实现方式:
1、基于数据库实现分布式锁;
2、基于缓存(Redis等)实现分布式锁;
3、基于Zookeeper实现分布式锁。
从性能角度(从高到低)来看:“缓存方式 > Zookeeper方式 >= 数据库方式”。
从可靠性角度(从高到低)来看:”Zookeeper > 缓存 > 数据库”。
下面基于这三种实现方式展开阐述。
基于数据库实现分布式锁
- 悲观锁
利用select … where … for update 排他锁
注意 :这里需要注意的是“where name=lock ”,name字段必须要走索引, 这里是开发者最容易忽视的点 ,否则会”锁表”。因为有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题。
- “乐观锁”
通过增加递增的版本号字段实现乐观锁。
所谓乐观锁与前边最大区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有 update version 失败后才能觉察到。我们的抢购、秒杀就是用了这种实现以防止超卖。
基于缓存(Redis等)实现分布式锁
通过setenx命令配合delete实现。
- 使用介绍
SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,返回0,则什么都不做。
expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
delete
delete key:删除key
- 实现思想
1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
- 死锁问题
考虑一种情况,如果进程获得锁后,断开了与 Redis 的连接(可能是进程挂掉,或者网络中断),如果没有有效地释放锁的机制,那么其他进程都会处于一直等待的状态,即出现“死锁”。
解决办法之一:设置 有效时间 的同时,在执行setnx时,把value值利用起来,设置为当前的时间,在每次获取锁失败后都判断一次当前时间和redis的缓存值时间,是否超过设置的超时时间,超过则删除此key,重新获取锁。
使用redis做分布式锁时,大家都知道setnx容易发生死锁情况,这里推荐使用 redission 来实现。
基于Zookeeper实现分布式锁
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己更小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端, Curator 提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。
总结
内容有参考,有自己的思考和总结,修行之路源于记录和不断地学习。