I. Preface:

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) :

/** ** **@paramInt $intOrderId Document ID *@paramInt $intExpireTime Lock expiration time (s) *@returnBool | int lock successfully returns only ID lock, lock failure returns false * /
public static function addLock($intOrderId.$intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME)
{
 // Check parameters
 if (empty($intOrderId) | |$intExpireTime< =0) {
  return false;
 }

 // Get the Redis connection
 $objRedisConn = self::getRedisConn();

 // Generate a unique lock ID, which is required for unlocking
 $intUniqueLockId = self::generateUniqueLockId();

 // Generate a unique Redis key based on the template and the document ID (generally, the document ID is unique in the business system)
 $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 set, and the timeout can be set atomically)
 $bolRes = $objRedisConn->set($strKey.$intUniqueLockId['nx'.'ex'= >$intExpireTime]);

 // Return the lock ID on success, false on failure
 return $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) :

/** ** **@paramInt $intOrderId Document ID *@paramInt $intLockId Lock unique ID *@return bool
  */
 public static function releaseLock($intOrderId.$intLockId)
 {
  // Check parameters
  if (empty($intOrderId) | |empty($intLockId)) {
   return false;
  }

  // Get the Redis connection
  $objRedisConn = self::getRedisConn();

  // Generate a Redis key
  $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId);

  // Monitor Redis key to prevent [lock ID comparison] and [unlock transaction execution process] from being modified or deleted. The monitor will be automatically cancelled after the transaction is committed. Otherwise, you need to manually remove the monitor
  $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)


      

/** * Class Lock_Service */
class Lock_Service
{
 /** * Document lock redis key template */
 const REDIS_LOCK_KEY_TEMPLATE = 'order_lock_%s';

 /** * Default timeout duration of document lock (s) */
 const REDIS_LOCK_DEFAULT_EXPIRE_TIME = 86400;

 /** ** **@paramInt $intOrderId Document ID *@paramInt $intExpireTime Lock expiration time (s) *@returnBool | int lock successfully returns only ID lock, lock failure returns false * /
 public static function addLock($intOrderId.$intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME)
 {
  // Check parameters
  if (empty($intOrderId) | |$intExpireTime< =0) {
   return false;
  }

  // Get the Redis connection
  $objRedisConn = self::getRedisConn();

  // Generate a unique lock ID, which is required for unlocking
  $intUniqueLockId = self::generateUniqueLockId();

  // Generate a unique Redis key based on the template and the document ID (generally, the document ID is unique in the business system)
  $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 set, and the timeout can be set atomically)
  $bolRes = $objRedisConn->set($strKey.$intUniqueLockId['nx'.'ex'= >$intExpireTime]);

  // Return the lock ID on success, false on failure
  return $bolRes ? $intUniqueLockId : $bolRes;
 }

 /** ** **@paramInt $intOrderId Document ID *@paramInt $intLockId Lock unique ID *@return bool
  */
 public static function releaseLock($intOrderId.$intLockId)
 {
  // Check parameters
  if (empty($intOrderId) | |empty($intLockId)) {
   return false;
  }

  // Get the Redis connection
  $objRedisConn = self::getRedisConn();

  // Generate a Redis key
  $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId);

  // Monitor Redis key to prevent [lock ID comparison] and [unlock transaction execution process] from being modified or deleted. The monitor will be automatically cancelled after the transaction is committed. Otherwise, you need to manually remove the monitor
  $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 (simple version, available singleton) *@param string $strIp IP
  * @paramInt $intPort Port *@returnObject 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;
 }

 /** * redis key */ to generate a unique lock ID
 const REDIS_LOCK_UNIQUE_ID_KEY = 'lock_unique_id';

 /** * Generate lock unique ID (through Redis INCr instructions to achieve a simple version, can combine date, timestamp, mod, string padding, random number and other functions, generate the specified digit unique ID) *@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);// Return the lock id, the lock is successful
$res2 = Lock_Service::addLock('666666');
var_dump($res2);//false, lock failed
$res3 = Lock_Service::releaseLock('666666'.$res1);
var_dump($res3);//true, the account is unlocked successfully
$res4 = Lock_Service::releaseLock('666666'.$res1);
var_dump($res4);//false, the unlock fails
Copy the code

The above content hopes to help you, more free PHP factory PDF, PHP advanced architecture video materials, PHP wonderful good article can be wechat search concerns: PHP open source community

2021 Jinsanyin four big factory interview real questions collection, must see!

Four years of PHP technical articles collation collection – PHP framework

A collection of four years’ worth of PHP technical articles – Microservices Architecture

Distributed Architecture is a four-year collection of PHP technical articles

Four years of PHP technical essays – High Concurrency scenarios

Four years of elite PHP technical article collation collection – database