PHP implémente un simple verrouillage de document Redis et empêche les appels répétés simultanés

零到壹度
Libérer: 2023-03-23 13:56:02
original
2158 Les gens l'ont consulté

Dans l'ensemble du système de chaîne d'approvisionnement, il y aura de nombreux types de documents (bons de commande, ordres d'entreposage, bons d'arrivée, lettres de transport, etc.) En ce qui concerne l'interface d'écriture des données des documents (opérations d'ajout, de suppression et de modification). ), même si le front-end a effectué des restrictions pertinentes, il est toujours possible que des appels répétés simultanés se produisent en raison de problèmes de réseau ou d'opérations anormales, entraînant le même traitement du même document

Dans ; Afin d'éviter que cette situation n'ait un impact anormal sur le système, nous utilisons Redis Un simple verrouillage de document est implémenté. Chaque requête doit acquérir le verrou avant d'exécuter la logique métier. Le verrou sera libéré une fois l'exécution terminée. une seule demande d'opérations répétées simultanées sur le même document peut acquérir le verrou (en s'appuyant sur le seul thread de Redis), est une conception de verrouillage pessimiste

Remarque : le verrouillage Redis n'est généralement utilisé que pour résoudre ; demandes répétées simultanées dans notre système. Pour les demandes répétées non simultanées, la base de données ou le journal sera généralement vérifié. La combinaison des deux mécanismes peut garantir la fiabilité de l'ensemble du lien.

2. Mécanisme de verrouillage :
S'appuie principalement sur l'instruction Redis setnx :
PHP implémente un simple verrouillage de document Redis et empêche les appels répétés simultanés
Mais il y a un problème avec l'utilisation de setnx, c'est-à-dire que le L'instruction setnx ne prend pas en charge le paramètre Pour le délai d'expiration, vous devez utiliser la commande expire pour définir un délai d'attente pour la clé. De cette façon, l'ensemble de l'opération de verrouillage n'est pas une opération atomique. Il est possible que le verrouillage setnx réussisse. mais le délai d'attente n'est pas défini avec succès en raison d'une sortie anormale du programme et le verrou n'est pas déverrouillé à temps. Dans ce cas, cela peut conduire à un blocage (même si un blocage ne se produit pas dans le scénario commercial, ce n'est pas une bonne conception. pour que les clés inutiles restent en mémoire);

Cette situation peut être résolue en utilisant des transactions Redis, exécutez les instructions setnx et expirez comme une opération atomique, mais cela sera relativement gênant, heureusement, dans les versions postérieures à Redis 2.6. .12, l'instruction Redis set prend en charge les modes nx et ex, et prend en charge les paramètres atomiques :
PHP implémente un simple verrouillage de document Redis et empêche les appels répétés simultanés
3. Implémentation du verrouillage (le code de test complet sera publié à la fin) :

  /**
     * 加单据锁
     * @param int $intOrderId 单据ID
     * @param int $intExpireTime 锁过期时间(秒)
     * @return bool|int 加锁成功返回唯一锁ID,加锁失败返回false
     */
    public static function addLock($intOrderId, $intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME)
    {
        //参数校验
        if (empty($intOrderId) || $intExpireTime <= 0) {            return false;
        }        
        
        //获取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, [&#39;nx&#39;, &#39;ex&#39;=>$intExpireTime]);        
        
        //加锁成功返回锁ID,加锁失败返回false
        return $bolRes ? $intUniqueLockId : $bolRes;
    }
Copier après la connexion

4. Mécanisme de déverrouillage :
Le déverrouillage signifie comparer l'identifiant de verrouillage unique lors du verrouillage. Si la comparaison est réussie, la clé sera supprimée. Il convient de noter que l'ensemble du processus de déverrouillage doit également garantir l'atomicité. Cela repose sur la mise en œuvre de la surveillance et des transactions de redis ;

La commande WATCH peut surveiller une ou plusieurs clés une fois qu'une des clés est activée. modifiée (ou supprimée), les transactions ultérieures ne seront pas exécutées. La surveillance continue jusqu'à la commande EXEC (les commandes de la transaction sont exécutées après EXEC, donc la valeur clé de la surveillance WATCH peut être modifiée après la commande MULTI)
Pour la surveillance et les transactions Redis, veuillez vous référer au court article du livre : https://www.jianshu.com/p/361cb9cd13d5

5. le code de test complet sera Publié à la fin) :

/**
     * 解单据锁
     * @param int $intOrderId 单据ID
     * @param int $intLockId 锁唯一ID
     * @return bool
     */
    public static function releaseLock($intOrderId, $intLockId)
    {
        //参数校验
        if (empty($intOrderId) || empty($intLockId)) {            return false;
        }        
        
        //获取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;
    }
Copier après la connexion

6. Ci-joint le code de test global (ce code n'est qu'une version simple)

<?php/**
 
 * Class Lock_Service 单据锁服务
 */class Lock_Service
{    
    
    /**
     * 单据锁redis key模板
     */
    const REDIS_LOCK_KEY_TEMPLATE = &#39;order_lock_%s&#39;;    
    
    /**
     * 单据锁默认超时时间(秒)
     */
    const REDIS_LOCK_DEFAULT_EXPIRE_TIME = 86400;    /**
     
     * 加单据锁
     * @param int $intOrderId 单据ID
     * @param int $intExpireTime 锁过期时间(秒)
     * @return bool|int 加锁成功返回唯一锁ID,加锁失败返回false
     */
    public static function addLock($intOrderId, $intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME)
    {        
        //参数校验
        if (empty($intOrderId) || $intExpireTime <= 0) {            return false;
        }        
        
        //获取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, [&#39;nx&#39;, &#39;ex&#39;=>$intExpireTime]);        
        
        //加锁成功返回锁ID,加锁失败返回false
        return $bolRes ? $intUniqueLockId : $bolRes;
    }    
     
     /**
     * 解单据锁
     * @param int $intOrderId 单据ID
     * @param int $intLockId 锁唯一ID
     * @return bool
     */
    public static function releaseLock($intOrderId, $intLockId)
    {        
        //参数校验
        if (empty($intOrderId) || empty($intLockId)) {            return false;
        }        
        
        //获取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;
    }    
    
     /**
     * Redis配置:IP
     */
    const REDIS_CONFIG_HOST = &#39;127.0.0.1&#39;;    
    
     /**
     * 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 = &#39;lock_unique_id&#39;;    /**
     * 生成锁唯一ID(通过Redis incr指令实现简易版本,可结合日期、时间戳、取余、字符串填充、随机数等函数,生成指定位数唯一ID)
     * @return mixed
     */
    public static function generateUniqueLockId()
    {        return self::getRedisConn()->incr(self::REDIS_LOCK_UNIQUE_ID_KEY);
    }
}//test$res1 = Lock_Service::addLock(&#39;666666&#39;);
var_dump($res1);//返回lock id,加锁成功$res2 = Lock_Service::addLock(&#39;666666&#39;);
var_dump($res2);//false,加锁失败$res3 = Lock_Service::releaseLock(&#39;666666&#39;, $res1);
var_dump($res3);//true,解锁成功$res4 = Lock_Service::releaseLock(&#39;666666&#39;, $res1);
var_dump($res4);//false,解锁失败
Copier après la connexion

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!