Last-Modified: 2019年6月5日15:59:34php
若是是Redis集群, 还得考虑具备容错性: 只要大部分Redis节点正常运行, 客户端就能够加锁和解锁.git
如下只考虑 Redis单机部署的 场景.github
若是是Redis集群部署, 可使用redis
php 加锁示例算法
$redis = new Redis(); $redis->pconnect("127.0.0.1", 6379); $redis->auth("password"); // 密码验证 $redis->select(1); // 选择所使用的数据库, 默认有16个 $key = "..."; $value = "..."; $expire = 3; // 参数解释 ↓ // $value 加锁的客户端请求标识, 必须保证在全部获取锁清秋的客户端里保持惟一, 知足上面的第3个条件: 加锁/解锁的是同一客户端 // "NX" 仅在key不存在时加锁, 知足条件1: 互斥型 // "EX" 设置锁过时时间, 知足条件2: 避免死锁 $redis->set($key, $value, ["NX", "EX" => $expire])
执行上面代码结果:数据库
加锁容易错误的点:缓存
setnx
和 expire
的组合缘由: 若在 setnx
后脚本崩溃会致使死锁服务器
$value
客户端标识的:分布式
在php中, 若使用 pconnect
链接redis, 则在当前脚本声明周期结束后, 与redis创建的链接仍会保留, 直到对应fpm进程的生命周期结束, 同时在下一次请求时, fpm会重用该链接.ide
即该链接的生命周期是 fpm 进程的生命周期, 而非一次php脚本的执行.
若代码使用 pconnect
, close
的做用仅是使当前php脚本不能再进行redis请求, 并无真正关闭与redis的链接, 链接在后续请求中仍然会被重用.
pconnect函数在线程版本中不能被使用
上图中, php-fpm 与redis创建的链接并未随请求结束后立刻断开
php解锁示例: 使用lua脚本
$key = "..."; $identification = "..."; // KEYS 和 ARGV 是lua脚本中的全局变量 $script = <<< EOF if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end EOF; # $result = $redis->eval($script, [$key, $identification], 1); // 返回结果 >0 表示解锁成功 // php中参数的传递顺序与标准不同, 注意区分 // 第2个参数表示传入的 KEYS 和 ARGV, 经过第3个参数来区分, KEYS 在前, ARGV 在后 // 第3个参数表示传入的 KEYS 的个数 $result = $redis->evaluate($script, [$key, $identification], 1);
使用Lua脚本的缘由:
避免误删其余客户端加的锁
eg. 某个客户端获取锁后作其余操做太久致使锁被自动释放, 这时候要避免这个客户端删除已经被其余客户端获取的锁, 这就用到了锁的标识.
get
和 del
是原子性的, 整个lua脚本会被当作一条命令来执行get
后锁恰好过时, 此时也不会被其余客户端加锁eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,而且直到eval命令执行完成,Redis才会执行其余命令。因为 script 执行的原子性, 因此不要在script中执行过长开销的程序,不然会验证影响其它请求的执行。
解锁容易错误的点:
del
删除键缘由: 可能移除掉其余客户端加的锁(在本身的锁已过时状况下)
get
判断锁归属, 若符合再 del
缘由: 非原子性操做, 若在 get
后锁过时了, 此时别的客户端进行加锁操做, 这里的 del
就会错误的将其余客户端加的锁解开.
↓ 这一段内容转载自 https://blog.csdn.net/zhouzme...
注意点:
义变量必定要使用局部变量, 即 local var = 1
, 局部变量只在所定义的块(指控制结构, 函数或chunk等)内有效, 使用局部变量能够避免命名冲突 而且访问更快(lua中局部变量和全局变量存储方式是不同的)
若是Lua脚本写的比较长,非本地或局域网的状况下,建议使用 SHA 签名的方法来调用,这样节省带宽,但对性能彷佛没什么直接的提高。这里对小白普及下我理解的原理就是 Redis 会把每一个脚本都生成惟一签名,把脚本做为函数体,并使用该签名做为脚本的函数名放到缓存中,因此后面调用就只须要传一个 SHA 签名就能够调用该函数了,精简不少了。同一个脚本生成的签名都是相同的,因此SHA签名能够先在本地生成,而后在服务器上 script load 一次脚本,程序中只需保存和使用该签名便可。另外须要注意的是,脚本若是被改动哪怕一个换行或一个空格(这些容易被忽略或误操做)都必须从新 load 来获取新的 SHA
注意:获取 SHA 签名是单独的功能,不要放在你的正常流程中,当本地开发时就能够生成SHA,把字符串写死在流程中。一样的脚本,Reids是始终生成相同的签名的。
tonumber()
方法 if (tonumber(ARGV[1]) > 0) then return 1; end;
Redis 集群相对单机来讲, 须要考虑一个 容错性, 设计上更为复杂
因为这个我也从未实践过, 先贴一个官方的教程贴压压惊
https://github.com/antirez/re...
对应的翻译: http://ifeve.com/redis-lock/
官方给出了一个 RedLock 算法
情景: 当前有N个彻底独立的Redis master节点, 分别部署在不一样的主机上
客户端获取锁的操做: