Heim  >  Artikel  >  Backend-Entwicklung  >  PHP implementiert eine einfache Redis-Dokumentsperre und verhindert gleichzeitige wiederholte Aufrufe

PHP implementiert eine einfache Redis-Dokumentsperre und verhindert gleichzeitige wiederholte Aufrufe

零到壹度
零到壹度Original
2018-04-10 16:12:092166Durchsuche

Im gesamten Lieferkettensystem gibt es viele Arten von Dokumenten (Bestellungen, Lageraufträge, Ankunftsscheine, Frachtbriefe usw.), wenn es um die Schnittstelle zum Schreiben von Dokumentdaten (Hinzufügungs-, Lösch- und Änderungsvorgänge) geht ), selbst wenn das Front-End entsprechende Einschränkungen vorgenommen hat, ist es dennoch möglich, dass aufgrund von Netzwerk- oder abnormalen Vorgängen gleichzeitige wiederholte Aufrufe auftreten, was zur gleichen Verarbeitung desselben Dokuments führt

In Um zu verhindern, dass diese Situation ungewöhnliche Auswirkungen auf das System hat, wird eine einfache Dokumentsperre implementiert, bevor die Geschäftslogik ausgeführt wird. Dies wird sichergestellt Nur eine Anforderung für gleichzeitige wiederholte Betriebsanforderungen desselben Dokuments kann die Sperre erhalten (basierend auf dem einzelnen Thread von Redis).

Hinweis: Die Redis-Sperre wird im Allgemeinen nur zum Lösen verwendet Bei gleichzeitigen wiederholten Anforderungen in unserem System wird im Allgemeinen die Datenbank oder das Protokoll überprüft. Um den Status der Daten zu überprüfen, kann die Kombination der beiden Mechanismen die Zuverlässigkeit der gesamten Verbindung sicherstellen.

2. Sperrmechanismus:
Verlässt sich hauptsächlich auf die Redis-Setnx-Anweisung:
PHP implementiert eine einfache Redis-Dokumentsperre und verhindert gleichzeitige wiederholte Aufrufe
Aber es gibt ein Problem bei der Verwendung von Setnx, das heißt Die setnx-Anweisung unterstützt die Einstellung der Ablaufzeit nicht. Sie müssen den Befehl „expire“ verwenden, um eine Zeitüberschreitung für den Schlüssel festzulegen. Auf diese Weise ist der gesamte Sperrvorgang keine atomare Operation. Es ist möglich, dass die setnx-Sperre erfolgreich ist. Aufgrund des abnormalen Beendens des Programms wurde das Timeout jedoch nicht erfolgreich festgelegt und die Sperre wurde nicht rechtzeitig aufgehoben. In diesem Fall kann es zu einem Deadlock kommen (auch wenn im Geschäftsszenario kein Deadlock auftritt, ist dies kein Fall). gutes Design, damit nutzlose Schlüssel im Speicher verbleiben);

Diese Situation kann mithilfe von Redis-Transaktionen gelöst werden. Führen Sie die beiden Anweisungen „setnx“ und „expire“ als atomare Operation aus, aber dies wird in späteren Versionen relativ problematisch sein Redis 2.6.12, die Redis-Set-Anweisung unterstützt nx- und ex-Modi und unterstützt atomare Einstellungen. Ablaufzeit:
PHP implementiert eine einfache Redis-Dokumentsperre und verhindert gleichzeitige wiederholte Aufrufe
3. Sperrimplementierung (der vollständige Testcode wird am Ende veröffentlicht ):

  /**
     * 加单据锁
     * @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. Entsperrmechanismus:
Entsperren bedeutet den Vergleich der eindeutigen Schloss-ID beim Sperren. Wenn der Vergleich erfolgreich ist, wird der Schlüssel gelöscht. Es ist zu beachten, dass der gesamte Entsperrvorgang auch die Atomizität gewährleisten muss.

Der WATCH-Befehl kann einen oder mehrere Schlüssel überwachen Schlüssel geändert (oder gelöscht) werden, werden nachfolgende Transaktionen nicht ausgeführt. Die Überwachung wird bis zum EXEC-Befehl fortgesetzt (Befehle in der Transaktion werden nach EXEC ausgeführt, sodass der Schlüsselwert der WATCH-Überwachung nach dem MULTI-Befehl geändert werden kann)
Informationen zu Redis-Überwachung und -Transaktionen finden Sie im kurzen Buchartikel: https://www.jianshu.com/p/361cb9cd13d5

5. Der vollständige Testcode wird am Ende veröffentlicht):

/**
     * 解单据锁
     * @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. Anbei ist der gesamte Testcode (dieser Code ist nur eine einfache Version)

<?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,解锁失败

Das obige ist der detaillierte Inhalt vonPHP implementiert eine einfache Redis-Dokumentsperre und verhindert gleichzeitige wiederholte Aufrufe. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn