Last-Modified:June5,201915:59:34参考链接PHP使用Redis+Lua脚本操作注意事项《Redis官方文档》Redis构建分布式锁的注意点点互斥:at任何时候,只有一个客户端可以获取锁,不会死锁:客户端在持有锁时崩溃,并没有主动释放锁,可以保证其他后续客户端获取到锁。锁归属地:lock和unlock必须是同一个client,client不能解锁非client持有的锁(锁应该有标识)。如果是Redis集群,还必须考虑容错性:只要大部分Redis节点正常运行,客户端就可以加Lock和解锁。下面只考虑Redis单机部署的场景。如果是Redis集群部署,可以使用lockphplockexample$redis=newRedis();$redis->pconnect("127.0.0.1",6379);$redis->auth("密码");//密码认证$redis->select(1);//选择使用的数据库,默认有16个$key="...";$value="...";$expire=3;//参数说明↓//$value锁定客户端请求标识,必须保证在所有获取锁的client中是唯一的,并且满足上面第三个条件:lock/Unlock是同一个client//"NX"只在key不存在的时候才加锁,满足条件1:互斥//"EX"设置锁过期时间,满足条件2:避免死锁$redis->set($key,$value,["NX","EX"=>$expire])执行以上代码结果:$对应的锁key不存在,执行加锁操作$key对应的锁已经存在,不做任何操作加锁的易错点:使用setnx和expire的组合原因:如果setnx后脚本崩溃,会导致死锁$valueClientID:只需使用毫秒级别的unix时间戳+客户端ID(大多数情况下就足够了)使用其他算法来确保生成唯一的随机值。connect和pconnect在php中,如果使用pconnect连接redis,当前脚本语句循环结束后,会一直保持与redis的连接,直到对应的fpm进程生命周期结束,在下一次请求时,fpm将重用该连接。即连接的生命周期是fpm进程的生命周期,而不是一个php脚本的执行。如果代码中使用了pconnect,close的作用只是让当前的php脚本不再进行redis请求,并没有真正关闭与redis的连接,在后续的请求中仍然会重用该连接。pconnect函数不能在线程版本中使用。上图中php-fpm与redis建立的连接在请求结束后并没有立即断开unlockphpunlock示例:使用lua脚本$key="...";$identification="...";//KEYS和ARGV是lua脚本中的全局变量$script=<<eval($script,[$key,$identification],1);//返回结果>0表示解锁成功//参数在php中的传递顺序为与标准不同,注意区分//第二个参数表示传入的KEYS和ARGV,通过第三个参数来区分,KEYS在前,ARGV在后//第三个参数表示传入的KEYS个数$result=$redis->evaluate($script,[$key,$identification],1);使用Lua脚本的原因:避免误删其他客户端加的锁。例如。客户端获取锁并执行其他操作的时间过长,锁被自动释放。这时候就需要防止客户端删除已经被其他客户端获取的锁。这是用到的Lock标识。lua脚本中get和del的执行是原子的,整个lua脚本会作为一条命令执行。即使锁刚好在get之后过期,此时也不会被其他客户端加锁。eval命令执行Lua代码届时,Lua代码会作为一条命令执行,直到eval命令执行完毕,Redis才会执行其他命令。由于脚本执行的原子性,不要在脚本中执行过长的开销程序,否则校验会影响其他请求。执行。解锁易错点:直接deldeletekey原因:有可能移除其他客户端加的锁(当自己的锁过期时)get判断锁的归属,满足则del原因:非原子操作,如果在get之后,锁就过期了。这时其他客户端进行加锁操作,这里的del会误解锁其他客户端加的锁。Redis中使用Lua脚本的注意事项↓本节转载自https://blog.csdn.net/zhouzme...注:Redis会将所有执行过的脚本缓存在内存中。当Redis重启时,它会释放之前保存的脚本。Lua脚本中需要的键名和参数必须用KEYS和ARGV代替。不要把它们写在代码中,除非你100%确定它们是每个请求的固定值,尤其是涉及到时间和随机数的时候。使用参数来替代,因为Redis每次使用脚本都会检查脚本缓存中是否已经存在相同的脚本,否则会存入缓存。如果你的脚本很长,每次请求中都有不同的变量值,就会产生无数的脚本缓存,你会发现Redis占用的内存会快速增加。一开始因为key和参数太多,分开写太麻烦,所以直接把变量拼接在脚本里,省事方便,原来是内存一直在往上升,疯了,找了半天才发现是这个原因。定义的变量必须使用局部变量,即localvar=1,局部变量只在定义的块(指控制结构、函数或块等)内有效,使用局部变量可以避免命名冲突,访问更快(locallua中变量和全局变量的存储方式不同)如果长期写lua脚本,建议在非本地或者局域网的情况下使用SHA签名方式调用,这样可以节省带宽,但是确实似乎不能直接提高性能。我这里从小白的普及中了解到的原理是,Redis会为每一个脚本生成一个唯一的签名,将脚本作为函数体,将签名作为脚本的函数名放入缓存,这样你只需要通过一个SHA签名就可以调用这个函数,这样就精简了很多。同一个脚本生成的签名是一样的,所以可以先在本地生成SHA签名,然后在服务器端加载一次脚本,程序只需要保存并使用签名即可。另外需要注意的是,如果改了脚本,即使换行或者空格(这些很容易被忽略或者误操作)也必须重新加载才能获取新的SHA注意:获取SHA签名是一个单独的函数,做不要放在你正常的进程中,本地开发的时候可以生成SHA,把字符串硬编码在进程中。相同的脚本,Reids总是生成相同的签名。如果eval带进来的ARGV参数是数字,会转成字符串。如果你的逻辑需要判断变量>0或者<0,你必须把字符串转成数字。使用tonumber()方法if(tonumber(ARGV[1])>0)thenreturn1;结尾;测试了几个lua脚本,和PIPELINE对比,发现脚本效率普遍比PIPELINE高30%到40%RedisCluster和单机相比,分布式锁Redis集群需要考虑容错性,而且设计比较复杂。由于从来没有实践过这个,所以先发个官方教程https://github.com/antirez/re...对应翻译:http://ifeve.com/redis-lock/RedLockAlgorithm官方给出了一个RedLockAlgorithm场景:当前有N个完全独立的Redis主节点部署在不同的主机上获取锁的操作:使用相同的key和唯一值(asvalue)同时向N个redis节点请求锁。锁的超时时间应该是>>超时时间(考虑到请求的耗时),如果有节点阻塞应用,尽快跳过计算步骤1所消耗的时间。如果总消费时间超过超时时间,则认为锁失效。客户端必须成功获取到大部分(超过一半)的节点上的锁才能认为锁成功。如果加锁成功,则加锁的有效时间为原来的加锁有效时间-第1步消耗的时间。如果加锁失败(超时或无法获取N/2+1实例的一半以上)锁),客户端会去每个节点释放锁(每个,甚至是之前认为锁失败的节点)