PHP使用redis实现分布式锁的示例详解

作者:程序员零壹 时间:2023-06-01 16:32:19 

最近在做一个领券功能的时候,发现在一定并发下会出现重复领券的问题。使用度娘一顿搜索操作之后,发现可以使用分布式锁来解决这个问题。

什么是分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。

实现原理

实现分布式锁的原理很简单,就是需要有一把锁,多个服务同时去获取锁,但是只有一个服务能获取到锁。获取到锁的服务就可以执行自己的业务,没有获取到锁的其他服务需要等待获取到锁的服务业务执行完成后释放锁,然后再次尝试获取锁。

实现分布式的方案有很多种。如下

  • 基于数据库实现分布式锁,比如mysql

  • 基于缓存实现分布式锁,比如redis

  • 基于Zookeeper实现分布式锁

这里我们使用redis来实现分布式锁,在执行业务之前先获取一个key,如果key存在就说明已经有其他服务获得锁,这个时候需要等待或者返回系统繁忙。如果key不存在,说明没有其他服务获取锁,把这个key保存到redis,然后执行业务,等待业务执行完就从redis中删除这个key。

php实现代码

<?php

class RedisLock
{
   protected $redis;

   public function __construct(){
   
       $redis = new Redis();
       $redis->connect('127.0.0.1',6379);

       $this->redis = $redis;
   }
   public function getLock($key){
       $value = $this->redis->get($key);
       return $value;
   }

   public function setLock($key,$value){
       $this->redis->set($key,$value);
   }

   public function delLock($key){
       $lineNumber = $thid->redis->del($key);
       return $lineNumber;
   }
}

$key = 'your_lock_key';
$value = time();

$redisLock = new RedisLock();
$isLock = $redisLock->get($key);
if($isLock) {
   //已有锁,直接返回,不往下执行了
   return false;
}

//没有锁,加锁
$redisLock->setLock($key,$value);



//todo 执行业务逻辑
sleep(5);

// 解锁
$redisLock->delLock($key);

使用ab进行测试

 加锁     
 加锁     
 加锁     
 加锁     
 加锁     
 加锁     
 加锁     
 加锁     
 执行业务     
 解锁     
 解锁     
 执行业务     
 解锁     
 执行业务     
 解锁     
 执行业务     
 解锁     
 解锁     
 执行业务     
 解锁     
 加锁     
 执行业务     
 解锁     
 加锁     
 执行业务     
 解锁

从测试结果来看,发现有多个执行业务,并没有完全锁住。这个是因为我们用的是redis的set命令。set 命令用于设置给定 key 的值。如果 key 已经存储其他值, SET 就覆写旧值,且无视类型。这样会导致很多服务都能加锁成功,而我们想要的是只有一个服务能加锁成功。

要解决这个问题,需要了解redis的另一个命令setnx。setnx 命令在指定的 key 不存在时,为 key 设置指定的值。

<?php

class RedisLock
{
   protected $redis;

   public function __construct(){
   
       $redis = new Redis();
       $redis->connect('127.0.0.1',6379);

       $this->redis = $redis;
   }
   public function getLock($key){
       $value = $this->redis->get($key);
       return $value;
   }

   public function setLock($key,$value){
       return $this->redis->setnx($key,$value);
   }

   public function delLock($key){
       $lineNumber = $thid->redis->del($key);
       return $lineNumber;
   }
}

$key = 'your_lock_key';
$value = time();

$redisLock = new RedisLock();
$isLock = $redisLock->get($key);
if($isLock) {
   //已有锁,直接返回,不往下执行了
   return false;
}

//没有锁,加锁
$setLock = $redisLock->setLock($key,$value);
if(!$setLock) {
   //加锁失败
   return false;
}


//todo 执行业务逻辑
sleep(5);

// 解锁
$redisLock->delLock($key);

