First, write in front:

In the whole supply chain system, there will be a lot of kinds of documents (purchase order, receipt, the invoice and waybill, etc.), in relation to write documents data interface (add deletion operation), even if the front do the restrictions, it is possible because the network or abnormal operations concurrent repeated calls, lead to do the same with the same documents;

In order to prevent this situation from causing abnormal impact on the system, we implemented a simple document lock through Redis. Each request needs to obtain the lock before the business logic can be executed, and the lock will be released after the execution. It ensures that only one request can obtain the lock (Redis dependent single thread) for the concurrent repeated operation requests of the same document, which is a pessimistic lock design.

Note: Redis lock in our system is generally only used to solve the situation of concurrent repeated requests. For non-concurrent repeated requests, it is generally used to check the status of data in the database or log. The combination of the two mechanisms can ensure the reliability of the whole link.

Two, locking mechanism:

Mainly rely on Redis setnx instruction to achieve:

However, there is a problem with setnx instruction, that is, setnx instruction does not support setting the expiration time, and the expire instruction needs to be used to set the timeout time for the key. In this way, the entire locking operation is not an atomic operation. There may be setnx locking success, but failed to set the timeout time due to the abnormal exit of the program. If not unlocked in a timely manner, deadlocks can occur (even if deadlocks do not occur in business scenarios, it is not a good design to keep useless keys in memory).

This can be resolved using Redis transactions, which use the setnx and EXPIRE directives as an atomic operation. However, this is relatively cumbersome. However, after Redis 2.6.12, the Redis set directive supports THE NX and EX modes and atomically sets the expiration time:

Three, lock implementation (complete test code will be posted at the end) :

/ * * * add documents lock * @ param int ID * @ $intOrderId documents param int $intExpireTime lock expiration time (in seconds) * @ return a bool | int lock successfully returns only lock ID, Public static function addLock($intOrderId, $intOrderId, $intOrderId) $intExpireTime = self: : REDIS_LOCK_DEFAULT_EXPIRE_TIME) {/ / check if parameters (empty ($intOrderId) | | {$intExpireTime < = 0) return false; $objRedisConn = self::getRedisConn(); / / generate a unique ID lock and unlock $intUniqueLockId must hold this ID = self: : generateUniqueLockId (); $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId); $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId); // Setnx (with Redis setnx, starting with Redis 2.6.12, setnx can also be set with the optional parameter, $bolRes = $objRedisConn->set($strKey, $intExpireTime, ['nx', 'ex']); $bolRes? $bolRes? $intUniqueLockId : $bolRes; }Copy the code

Iv. Unlocking Mechanism:

Unlock is to compare the unique LOCK ID of the lock. If the comparison succeeds, the key is deleted. It should be noted that the whole process of unlocking also needs to ensure atomicity, which depends on the watch and transaction implementation of Redis.

The WATCH command can monitor one or more keys, and once one of them is modified (or deleted), subsequent transactions will not be executed. Monitoring continues up to the EXEC command (the commands in the transaction are executed after EXEC, so the WATCH monitor can be changed after MULTI command)

Five, unlock implementation (complete test code will be posted at the end) :

@param int $intLockId lock ID * @param int $intLockId lock ID * @return bool */ public static function ReleaseLock ($intOrderId, $intLockId) {/ / check if parameters (empty ($intOrderId) | | the empty ($intLockId)) {return false. $objRedisConn = self::getRedisConn(); $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId); $objRedisConn->watch($strKey); $objRedisConn->watch($strKey); if ($intLockId == $objRedisConn->get($strKey)) { $objRedisConn->multi()->del($strKey)->exec(); return true; } $objRedisConn->unwatch(); return false; }Copy the code

Vi. Attached with the overall test code (this code is only a simple version)

<? PHP / * * * Class Lock_Service documents lock service * / Class Lock_Service {/ * * * * documents redis key lock template/const REDIS_LOCK_KEY_TEMPLATE = 'order_lock_%s'; */ const REDIS_LOCK_DEFAULT_EXPIRE_TIME = 86400; / * * * add documents lock * @ param int ID * @ $intOrderId documents param int $intExpireTime lock expiration time (in seconds) * @ return a bool | int lock successfully returns only lock ID, Public static function addLock($intOrderId, $intOrderId, $intOrderId) $intExpireTime = self: : REDIS_LOCK_DEFAULT_EXPIRE_TIME) {/ / check if parameters (empty ($intOrderId) | | {$intExpireTime < = 0) return false; $objRedisConn = self::getRedisConn(); / / generate a unique ID lock and unlock $intUniqueLockId must hold this ID = self: : generateUniqueLockId (); $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId); $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId); // Setnx (with Redis setnx, starting with Redis 2.6.12, setnx can also be set with the optional parameter, $bolRes = $objRedisConn->set($strKey, $intExpireTime, ['nx', 'ex']); $bolRes? $bolRes? $intUniqueLockId : $bolRes; } @param int $intLockId lock ID * @param int $intLockId lock ID * @return bool */ public static function ReleaseLock ($intOrderId, $intLockId) {/ / check if parameters (empty ($intOrderId) | | the empty ($intLockId)) {return false. $objRedisConn = self::getRedisConn(); $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId); $objRedisConn->watch($strKey); $objRedisConn->watch($strKey); if ($intLockId == $objRedisConn->get($strKey)) { $objRedisConn->multi()->del($strKey)->exec(); return true; } $objRedisConn->unwatch(); return false; } /** * Redis configuration: IP */ const REDIS_CONFIG_HOST = '127.0.0.1'; /** * Redis configuration: port */ const REDIS_CONFIG_PORT = 6379; /** * Get Redis connection (easy version, * @param string $strIp IP * @param int $intPort port * @return object Redis connection */ public static function getRedisConn($strIp = self::REDIS_CONFIG_HOST, $intPort = self::REDIS_CONFIG_PORT) { $objRedis = new Redis(); $objRedis->connect($strIp, $intPort); return $objRedis; } /** * const REDIS_LOCK_UNIQUE_ID_KEY = 'lock_unique_id'; /** * generate lock unique ID (via Redis INCr instructions to achieve a simple version, can combine date, timestamp, mod, string padding, random number, etc. Functions, * @return mixed */ public static function generateUniqueLockId() {return self::getRedisConn()->incr(self::REDIS_LOCK_UNIQUE_ID_KEY); } } //test $res1 = Lock_Service::addLock('666666'); var_dump($res1); $res2 = Lock_Service::addLock('666666'); var_dump($res2); $res3 = Lock_Service::releaseLock('666666', $res1); var_dump($res3); $res4 = Lock_Service::releaseLock('666666', $res1); var_dump($res4); //false, the unlock failsCopy the code

The above is the content of this article, I hope to help you.