redis是php的好敌人,在php写业务过程中,有时候会应用到锁的概念,同时只能有一个人能够操作某个行为。这个时候咱们就要用到锁。锁的形式有好几种,php不能在内存中用锁,不能应用zookeeper加锁,应用数据库做锁又耗费比拟大,这个时候咱们个别会选用redis做锁机制。
setnx
锁在redis中最简略的数据结构就是string。最早的时候,上锁的操作个别应用setnx,这个命令是当:lock不存在的时候set一个val,或者你还会记得应用expire来减少锁的过期,解锁操作就是应用del命令,伪代码如下:
1if (Redis::setnx("my:lock", 1)) { 2 Redis::expire("my:lock", 10); 3 // ... do something 4 5 Redis::del("my:lock") 6}
这里其实是有问题的,问题就在于setnx和expire两头如果遇到crash等行为,可能这个lock就不会被开释了。于是进一步的优化计划可能是在lock中存储timestamp。判断timestamp的长短。
set
当初官网倡议间接应用set来实现锁。咱们能够应用set命令来代替setnx,就是上面这个样子
1if (Redis::set("my:lock", 1, "nx", "ex", 10)) { 2 ... do something 3 4 Redis::del("my:lock") 5}
下面的代码把my:lock设置为1,当且仅当这个lock不存在的时候,设置实现之后设置过期工夫为10。
获取锁的机制是对了,然而删除锁的机制间接应用del是不对的。因为有可能导致误删他人的锁的状况。
比方,这个锁我上了10s,然而我解决的工夫比10s更长,到了10s,这个锁主动过期了,被他人取走了,并且对它从新上锁了。那么这个时候,我再调用Redis::del就是删除他人建设的锁了。
官网对解锁的命令也有倡议,倡议应用lua脚本,先进行get,再进行del
程序变成:
1$token = rand(1, 100000); 2 3function lock() { 4 return Redis::set("my:lock", $token, "nx", "ex", 10); 5} 6 7function unlock() { 8 $script = ` 9if redis.call("get",KEYS[1]) == ARGV[1] 10then 11 return redis.call("del",KEYS[1]) 12else 13 return 0 14end 15 ` 16 return Redis::eval($script, "my:lock", $token) 17} 18 19if (lock()) { 20 // do something 21 22 unlock(); 23}
这里的token是一个随机数,当lock的时候,往redis的my:lock中存的是这个token,unlock的时候,先get一下lock中的token,如果和我要删除的token是统一的,阐明这个锁是之前我set的,否则的话,阐明这个锁曾经过期,是他人set的,我就不应该对它进行任何操作。
所以:不要再应用setnx,间接应用set进行锁实现。