再次使用ab进行测试

 加锁      
 加锁      
 加锁      
 加锁      
 加锁      
 加锁      
 加锁      
 加锁失败      
 加锁失败      
 加锁失败      
 加锁失败      
 加锁失败      
 已锁      
 已锁      
 已锁      
 执行业务      
 解锁

从测试结果来看,在未加锁的状态下,有多个服务同时获取加锁,但是只有一个加锁成功, 其他的都是返回加锁失败,再后面的服务更是直接返回已锁。由此可见,加锁成功。

那么到此就结束了吗?其实并不是的。假如在已加锁的情况执行业务,在业务过程中因为一些原因出现异常导致退出而没有进行解锁,那么将造成死锁,后面的所有服务都无法再次获取锁。为了解决这个问题,我们需要对锁设置一个过期的时间,防止死锁的发生。

<?php

class RedisLock
{
   protected $redis;

   public function __construct(){
   
       $redis = new Redis();
       $redis->connect('127.0.0.1',6379);

       $this->redis = $redis;
   }
   public function getLock($key){
       $value = $this->redis->get($key);
       return $value;
   }

   public function setLock($key,$value,$second){
       $setnx = $this->redis->setnx($key,$value);
       if(!$setnx) {
           return $setnx;
       }
       $expire = $this->redis->expire($key,$second);
       if(!$expire) {
           $this->redis->del($key);
       }

       return $expire;
   }

   public function delLock($key){
       $lineNumber = $thid->redis->del($key);
       return $lineNumber;
   }
}

$key = 'your_lock_key';
$value = time();

$redisLock = new RedisLock();
$isLock = $redisLock->get($key);
if($isLock) {
   //已有锁,直接返回,不往下执行了
   return false;
}

//没有锁,加锁
$second = 5;
$setLock = $redisLock->setLock($key,$value,$second);
if(!$setLock) {
   //加锁失败
   return false;
}


//todo 执行业务逻辑
sleep(5);

// 解锁
$redisLock->delLock($key);

来源:https://blog.csdn.net/u010594957/article/details/125189169

标签:PHP,redis,分布式锁
0
投稿

猜你喜欢

  • ASP+FSO生成的网页文件默认编码格式以及转换成UTF-8编码方法

    2011-03-07 11:10:00
  • Python实现网络聊天室的示例代码(支持多人聊天与私聊)

    2022-11-21 15:17:23
  • Python编程实现tail-n查看日志文件的方法

    2023-07-26 01:17:42
  • Go程序性能优化及pprof使用方法详解

    2023-08-28 14:04:40
  • Python实战整活之聊天机器人

    2022-10-13 09:57:50
  • Python+Opencv识别两张相似图片

    2022-11-07 09:12:55
  • 详解如何让页面与 iframe 进行通信

    2024-04-19 09:42:44
  • SQL Server 2008网络协议深入理解

    2024-01-17 02:42:34
  • layDate插件设置开始和结束时间

    2024-05-03 15:05:03
  • Python的Asyncore异步Socket模块及实现端口转发的例子

    2023-04-23 13:24:38
  • Python使用Selenium实现淘宝抢单的流程分析

    2022-01-31 04:04:18
  • 一个二级伸缩下拉菜单代码

    2008-06-24 18:12:00
  • 简单了解mysql mycat 中间件

    2024-01-19 18:59:13
  • 使用python检查yaml配置文件是否符合要求

    2021-06-23 05:27:53
  • python snownlp情感分析简易demo(分享)

    2021-07-18 04:32:35
  • 内容,而不是Chrome

    2008-10-16 13:43:00
  • 每个ASP程序员必备的知识

    2008-09-21 21:34:00
  • python-opencv中的cv2.inRange函数用法说明

    2022-09-29 23:39:08
  • 如何用python批量发送工资条邮件

    2021-03-07 10:53:09
  • 从零学python系列之浅谈pickle模块封装和拆封数据对象的方法

    2023-07-13 17:23:15
  • asp之家 网络编程 m.aspxhome.com