加入收藏 | 设为首页 | 会员中心 | 我要投稿 湖南网 (https://www.hunanwang.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程 > 正文

PHP实现Redis单子锁以及防备并发一再写入

发布时间:2021-05-23 22:46:37 所属栏目:编程 来源:网络整理
导读:一、写在前面: 在整个供给链体系中,会有许多种单子(采购单、入库单、到货单、运单等等),在涉及写单子数据的接口时(增编削操纵),纵然前端做了相干限定,照旧有也许由于收集或非常操纵发生并发一再挪用的环境,导致对沟通单子做沟通的处理赏罚; 为了防备
副问题[/!--empirenews.page--]

一、写在前面:

在整个供给链体系中,会有许多种单子(采购单、入库单、到货单、运单等等),在涉及写单子数据的接口时(增编削操纵),纵然前端做了相干限定,照旧有也许由于收集或非常操纵发生并发一再挪用的环境,导致对沟通单子做沟通的处理赏罚;

为了防备这种环境对体系造成非常影响,我们通过Redis实现了一个简朴的单子锁,每个哀求需先获取锁才气执行营业逻辑,执行竣事后才会开释锁;担保了统一单子的并发一再操纵哀求只有一个哀求可以获取到锁(依靠Redis的单线程),是一种气馁锁的计划;

注:Redis锁在我们的体系中一样平常只用于办理并发一再哀求的环境,对付非并发的的一再哀求一样平常会去数据库或日记校验数据的状态,两种机制团结起来才气担保整个链路的靠得住。

二、加锁机制:

首要依靠Redis setnx指令实现:

但行使setnx有一个题目,即setnx指令不支持配置逾期时刻,必要行使expire指令另举动key配置超时时刻,这样整个加锁操纵就不是一个原子性操纵,有也许存在setnx加锁乐成,但因措施非常退出导致未乐成配置超时时刻,在不实时解锁的环境下,有也许会导致死锁(纵然营业场景中不会呈现死锁,无用的key一向常驻内存也不是很好的计划);

这种环境可以行使Redis事宜办理,把setnx与expire两条指令作为一个原子性操纵执行,但这样做相对而言会较量贫困,亏得Redis 2.6.12之后版本,Redis set指令支持了nx、ex模式,并支持原子化地配置逾期时刻:

三、加锁实现(完备测试代码会贴在最后):

//获取Redis毗连
$objRedisConn = self::getRedisConn();

//天生独一锁ID,解锁需持有此ID
$intUniqueLockId = self::generateUniqueLockId();

//按照模板,团结单子ID,天生独一Redis key(一样平常来说,单子ID在营业中体系中独一的)
$strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE,$intOrderId);

//加锁(通过Redis setnx指令实现,从Redis 2.6.12开始,通过set指令可选参数也可以实现setnx,同时可原子化地配置超时时刻)
$bolRes = $objRedisConn->set($strKey,$intUniqueLockId,['nx','ex'=>$intExpireTime]);

//加锁乐成返回锁ID,加锁失败返回false
return $bolRes ? $intUniqueLockId : $bolRes;
}

四、解锁机制:

解锁即比对加锁时的独一lock id,假如比对乐成,则删除key;必要留意的是,解锁整个进程中同样必要担保原子性,这里依靠redis的watch与事宜实现;

WATCH呼吁可以监控一个或多个键,一旦个中有一个键被修改(或删除),之后的事宜就不会执行。监控一向一连到EXEC呼吁(事宜中的呼吁是在EXEC之后才执行的,以是在MULTI呼吁后可以修改WATCH监控的键值)

五、解锁实现(完备测试代码会贴在最后):

//获取Redis毗连
$objRedisConn = self::getRedisConn();

//天生Redis key
$strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE,$intOrderId);

//监听Redis key防备在【比对lock id】与【解锁事宜执行进程中】被修改或删除,提交事宜后会自动打消监控,其他环境需手动扫除监控
$objRedisConn->watch($strKey);
if ($intLockId == $objRedisConn->get($strKey)) {
$objRedisConn->multi()->del($strKey)->exec();
return true;
}
$objRedisConn->unwatch();
return false;
}

六、附整体测试代码(此代码仅为浅显版本)

/**

  • Class Lock_Service 单子锁处事
    */
    class Lock_Service
    {
    /**
  • 单子锁redis key模板
    */
    const REDIS_LOCK_KEY_TEMPLATE = 'orderlock%s';

/**

  • 单子锁默认超时时刻(秒)
    */
    const REDIS_LOCK_DEFAULT_EXPIRE_TIME = 86400;

/**

  • 加单子锁
  • @param int $intOrderId 单子ID
  • @param int $intExpireTime 锁逾期时刻(秒)
  • @return bool|int 加锁乐成返回独一锁ID,加锁失败返回false
    */
    public static function addLock($intOrderId,'ex'=>$intExpireTime]);

//加锁乐成返回锁ID,加锁失败返回false
return $bolRes ? $intUniqueLockId : $bolRes;
}

/**

  • 解单子锁
  • @param int $intOrderId 单子ID
  • @param int $intLockId 锁独一ID
  • @return bool
    */
    public static function releaseLock($intOrderId,$intOrderId);

//监听Redis key防备在【比对lock id】与【解锁事宜执行进程中】被修改或删除,提交事宜后会自动打消监控,其他环境需手动扫除监控
$objRedisConn->watch($strKey);
if ($intLockId == $objRedisConn->get($strKey)) {
$objRedisConn->multi()->del($strKey)->exec();
return true;
}
$objRedisConn->unwatch();
return false;
}

/**

  • Redis设置:IP
    */
    const REDIS_CONFIG_HOST = '127.0.0.1';

/**

  • Redis设置:端口
    */
    const REDIS_CONFIG_PORT = 6379;

/**

  • 获取Redis毗连(浅显版本,可用单例实现)
  • @param string $strIp IP
  • @param int $intPort 端口
  • @return object Redis毗连
    */
    public static function getRedisConn($strIp = self::REDIS_CONFIG_HOST,$intPort = self::REDIS_CONFIG_PORT)
    {
    $objRedis = new Redis();
    $objRedis->connect($strIp,$intPort);
    return $objRedis;
    }

/**

  • 用于天生独一的锁ID的redis key
    */
    const REDIS_LOCK_UNIQUE_ID_KEY = 'lock_unique_id';

(编辑:湖南网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读