锁在我们的日常开发可谓用得比较多。通常用来解决资源并发的问题。特别是多机集群情况下,资源争抢的问题。但是,很多新手在锁的处理上常常会犯一些问题。今天我们来深入理解锁。

一、Redis 锁错误使用之一
我曾经见过有的项目把查询结果存储到 Redis 当中时的伪代码如下:

$redis    = new \Redis('127.0.0.1', 6379);
$cacheKey = 'query_cache';
$result   = $redis->get($cacheKey);
if ($result) { // 缓存有效则直接返回。
    return $result;
} else { // 缓存失效则重新获取并存储到 Redis。
    $mysqlResult = []; 
    $redis->set($cacheKey, json_encode($mysqlResult), 3600);
    return $mysqlResult;
}

初看代码并不会发现问题所在。通常情况下,当服务器资源压力非常小的时候,这段代码不会有任何问题。并且,真的可以提升服务器吞吐性能。

假如,这个位置的代码出现了单点压力呢?比如,这个功能是统计结果,查询数据库需要花 5s。而且,由于该功能比较常用,单位时间内达到了 1000 次/秒。

这时就会出现并发穿透问题。

1000 个请求同时到达这个程序位置,都去读取缓存是否存在。假如此时缓存不存在。这 1000 个请求都会得到不存在的结果。并且都会执行到去数据库取缓存结果的步骤。同时也会把结果重写到 Redis。

那就导致了这一瞬间单点压力导致穿透到数据库,造成数据库压力瞬间到达峰值。如果我们的数据库的性能处理不了这么大的压力,就会导致数据库服务器 CPU 直接爆满。响应给前端的数据就会陷入停顿状态。

所以,这段代码是不正确的锁使用。

二、Redis 锁错误使用之二
在第一点中,我们发现了问题。于是,就有人想着去优化它。于是就有了下面的代码:

$redis    = new \Redis('127.0.0.1', 6379);
$lockKey  = 'query_cache_lock'; // 锁专用的 KEY。
$cacheKey = 'query_cache'; // 存储查询结果的 KEY。
$result   = $redis->get($cacheKey);
if ($result) { // 缓存有效则直接返回。
    return $result;
} else { // 缓存失效则重新获取并存储到 Redis。
    if ($redis->setNx($lockKey) === false) {
        throw new \Exception("服务器火爆,请稍候重试");
    } else {
        $mysqlResult = []; 
        $redis->set($cacheKey, json_encode($mysqlResult), 3600);
        $redis->delete($lockKey); // 锁用完了要解锁。删掉就是解锁。
        return $mysqlResult;
    }
}

这段代码就完全避免了第一点中的并发穿透的问题。但是,相对第一点,代码也多增加了几行。不过性能依然强劲。

即使如此,这段代码依然存在三个问题:
1)并发越大,第一个取到锁的请求能正常响应,后续的请求就会得到一个“服务器火爆,请稍候重试”的异常提示。
2)没办法对后续请求取锁失效加一个等待时间。
3)如果代码执行到 $redis->delete($lockKey) 之前程序异常了。那么锁就不能正常释放。后续的锁也无法正常取到锁了。

针对第 1) 点,这个是用户体验极差的。
针对第 2) 点,它是解决第一点的方案。
针对第 3) 点,它是我们必须解决的问题。否则,我们的分布式锁将无法正常使用。

三、正确的分布式锁
正常的分布式锁要满足以下几点要求:
1)能解决并发时资源争抢。这是最核心的需求。
2)锁能正常添加与释放。不能出现死锁。
3)锁能实现等待,否则不能最大保证用户的体验。

针对以上三点,得出 Redis 分布式锁示例

class RedisMutexLock
{
    /**
     * 缓存 Redis 连接。
     *
     * @return void
     */
    public static function getRedis()
    {
        // 这行代码请根据自己项目替换为自己的获取 Redis 连接。
        return YCache::getRedisClient();
    }
 
