Use scenario case of incr concurrent lock in redis

PHP: Use Cases of Incr Concurrent Locks in Redis


Concurrency is often encountered during development. I currently encounter a usage scenario and need to pull the statement regularly. However, there may be a problem with the original framework. When the original pull was started at 10 o'clock, it caused multiple times at 10 o'clock at the same time. The request resulted in no successful interception on the program, and multiple pieces of the same data were inserted into the data at the same time. In fact, in the current project, a method has been made to determine whether there is a method. If the current statement exists, it will be updated directly, but because of the existence of concurrency, under the circumstances, multiple pieces of the same data at the same time have been judged whether there is No, the insert operation was performed at the same time, resulting in a large amount of duplicate data. That is to say, even if you make a judgment on whether it exists, it is actually unreliable under concurrency. At this time, we need to lock in the method of the program. Operation, the lock at the beginning is done with get and set, simply write a demo as follows:

<?php
	/**
	 * 此处是redis操作类中的方法
	 */
	public function lock($key, $ttl)
	{
    
    
		return $this->redis->set($key, true, $ttl);
	}
	public function unLock($key)
	{
    
    
		return $this->redis->delete($key);
	}

	public function existLock($key)
	{
    
    
		return $this->get($key) ? true : false;
	}
//
	/**
	 * 此处是调用锁
	 */
	$key = 'lock';
	// 如果锁存在则直接返回,否则上锁,继续下边流程
	if ($redis->existLock($key)) {
    
    
		return ;
	}
	
	// 进行上锁
	$redis->lock($key, $ttl);

	// 中间获取对账单数据,然后进行插入操作
	$this->insertOrUpdate($where, $data);	
	...
	...
	// 一个大致的更新数据库的方法
	public function insertOrUpdate($where, $data)
	{
    
    
		// 此时操作因为是并发请求,比如说是5条相同的数据进行了请求,此时五次并发请求都没有查找到相同数据,然后五条同时进行了插入操作,从而导致数据库有五条相同的数据
		$res = $model->getOne($where);
		if (!$res) {
    
    
			//执行插入
			return $model->insert($data);
		}
		// 执行更新
		return $model->update($res['id'], $data);
	}
	

The above is the approximate code distribution of the problem. Obviously the problem is on the lock. Get and set are unreasonable. When concurrent, all requests cannot be intercepted. The reason is the same as the database insert and update. At this time, you need to change the lock method, use the atomicity of redis incr lock, the atomicity is explained as follows

原子性(atomicity):一个事务是一个不可分割的最小工作单位,事务中包括的诸操作要么都做,要么都不做。

Redis所有单个命令的执行都是原子性的,这与它的单线程机制有关;

Redis命令的原子性使得我们不用考虑并发问题,可以方便的利用原子性自增操作

实现简单计数器功能;

To put it simply, when programs A, B, and C call redis->incr('incrKey') at the same time, the value of incrKey must be from 1 to 3. When program A is called, incrKey is 1, and program B is called When the incrKey is 3, the incrKey is 2 when the C program is called, or other order. We can intercept concurrency based on this feature. Only when incrKey is equal to 1, we can proceed to the next logical operation. All other values ​​are discarded, which can prevent multiple programs from requesting at the same time, resulting in repeated operations. Specific code implementation As follows, we change the redis operation class and specific business calling method

<?php
	// redis操作类
	
	// 这次我们在上锁的时候直接进行锁判断,直接返回true或者false
	public function lock($key, $ttl)
	{
    
    
		$lock = $this->redis->incr($key);
        // 设置过期时间,incr会把key的过期时间设为长期有效,需要加上有效期
        $this->redis->expire($key, $expire);
		return $this->existLock($key);
	}

	public function existLock($key)
	{
    
    
		return $this->get($key) == 1 ? true : false;
	}

	// 然后业务逻辑
	if (!$redis->lock($key)) {
    
    
		echo '已上锁';
	}
	// 下边正常的逻辑

After completing this code, perform a concurrent test, and then find that there is still a problem, where the specific problem occurred, appeared in the redis lock operation,

	public function lock($key, $ttl)
	{
    
    
		$lock = $this->redis->incr($key);
        // 设置过期时间,incr会把key的过期时间设为长期有效,需要加上有效期
        $this->redis->expire($key, $expire);
        // 此时调用existLock的时候又发生了并发,上锁的时候是1,但是返回的时候,因为同时并发请求的原因,$key已经不是1了,导致程序依然有问题,没办法执行
		return $this->existLock($key);
	}

The reason is as above:
Then the reason is found, and the actual solution will come out

<?php
	// redis操作类
	
	// 这次我们在上锁的时候直接进行锁判断,直接返回true或者false
	public function lock($key, $ttl)
	{
    
    
		$lock = $this->redis->incr($key);
        // 设置过期时间,incr会把key的过期时间设为长期有效,需要加上有效期
        $this->redis->expire($key, $expire);
        // 保证程序完整性,在设置完后,立马进行判断返回
		if($lock == 1) {
    
    
			return true;
		} else {
    
    
			return false;
		}
	}

	//  要什么自行车,要什么检查,直接干掉
	//public function existLock($key)
	//{
    
    
	//	return $this->get($key) == 1 ? true : false;
	//}

	// 然后业务逻辑
	if (!$redis->lock($key)) {
    
    
		echo '已上锁';
	}
	// 下边正常的逻辑

	// 当然不要忘记解锁
	$redis->unLock($key);

At this time, the problem of repeated data insertion caused by concurrency has been solved.
THE END

Guess you like

Origin blog.csdn.net/qq_15915293/article/details/100145820