手写一个php api框架[13] -- 分布式锁

79 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 17 天,点击查看活动详情

分布式锁是干什么的

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

分析

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

实现方式

常见的有以下方式:

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

具体实现

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

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

image

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

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

  • Redis的setnx方法
  • php的 register_shutdown_function 方法
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

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 之前程序异常了怎么办?
  • 你还有其他的实现方式吗?

不妨在评论区分享给大家。