1. 概念
分布式锁是为了解决不同进程需要互斥地访问共享资源产生的。在分布式模式下,假如某份数据只有一份或者存在限制,那么我们就需要使用锁技术来控制某一时刻修改数据的进程数。
2. 实现方式
- 基于数据库实现分布式锁;如mysql排它锁。
- 基于缓存(Redis等)实现分布式锁; 本文介绍内容。
- 基于Zookeeper实现分布式锁。
3. 几个属性
- 安全属性:互斥,不管任何时候,只有一个客户端能持有同一个锁。
- 效率属性A:不会死锁,最终一定会得到锁,就算一个持有锁的客户端宕掉或者发生网络分区。
- 效率属性B:容错,只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。
- 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
4. 思路及实现
- redis分布式锁的实现主要基于redis的SETNX命令,若给定的 key 已经存在,SETNX 不做任何动作。否则将 key 的值设为 value,成功返回1,失败返回0。
- 使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功。
- 为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间。
- 释放锁,使用DEL命令将锁数据删除。
<?php
class RedisLockService
{
const LOCK_SUCCESS = 'OK';
const IF_NOT_EXISTS = 'NX';
const MILLISECOND_EXPIRE_TIME = 'EX';
const EXPIRE_TIME = 50;
/**
* @param $key
* @param $uuid
* @param $expire_time
* 加锁
*/
public function lock($key,$uuid,$expire_time = '')
{
if (empty($expire_time)) {
$expire_time = self::EXPIRE_TIME;
}
$res = Redis::set($key,$uuid,self::MILLISECOND_EXPIRE_TIME ,$expire_time,self::IF_NOT_EXISTS);
if ($res == self::LOCK_SUCCESS) {
return true;
} else {
return false;
}
}
/**
* @param $key
* @param $uuid
* 释放锁
*/
public function unlock($key,$uuid)
{
$lua =<<<LUA_SCRIPT
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
LUA_SCRIPT;
return Redis::eval($lua,1,$key,$uuid);
}
}
?>