PHP解决高并发加锁失效问题(涉及性能问题)

文章介绍了一个基于Redis的分布式锁实现,通过lua脚本确保操作的原子性。锁的添加和删除都通过特定的lua脚本完成,同时还有一个监听锁的机制,检查并更新锁的过期时间,确保锁的有效管理。

通过redis集合记录加锁的key,然后监听这个集合的key,判断锁key是否存在,不存在则在集合的key进行删除,存在则判断过期时间是否小于3秒,如果小于3秒则更新过期时间,保证redis执行命令的原子性,需要通过lua脚本来实现。

实现的流程图:

 

RedisLock代码

<?php
class RedisLock
{

    //集合key分片最大数量
    public $sliceMax = 2;

    //保存redis
    protected $redis;

    //集合key名字
    protected $lockKeyName = "lock_key_list";
    
    /**
     * 构造函数
     * @param string $host redis地址
     * @param int $prot redis端口
     */
    public function __construct($host = '127.0.0.1',$port = 6379) {
        $this->redis =  new Redis();
        $this->redis->connect($host,$port);            
    }
    
    /**
     * 加锁
     * @param string $key 加锁key
     * @param string $lockId 加锁的唯一表示
     * @param int $ttl 生存时间
     * @param int $retry 重试多少次
     * @param int $usleep 休眠多少微秒
     */
    public function lock($key,$lockId,$ttl = 5,$retry = 3, $usleep = 1000000) {
        $return = false;
        $luaScript = <<<LUA
            if redis.call("setnx",KEYS[1],ARGV[1])
            then
                redis.call("expire",KEYS[1],tonumber(ARGV[2]))
                redis.call("sadd",KEYS[2],KEYS[1])
                return 1
            else 
                return 0
            end
        LUA;  

        $this->setKeyName($key);

        $return = $this->redis->eval($luaScript,[ $key,$this->lockKeyName,$lockId,$ttl],2);
        if(!$return) {
            while($retry-- >= 0) {
               usleep($usleep); 
               $return = $this->redis->eval($luaScript,[ $key,$this->lockKeyName,$lockId,$ttl],2);
               if($return) {
                   break;                                  
               }                         
            }                                     
        }
        return $return;
    }
    
    /**
     * 解锁
     * @param string $key 加锁key
     * @param string $lockId 加锁的唯一表示
     */
    public function unlock($key,$lockId) {        
        $luaScript = <<<LUA
             if redis.call("get",KEYS[1]) == ARGV[1]
             then
                  redis.call("del",KEYS[1])
                  redis.call("srem",KEYS[2],KEYS[1])
                  return 1
             else 
                  return 0
             end
         LUA;  

         $this->setKeyName($key);
         
         $return = $this->redis->eval($luaScript,[ $key,$this->lockKeyName,$lockId ],2);    
         return $return;                                                                                    
    }

    /**
     * 设置集合key的名称
     */
    protected function setKeyName($key) {
        if($this->sliceMax > 1) {
            $num = $this->slices($key);
            $this->lockKeyName = $this->lockKeyName.':'.$num;
        }
    }

    /**
     * 分片集合key算法
     */
    protected function slices($key)
    {
        return abs(crc32($key)) % $this->sliceMax;
    }
    
}

$r = new RedisLock();
$r->lock("lock:809","x12233");
//$r->unlock("lock:1","x12233");

ListenLock.php代码:

<?php

class ListenLock
{
    protected $redis;

    protected $lockKeyName = "lock_key_list";
    
    public function __construct($host = '127.0.0.1',$port = 6379) {
        $this->redis =  new Redis();
        $this->redis->connect($host,$port);            
    }

    public function listen($argv)
    { 
        if(isset($argv[1]) && !empty($argv[1]))
        {
           $this->lockKeyName = $this->lockKeyName.":".$argv[1];
        }
        while(true)
        {
            $arr = $this->redis->sMembers($this->lockKeyName);
            if(!empty($arr))
            {
                foreach($arr as $k => $key) {
                    $luaScript = <<<LUA
                        if redis.call("exists",KEYS[1])
                        then
                            if redis.call("ttl",KEYS[1]) < tonumber(ARGV[1])
                            then
                                redis.call("expire",KEYS[1],tonumber(ARGV[2]))
                                return 1
                            end
                            return 0
                        else
                            redis.call('srem',KEYS[2],KEYS[1])
                            return 0
                        end 
                    LUA; 

                    $a = $this->redis->eval($luaScript,[$key,$this->lockKeyName,4,10],2);
                }
            }
            sleep(1);
        }
       
    }


}
$l = new ListenLock();
$l->listen($argv);

执行监听命令

php ListenLock.php [分片的数字]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值