    /**
     * 获得锁,如果锁被占用,阻塞,直到获得锁或者超时。
     * -- 1、如果 $timeout 参数为 0,则立即返回锁。
     * -- 2、建议 timeout 设置为 0,避免 redis 因为阻塞导致性能下降。请根据实际需求进行设置。
     *
     * @param  string  $key         缓存KEY。
     * @param  int     $timeout     取锁超时时间。单位(秒)。等于0,如果当前锁被占用,则立即返回失败。如果大于0,则反复尝试获取锁直到达到该超时时间。
     * @param  int     $lockSecond  锁定时间。单位(秒)。
     * @param  int     $sleep       取锁间隔时间。单位(微秒)。当锁为占用状态时。每隔多久尝试去取锁。默认 0.1 秒一次取锁。
     * @return bool 成功:true、失败:false
     */
    public static function lock($key, $timeout = 0, $lockSecond = 20, $sleep = 100000)
    {
        if (strlen($key) === 0) {
            // 项目抛异常方法
            YCore::exception(500, '缓存KEY没有设置');
        }
        $start = self::getMicroTime();
        $redis = self::getRedis();
        do {
            // [1] 锁的 KEY 不存在时设置其值并把过期时间设置为指定的时间。锁的值并不重要。重要的是利用 Redis 的特性。
            $acquired = $redis->set("Lock:{$key}", 1, ['NX', 'EX' => $lockSecond]);
            if ($acquired) {
                break;
            }
            if ($timeout === 0) {
                break;
            }
            usleep($sleep);
        } while (!is_numeric($timeout) || (self::getMicroTime()) < ($start + ($timeout * 1000000)));
        return $acquired ? true : false;
    }
 
    /**
     * 释放锁
     *
     * @param  mixed  $key  被加锁的KEY。
     * @return void
     */
    public static function release($key)
    {
        if (strlen($key) === 0) {
            // 项目抛异常方法
            YCore::exception(500, '缓存KEY没有设置');
        }
        $redis = self::getRedis();
        $redis->del("Lock:{$key}");
    }
 
    /**
     * 获取当前微秒。
     *
     * @return bigint
     */
    protected static function getMicroTime()
    {
        return bcmul(microtime(true), 1000000);
    }
}
以上是在项目中一些的用到的之处,大家可以更换为自己项目

进阶之路:想要了解更多的进阶优化知识点的话可以点击这里奥!

PHP实现Redis分布式锁的更多相关文章

  1. 利用redis分布式锁的功能来实现定时器的分布式

    文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...

  2. Redis分布式锁

    Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...

  3. redis分布式锁和消息队列

    最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令. 分布式锁 由于目前一些编程语言,如PHP ...

  4. redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  5. spring boot redis分布式锁

    随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...

  6. Redis分布式锁的正确实现方式

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  7. Redis分布式锁---完美实现

    这几天在做项目缓存时候,因为是分布式的所以需要加锁,就用到了Redis锁,正好从网上发现两篇非常棒的文章,来和大家分享一下. 第一篇是简单完美的实现,第二篇是用到的Redisson. Redis分布式 ...

  8. redis分布式锁实践

    分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...

  9. Redis分布式锁的try-with-resources实现

    Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...

  10. 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

随机推荐

  1. InnoDB Architecture (InnoDB In-Memory Structures 转载)

    转载.节选于 https://dev.mysql.com/doc/refman/8.0/en/innodb-in-memory-structures.html InnoDB Architecture ...

  2. 12c Data guard Switchover Best Practices using SQLPLUS (Doc ID 1578787.1)

    12c Data guard Switchover Best Practices using SQLPLUS (Doc ID 1578787.1) APPLIES TO: Oracle Databas ...

  3. Windows 10 神州网信版

    一.版本介绍:官网链接:http://document.cmgos.com/release_notes/release_notes_V0_H 下载链接:请自行百度Windows 10 神州网信政府版( ...

  4. leetcode-10

    给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配. '.' 匹配任意单个字符'*' 匹配零个或多个前面的那一个元素所谓匹配,是要涵盖 整个 字符串 s的 ...

  5. [译]Vulkan教程(03)开发环境

    [译]Vulkan教程(03)开发环境 这是我翻译(https://vulkan-tutorial.com)上的Vulkan教程的第3篇. In this chapter we'll set up y ...

  6. win10系统怎么设置软件开机启动

    win10开机自动启动软件设置教程: 1:在windows10桌面,右键点击桌面左下角的开始按钮,在弹出的菜单中选择运行菜单项. 2:这时就会打开windows10的运行窗口,在窗口中输入命令shel ...

  7. IT兄弟连 HTML5教程 CSS3揭秘 CSS3属性3

    5 用户界面属性 在CSS3中,新的用户界面特性包括重设元素尺寸.盒尺寸及轮廓等.本小节着重介绍一下resize属性,只有Firefox 4和Safari 3浏览器支持此属性.resize属性可用于重 ...

  8. cisco ssh实验--附带配置脚本-2019.11.19

    cisco ssh实验

  9. 5种智能指针指向数组的方法| 5 methods for c++ shared_ptr point to an array

    本文首发于个人博客https://kezunlin.me/post/b82753fc/,欢迎阅读最新内容! 5 methods for c++ shared_ptr point to an array ...

  10. maven私服的配置使用

    maven的仓库分为本地仓库,远程仓库和私服仓库. 私服仓库一般是公司内部私有的,内部进行维护的.公司员工连接私服,从私服中下载jar,或者将自身的jar传到私服上.私服还可以从中央仓库下载jar,当 ...