应用场景一个应用被部署到多个机器上做负载均衡。为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程(进程)执行,使其对这类资源由并行变为串行执行
实现方式
基于 Redis 实现,主要基于 redis 的 setnx(set if not exist)命令;
基于 Zookeeper 实现;
基于 version 字段实现,乐观锁,两个线程可以同时读取到原有的 version 值,但是最终只有一个可以完成操作
需要注意点
在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
获取锁与释放锁的高可用及高性能;
具备非阻塞锁特性,获取不到锁将直接返回获取锁失败;
具备锁失效机制,防止死锁。
基于乐观锁的实现
具体的做法是,给数据表添加一个字段 version,当读取数据的时候将此 version 的值一并读出,当我们提交更新的时候,判断当前的 version 和取出的 version 的值是否相同,如果相同,则进行更新,否则认为已经有其它线程(进程)更新
version 的取值一般为版本号,或者时间戳
具体的做法
//进行查询
select id,version from articles where id=#{id}
//更新
update articles where id=#{id} and version= select (status,status,version) from t_goods where id=#{id};
这种做法也存在一定的缺点,例如在读少写多的场景下,如果客户端更新失败,业务逻辑层就要添加错误重试的代码
实现方式
基于 redis 的实现,伪代码如下
<?php
use Redis;
class Lock
{
private $lockKey;
private $expire;
//锁的惟一标识
private $ident;
public function __construct($lockKey, $expire)
{
$this->expire = $expire
$this->lockKey = $lockKey;
}
public function lock()
{
$value = uniqid();
$res = Redis::set($this->lockKey, $value, ['NX', 'EX' => $this->expire]);
if ($res) {
$this->ident = $value;
return true;
}
return false;
}
public function unlock()
{
//未获取锁
if (!$this->ident) {
return false;
}
$lua_script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
$res = Redis::eval($lua_script,[$this->lockKey, $this->ident], 1);
if ($res) {
return true;
}
return false;
}
}