原文连接 https://www.gwalker.cn/article-1482d08g8952e122a6044ab1c87bdd95.html
设计思路
任何一个请求进来时;先向redis申请"加锁操作"
若加锁成功则进行执行业务逻辑,执行完毕之后,释放锁;否则 不执行业务逻辑。
设计细节
1 加锁时设置过期时间;这样可保证运行中间出现异常,没有进行到锁删除操作。一定时间之后,锁会自动释放,防止死锁。
2 释放锁操作,判断锁是否是自己加的。若是自己加的,才可以进行释放操作。防止锁超时,释放的为其他请求生成的锁。
实现细节
1 因为php-redis扩展提代的方法为 public function set( $key, $value, $timeout = 0 ) {}
,
实现不了 SET key value [EX seconds] [PX milliseconds] [NX|XX]
这样的原生命令。所以只能使用$redis->rawCommand()方法执行,原生redis指令。
2 释放锁,原子性操作,需要判断锁为自己设定的,才可进行删除。redis没有提供这样的原子性操作命令。采取$redis->eval()发送lua脚本去实现。
// 分布式锁
$redis = new Redis();
$redis->connect('172.17.0.3', 6379);
// 锁名
$key = 'lock:xxx';
// 锁值
$tag = uniqid();
// 加锁 且锁的有效时间为5秒,NX(意为 NOT EXISTS)如果锁不存在,才可设置成功。
$re = $redis->rawCommand('set', $key, $tag, 'EX', 5, 'NX');
if(!$re){
die('系统繁忙,请稍后重试');
}
// do_something(); // 待执行的业务逻辑
// 释放锁
$luaScript =<<<SCRIPT
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
SCRIPT;
$redis->eval($luaScript,[$key,$tag],1);