系列文章---应用篇:1.分布式锁


含义

为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。

分析

  • 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
  • 高可用的获取锁与释放锁
  • 高性能的获取锁与释放锁
  • 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
  • 具备锁失效机制,防止死锁
  • 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败

实现方式

常见的有:

  • 基于Redis
  • 基于mysql
  • 基于Zookeeper

具体实现

考虑到我们使用的PHP语言,简单结合Redis来实现一下分布式锁。

我们来画个简单的原理图:

image

原理十分简单,那么我们如何实现呢?

这里我们用到两个知识点:

  • Redis的setnx方法
  • php的register
1
2
3
4
5
6
7
8
9
10
11
12
13
function processLock($redis, $key, $ttl = 5)
{
$ret = $redis->setnx($key, 1);
if ($ret) {
$redis->expire($key, $ttl);

register_shutdown_function(function () use ($redis, $key){
$redis->del($key);
});
}

return $ret;
}

demo里的$redis是一个redis实例,我们在前面已经实现过,不多说明。

加锁

我们使用的setnx命令,senx表示“SET if Not eXists”,如果key不存在,则set,如果存在,则不set

1
2
3
4
5
6
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"

解锁

使用的是del,即删除这个key。这里结合了 register_shutdown_function — 注册一个会在php中止时执行的函数,设置了之后我们就可以不必手动去解锁

锁超时

这里给锁加了一个expire过期时间5秒,目的是防止使用这个方法之后的执行了“神奇”代码抛出了语法错误,导致register_shutdown_function里的代码没有执行,虽然请求结束,但是加锁后,没有解锁,影响了后续请求。

问题

  • 加入setnx之后expire之前程序异常了怎么办?