实现的原理
很多客户端想要锁住一个资源,此时没办法使用正常的锁(synchronize或者Lock),这两个只适用于单机器。
多个不同的机器。只能用分布式锁。通过redis获得锁的客户端,可以访问资源,没获得redis锁的等待。
使用最简单的redis的get 和set 命令。set成功代表获得了分布式锁。
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。
获得锁
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
使用set resource-name clientid NX EX max-lock-time
释放锁
释放锁可以用del命令,但是不安全。
更好的解决方案是使用:EVAL script numkeys key [key ...] arg [arg ...]
使用eval命令 嵌入一个lua的脚本,是为了实现查找+删除的原子性
Lua脚本 必须是纯函数命令
eval "return redis.call('set',KEYS[1],'bar')" 1 foo
要求使用正确的形式来传递键(key)是有原因的,因为不仅仅是 EVAL 这个命令,所有的 Redis命令,在执行之前都会被分析,籍此来确定命令会对哪些键进行操作。因此,对于 EVAL命令来说,必须使用正确的形式来传递键,才能确保分析工作正确地执行。除此之外,使用正确的形式来传递键还有很多其他好处,它的一个特别重要的用途就是确保 Redis 集群可以将你的请求发送到正确的集群节点。(对 Redis 集群的工作还在进行当中,但是脚本功能被设计成可以与集群功能保持兼容。)不过,这条规矩并不是强制性的,从而使得用户有机会滥用(abuse) Redis 单实例配置(single instance configuration),代价是这样写出的脚本不能被 Redis 集群所兼容。
EVALSHA 命令的表现如下:
如果服务器还记得给定的 SHA1 校验和所指定的脚本,那么执行这个脚本 如果服务器不记得给定的 SHA1 校验和所指定的脚本,那么它返回一个特殊的错误,提醒用户使用 EVAL 代替 EVALSHA 以下是示例:
set foo bar OK
eval "return redis.call('get','foo')" 0 "bar"
evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0 "bar"
evalsha ffffffffffffffffffffffffffffffffffffffff 0 (error)
NOSCRIPTNo matching script. Please use EVAL. 客户端库的底层实现可以一直乐观地使用 EVALSHA 来代替 EVAL ,并期望着要使用的脚本已经保存在服务器上了,只有当 NOSCRIPT 错误发生时,才使用 EVAL 命令重新发送脚本,这样就可以最大限度地节省带宽。
这也说明了执行 EVAL 命令时,使用正确的格式来传递键名参数和附加参数的重要性:因为如果将参数硬写在脚本中,那么每次当参数改变的时候,都要重新发送脚本,即使脚本的主体并没有改变,相反,通过使用正确的格式来传递键名参数和附加参数,就可以在脚本主体不变的情况下,直接使用 EVALSHA 命令对脚本进行复用,免去了无谓的带宽消耗。
Redis 保证所有被运行过的脚本都会被永久保存在脚本缓存当中,这意味着,当 EVAL 命令在一个 Redis 实例上成功执行某个脚本之后,随后针对这个脚本的所有 EVALSHA 命令都会成功执行。
SCRIPT 命令
- SCRIPT FLUSH :清除所有脚本缓存
- SCRIPT EXISTS :根据给定的脚本校验和,检查指定的脚本是否存在于脚本缓存
- SCRIPT LOAD :将一个脚本装入脚本缓存,但并不立即运行它
- SCRIPT KILL :杀死当前正在运行的脚本
sandbox
通过config get lua-time-limit 命令查到,脚本最大执行时间默认为5秒。可以通过 config set 或者修改redis.config文件来设置。
超时处理lua-time-limit
当一个脚本达到最大执行时间的时候,它并不会自动被 Redis 结束,因为 Redis 必须保证脚本执行的原子性,而中途停止脚本的运行意味着可能会留下未处理完的数据在数据集(data set)里面。
因此,当脚本运行的时间超过最大执行时间后,以下动作会被执行:
Redis 记录一个脚本正在超时运行 Redis 开始重新接受其他客户端的命令请求,但是只有 SCRIPT KILL 和 SHUTDOWN NOSAVE 两个命令会被处理,对于其他命令请求, Redis 服务器只是简单地返回 BUSY 错误。 可以使用 SCRIPT KILL 命令将一个仅执行只读命令的脚本杀死,因为只读命令并不修改数据,因此杀死这个脚本并不破坏数据的完整性 如果脚本已经执行过写命令,那么唯一允许执行的操作就是 SHUTDOWN NOSAVE ,它通过停止服务器来阻止当前数据集写入磁盘