首先要清楚,分布式锁的实现主要需要涉及到那几点,这里无非就是加锁和解锁。
后面要谈的就是怎么来实现,这其中又涉及到了redis的原子性以及使用lua脚本来操作的方法,进一步比较了lua脚本与redis事务的优缺点
最后谈到了php中如果调用lua脚本实现的问题
分布式锁的特点(也即说明吧)
分布式锁一般有如下的特点:
- 互斥性: 同一时刻只能有一个线程持有锁
- 可重入性: 同一节点上的同一个线程如果获取了锁之后能够再次获取锁
- 锁超时:和J.U.C中的锁一样支持锁超时,防止死锁
- 高性能和高可用: 加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效
- 具备阻塞和非阻塞性:能够及时从阻塞状态中被唤醒
分布式锁的实现方式
我们一般实现分布式锁有以下几种方式:
- 基于数据库
- 基于Redis
- 基于zookeeper
本篇文章主要介绍基于Redis如何实现分布式锁
1.利用redis实现分布式锁:【分布式缓存系列】Redis实现分布式锁的正确姿势(实现看这篇就好了)
主要点是:要使用Redis实现分布式锁。加锁操作的正确姿势为:
- 使用setnx命令保证互斥性
- 需要设置锁的过期时间,避免死锁
- setnx和设置过期时间需要保持原子性,避免在设置setnx成功之后在设置过期时间客户端崩溃导致死锁
- 加锁的Value 值为一个唯一标示。可以采用UUID作为唯一标示。加锁成功后需要把唯一标示返回给客户端来用来客户端进行解锁操作
解锁的正确姿势为:
1. 需要拿加锁成功的唯一标示要进行解锁,从而保证加锁和解锁的是同一个客户端
2. 解锁操作需要比较唯一标示是否相等,相等再执行删除操作。这2个操作可以采用Lua脚本方式使2个命令的原子性。
加锁通过setnx(即把是否存在以及设置过期时间统一通过setnx这个原子性来完成);释放锁因为涉及到要判断是否是同一个客户端,所以还有先判断再释放,这个需要使用lua的原子性来进行操作
redis的事务与lua脚本的对比
一.原理
1.redis事务
基本原理为乐观锁,多个client对操作的key进行watch,一旦有一个client进行了exec,那么其它client的exec就会失效。其实现原理可参考 Redis watch机制的分析。
2.lua脚本
基本原理为使脚本相当于一个redis命令,可以结合redis原有命令,自定义脚本逻辑。
使用Lua脚本的好处:
- 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。
- 原子操作。redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
- 复用。客户端发送的脚本会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。
3.两者异同
相同点
很好的实现了一致性、隔离性和持久性,但没有实现原子性,无论是redis事务,还是lua脚本,如果执行期间出现运行错误,之前的执行过的命令是不会回滚的。
不同点
(1)redis事务是基于乐观锁,lua脚本是基于redis的单线程执行命令。
(2)redis事务的执行原理就是一次 命令的批量执行,而lua脚本可以加入自定义逻辑。
个人总结:其实事务的操作实际上是利用了事务+watch实现原子操作,但是不免有点太僵硬,很明显这是一个if……else语句,为什么这么说呢,因为redis的事务是一种乐观锁的实现,事务A在进行操作key1时,然后执行exec,当事务B也在操作key2时,发现key2被exec了,那么事务B此时执行失败,不执行相关事务。
其他:
另外,php中使用lua的用法:
$redis->eval($lua, ['key1','key2','first','second'], 2)
在redis的lua解释器里面对应的执行的命令如下:
eval “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 2 key1 key2 first second
解释: “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 是被求值的 Lua 脚本,数字 2 指定了键名参数的数量, key1 和 key2 是键名参数,分别使用 KEYS[1] 和 KEYS[2] 访问,而最后的 first 和 second 则是附加参数,可以通过 ARGV[1] 和 ARGV[2] 访问它们。
PHP中使用redis拓展执行脚本时,eval方法的参数 3个,第一个是脚本代码,第二个是一个数组,参数数组,第三个参数是个整数,表示第二个参数中的前几个键名参数,剩下的都是附加参数
记住一点,lua总共就四个参数,非常简单
<1> script: lua脚本,如果是文本路径,需要加file_get_contens()来获取
<2> numkeys: key的个数
<3> key: redis中各种数据结构的替代符号
<4> arg: 你的自定义参数
参考: