高并发模拟实验2-解决高并发问题

作者: wxfeng 分类: linux,php,数据库 发布时间: 2018-12-26 14:59

通过实验一我们知道,由于并发查询库存剩余量时,会存在多个用户获取的库存剩余量相同的情况,继而导致更新库存出错。所以,如果解决了获取库存量错误的问题,即可解决最终库存量错误的问题。这里的实验,主要使用了以下几种方法进行解决。

1,排他锁

排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,所以我们可以这样修改之前的程序。

<?php

$db = new MySQLi("localhost","root","yptest","test");
mysqli_query($db, 'BEGIN');
$query = "select quantity from goods where ID =1 for update";
$res =$db->query($query);

while($row=mysqli_fetch_array($res)){
   $quantity = $row['quantity'];
}
$sql = "update goods set quantity = $quantity-1 where ID =1";
$reslut =$db->query($sql);
mysqli_query($db, 'COMMIT');
$file = fopen("log.txt", "a+") or die("Unable to open file!");
fwrite($file, $sql."\r\n");
fclose($file);


?>

开启事务:mysqli_query($db, ‘BEGIN’);

给查询语句添加排他锁:$query = “select quantity from goods where ID =1 for update”;

提交事务,解锁:mysqli_query($db, ‘COMMIT’);

这样就可以使每个请求作为一个独立的事务,在一个事务(请求)进行查询时,其他事务(请求)只有等到上一个事务提交,解锁之后,才能查询到剩余的库存量,进行更新操作,保障了数据的准确性。

模拟请求:

同时有10个人进行购物操作,总的购买量为20(即总的请求个数为20,每次发送10个请求)

ab -c 10 -n 20 "http://localhost:9101/concurrency.php"

结果(正确):

MariaDB [test]> select * from goods;
+------+----------+
| ID   | quantity |
+------+----------+
|    1 |       80 |
+------+----------+
1 row in set (0.000 sec)

日志(log.txt)明细:

update goods set quantity = 100-1 where ID =1
update goods set quantity = 99-1 where ID =1
update goods set quantity = 97-1 where ID =1
update goods set quantity = 98-1 where ID =1
update goods set quantity = 95-1 where ID =1
update goods set quantity = 96-1 where ID =1
update goods set quantity = 94-1 where ID =1
update goods set quantity = 92-1 where ID =1
update goods set quantity = 93-1 where ID =1
update goods set quantity = 90-1 where ID =1
update goods set quantity = 91-1 where ID =1
update goods set quantity = 89-1 where ID =1
update goods set quantity = 86-1 where ID =1
update goods set quantity = 85-1 where ID =1
update goods set quantity = 87-1 where ID =1
update goods set quantity = 84-1 where ID =1
update goods set quantity = 88-1 where ID =1
update goods set quantity = 82-1 where ID =1
update goods set quantity = 81-1 where ID =1
update goods set quantity = 83-1 where ID =1

2,使用redis,保障操作的原子性
在redis中,命令是满足原子性的,因此在值为阿拉伯数字的时候,我可以将getset命令修改为decr或者decrby来解决这个问题,并发请求时执行下面的代码,得到的结果是满足我们预期的80。

<?php
//连接本地的 Redis 服务
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
//自减
$redis->decr('quantity');
?>

结果:

127.0.0.1:6379> get quantity
"80"
127.0.0.1:6379>

注:其实redis本事是不会存在并发问题的,因为他是单进程的,再多的command都是one by one执行的。我们使用的时候,可能会出现并发问题,比如getset这一对。使用这种方法要在执行购买操作前,将商品的库存量写入redis当中,比如在商品浏览页面。在执行完毕,还需要将数据同步更新到mysql当中(redis,mysql的数据同步思路可参照《某网站Redis与MySql同步方案分析》)。这里只做了简单的减库存的操作,实际应用会复杂点,需要进行库存的判断,但库存为0时,返回购买失败。

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!

一条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注