PHP:redis并发锁的使用

PHP并发锁的使用


开发中时常会遇到并发,当前遇到一个使用场景,需要定时去拉取对账单,但是可能框架原本的问题,原本定时在10点整开始拉取一次的时候,导致10点钟同时多次请求,导致程序上没有拦截成功,数据同时插入了多条相同数据。其实在当前项目种已经做了判断是否存在方法,当前对账单如果存在的话,会直接进行更新操作,但是因为并发的存在,当时情况下,同时多条相同的数据进行了判断是否存在的,发现没有,同时进行了插入操作,导致数据,出现了大量重复数据,也就是说就算你做了是否存在的判断,在并发下,其实是不可靠的,这个时候我们就需要在程序的方法进行锁操作,刚开始的锁用的是get和set来做的,简单写个demo如下:

<?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);
	}
	

以上是大概的出问题的代码分布。很明显问题出现在了锁上边,get,set并不合理,并发的时候并不能拦截下所有的请求,原因那和数据库的插入和更新是一样的。这个时候就需要改一下锁的方法,利用redis incr锁的原子性,原子性的解释如下

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

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

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

实现简单计数器功能;

简单来说就是当A、B、C程序同时调用redis->incr(‘incrKey’)的时候,此时incrKey的值必然是由1到3,可能A程序调用的时候incrKey是1,B程序调用的时候incrKey是3,C程序调用的时候incrKey是2,或者其他顺序。我们可以根据此特性来进行拦截并发,只有当incrKey等于1的时候,我们才进行接下来的逻辑操作,其他值全部舍弃掉,就能避免多个程序同时进行请求,导致重复操作,具体代码实现如下,我们改下redis的操作类和具体业务的调用方法

<?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 '已上锁';
	}
	// 下边正常的逻辑

当完成这个代码以后进行并发测试,然后发现还是有问题,具体问题出现在哪那,出现在了redis的上锁操作中,

	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);
	}

原因如上:
然后找到原因了,实际上的解决办法也就出来了

<?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);

到这个时候,就已经解决了并发导致的数据重复插入问题
THE END

上一篇:jsp---jstl配置


下一篇:闭包