Maison  >  Article  >  développement back-end  >  PHP implémente le verrouillage des documents Redis et empêche l'écriture répétée simultanée

PHP implémente le verrouillage des documents Redis et empêche l'écriture répétée simultanée

不言
不言original
2018-04-10 15:53:341447parcourir

Le contenu de cet article est de partager avec vous comment PHP implémente le verrouillage des documents Redis et empêche l'écriture répétée simultanée. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer

1. Écrivez. dans Précédent :
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, ordres d'arrivée, lettres de transport, etc.), lorsqu'il s'agit de l'interface d'écriture des données du document ( opérations d'ajout, de suppression, de modification), même si le front-end a imposé des restrictions pertinentes, il est toujours possible que des appels répétés simultanés se produisent en raison d'opérations réseau ou anormales, entraînant le même traitement du même document

Afin d'éviter que cette situation n'ait un impact anormal sur le système, nous avons mis en œuvre un simple verrouillage de document via Redis. Chaque requête doit acquérir le verrou avant d'exécuter la logique métier, et le verrou sera libéré une fois l'exécution terminée. ; il est garanti qu'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 Redis) (un seul thread), est une conception de verrouillage pessimiste

Remarque : le verrouillage Redis n'est généralement utilisé qu'en général ; pour résoudre les demandes répétées simultanées dans notre système. Pour les demandes répétées non simultanées, il s'agira généralement de l'état des données de vérification de la base de données ou du journal, 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 le verrouillage des documents Redis et empêche lécriture répétée simultanéeMais 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 le verrouillage des documents Redis et empêche lécriture répétée simultanée
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;
    }

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. Implémentation du déverrouillage (le code de test complet sera affiché à 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;
    }

6. Ci-joint le code global du test (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,解锁失败
Délocalisation du blog : https://segmentfault.com /blog/leeonfancy

Recommandations associées :

configuration php et instructions pour étendre Redis sous Windows

Partage de commandes php pour faire fonctionner Redis

Explication détaillée du verrouillage de la mémoire Redis et de PHP empêchant les opérations simultanées

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!

Déclaration:
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