PHP implementiert die Redis-Dokumentsperre und verhindert gleichzeitiges wiederholtes Schreiben

不言
Freigeben: 2023-03-23 13:48:01
Original
1445 Leute haben es durchsucht

Der Inhalt dieses Artikels besteht darin, Ihnen mitzuteilen, wie PHP die Redis-Dokumentsperre implementiert und das gleichzeitige wiederholte Schreiben verhindert. Es hat einen bestimmten Referenzwert.

1. Schreiben Sie im Vorherigen:
Im gesamten Lieferkettensystem wird es viele Arten von Dokumenten geben (Bestellungen, Lageraufträge, Ankunftsaufträge, Frachtbriefe usw.), wenn es um die Schnittstelle zum Schreiben von Dokumentdaten geht ( Vorgänge hinzufügen, löschen, ändern) 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

Um zu verhindern, dass diese Situation ungewöhnliche Auswirkungen auf das System hat, implementieren wir über Redis eine einfache Dokumentsperre. Jede Anforderung muss die Sperre erwerben, bevor die Geschäftslogik ausgeführt wird. Die Sperre wird nach Abschluss der Ausführung aufgehoben ; Es ist garantiert, dass nur eine Anforderung für gleichzeitige wiederholte Betriebsanforderungen desselben Dokuments die Sperre erhalten kann (abhängig von Redis). Ein einzelner Thread ist ein pessimistisches Sperrdesign

Hinweis: Redis-Sperre wird im Allgemeinen nur verwendet Um gleichzeitige wiederholte Anforderungen in unserem System zu lösen, wird im Allgemeinen der Datenbank- oder Protokollüberprüfungsdatenstatus verwendet. Durch die Kombination der beiden Mechanismen kann die Zuverlässigkeit der gesamten Verbindung sichergestellt werden.

2. Sperrmechanismus: Verlässt sich hauptsächlich auf die Redis-Setnx-Anweisung:

PHP implementiert die Redis-Dokumentsperre und verhindert gleichzeitiges wiederholtes SchreibenAber 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 die Redis-Dokumentsperre und verhindert gleichzeitiges wiederholtes Schreiben
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;
    }
Nach dem Login kopieren

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. Entsperrungsimplementierung (der vollständige Testcode wird angezeigt). am Ende gepostet):

/**
     * 解单据锁
     * @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;
    }
Nach dem Login kopieren

6. Im Anhang ist der Gesamttestcode (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,解锁失败
Nach dem Login kopieren
Blog-Umzug: https://segmentfault.com /blog/leeonfancy

Verwandte Empfehlungen:

PHP-Konfiguration und Anweisungen zum Erweitern von Redis unter Windows

Freigabe von PHP-Befehlen zum Betrieb von Redis

Detaillierte Erläuterung der Redis-Speichersperre und PHP, die gleichzeitige Vorgänge verhindert

Das obige ist der detaillierte Inhalt vonPHP implementiert die Redis-Dokumentsperre und verhindert gleichzeitiges wiederholtes Schreiben. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
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
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage