1. 使用mysql中的排他锁
开启不自动提交事务
select xx for update //上锁
手动提交事务 //释放锁
一般不使用这种方式,因为如果数据库异常,数据库释放锁的时间会相对较长,且数据库压力会比较大。
当然也可以用其他排他锁,如update语句、insert语句等。
2. 使用redis
方法1.
setnx kkk vvv //加锁
expire kkk 10 //设置锁的超时时间,秒
del kkk //释放锁
上面是利用setnx命令只能设置一次,重复设置会失败的特点。
上面的写法有两个问题:
1. 如果第2行没来得及执行,可能就永远释放不了锁了;
2. 第3行的写法可能被别的客户端执行,这就是说别人可能释放自己的锁。
方法2. 加原子性
这是推荐的写法,解决上面方法的两个问题。
set kkk vvv ex 10 nx
用lua脚本里释放锁
第1行里,用一行代码执行加锁和设置超时时间,确保了这两个操作的原子性。
“用lua”脚本释放锁的时候,要先get判断锁是否是自己的,再del删除。为什么要用lua脚本?因为它有原子性。
方法3. 加看门狗
方法2还存在超时时间如何设置的问题:设置太短,有可能事情还没干完锁就自动释放了;设置越长锁持有时间就会越久,事情干完了还拿着半天锁。
解决办法就是加看门狗,增加一个后台线程,在锁快到期的时候去看看加锁的线程是不是还活着,如果活着,就帮它延一下时间。
项目中一般不自己写上面的redis相关的代码,而是直接用Redisson这个工具,它解决了上面的问题,也有看门狗。
3. 使用zookeeper(不推荐了)
使用zk的临时顺序节点来做分布式锁。
临时的意思是抢到锁的客户端如果没有正常释放锁(比如崩溃了、逻辑错误),zk自己会超时释放锁。
上锁流程为:
1.去zk创建节点,如果创建成功,判断节点是不是序号最小的,如果是,则抢锁成功,否则抢锁失败。
2.如果抢锁失败,就去监听比自己小的节点,如果比自己小的节点不在了,就算重新抢到锁了。
使用监听机制而非轮询机制,是因为轮询的效率太低,且系统开销较大。
事情做完后删除节点,代表释放锁。