天天看點

java redis 高并發鎖_如何利用Redis鎖解決高并發問題詳解

redis技术的使用:

redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动。。。

redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,解决了数据库服务器压力。

为什么redis的地位越来越高,我们为何不选择memcache,这是因为memcache只能存储字符串,而redis存储类型很丰富(例如有字符串、LIST、SET等),memcache每个值最大只能存储1M,存储资源非常有限,十分消耗内存资源,而redis可以存储1G,最重要的是memcache它不如redis安全,当服务器发生故障或者意外关机等情况时,redsi会把内存中的数据备份到硬盘中,而memcache所存储的东西全部丢失;这也说明了memcache不适合做数据库来用,可以用来做缓存。

引言

这里我们主要利用Redis的setnx的命令来处理高并发。

setnx 有两个参数。第一个参数表示键。第二个参数表示值。如果当前键不存在,那么会插入当前键,将第二个参数做为值。返回 1。如果当前键存在,那么会返回0。

创建库存表

CREATE TABLE `storage` (

`id` int(11) unsigned NOT NULL AUTO_INCREMENT,

`number` int(11) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1

设置初始库存为10

创建订单表

CREATE TABLE `order` (

`id` int(11) unsigned NOT NULL AUTO_INCREMENT,

`number` int(11) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1

测试不用锁的时候

$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'root');

$sql="select `number` from storage where id=1 limit 1";

$res = $pdo->query($sql)->fetch();

$number = $res['number'];

if($number>0)

{

$sql ="insert into `order` VALUES (null,$number)";

$order_id = $pdo->query($sql);

if($order_id)

{

$sql="update storage set `number`=`number`-1 WHERE id=1";

$pdo->query($sql);

}

}

ab测试模拟并发,发现库存是正确的。

mysql> select * from storage;

+----+--------+

| id | number |

+----+--------+

| 1 | 0 |

+----+--------+

1 row in set (0.00 sec)

在来看订单表

mysql> select * from `order`;

+----+--------+

| id | number |

+----+--------+

| 1 | 10 |

| 2 | 10 |

| 3 | 9 |

| 4 | 7 |

| 5 | 6 |

| 6 | 5 |

| 7 | 5 |

| 8 | 5 |

| 9 | 4 |

| 10 | 1 |

+----+--------+

10 rows in set (0.00 sec)

发现存在几个订单都是操作的同一个库存数据,这样就可能引起超卖的情况。

修改代码加入redis锁进行数据控制

class Lock

{

private static $_instance ;

private $_redis;

private function __construct()

{

$this->_redis = new Redis();

$this->_redis ->connect('127.0.0.1');

}

public static function getInstance()

{

if(self::$_instance instanceof self)

{

return self::$_instance;

}

return self::$_instance = new self();

}

public function set($key,$expTime)

{

//初步加锁

$isLock = $this->_redis->setnx($key,time()+$expTime);

if($isLock)

{

return true;

}

else

{

//加锁失败的情况下。判断锁是否已经存在,如果锁存在切已经过期,那么删除锁。进行重新加锁

$val = $this->_redis->get($key);

if($val&&$val

{

$this->del($key);

}

return $this->_redis->setnx($key,time()+$expTime);

}

}

public function del($key)

{

$this->_redis->del($key);

}

}

$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'root');

$lockObj = Lock::getInstance();

//判断是能加锁成功

if($lock = $lockObj->set('storage',10))

{

$sql="select `number` from storage where id=1 limit 1";

$res = $pdo->query($sql)->fetch();

$number = $res['number'];

if($number>0)

{

$sql ="insert into `order` VALUES (null,$number)";

$order_id = $pdo->query($sql);

if($order_id)

{

$sql="update storage set `number`=`number`-1 WHERE id=1";

$pdo->query($sql);

}

}

//解锁

$lockObj->del('storage');

}

else

{

//加锁不成功执行其他操作。

}

再次进行ab测试,查看测试结果

mysql> select * from `order`;

+----+--------+

| id | number |

+----+--------+

| 1 | 10 |

| 2 | 9 |

| 3 | 8 |

| 4 | 7 |

| 5 | 6 |

| 6 | 5 |

| 7 | 4 |

| 8 | 3 |

| 9 | 2 |

| 10 | 1 |

+----+--------+

10 rows in set (0.00 sec)

发现订单表没有操作同一个库存数据的情况。所以利用redis锁是可以有效的处理高并发的。

这里在加锁的时候其实是可以不需要判断过期时间的,这里我们为了避免造成死锁,所以加一个过期时间的判断。当过期的时候主动删除该锁。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。