方案一:
使用mysql的事务加排他锁来解决,首先我们选择数据库的存储引擎为innoDB,使用的是排他锁实现的,刚开始的时候我们测试了下共享锁,发现还是会出现超卖的现象。有个问题是,当我们进行高并发测试时,对数据库的性能影响很大,导致数据库的压力很大。利用数据库的forupdate来加锁(在数量少的情况下并不会出现问题,但是当并发达到(ab -n 1000 -c 200),就会出现请求非2XX的响应增多,1000 失败了 60)time per request 65.195在高并发的情况下,会导致数据库连接数不够,部分php获取不到连接而报错,或者是超过等待时间而报错
public function indexMysql()
{
DB::beginTransaction();
//通过for update 加排它锁
$shop = DB::table('shop')->where('id', '=', 1)->lockForUpdate()->first();
if ($shop->number > 0) {
if (DB::update("update shop set number = number - 1 where id = 1")) {
DB::commit();
} else {
DB::rollBack();//回滚并重试
usleep(100000);
$this->indexMysql();
}
} else {
DB::commit();
}
}
方案二:
使用文件锁实现。当用户抢到一件促销商品后先触发文件锁,防止其他用户进入,该用户抢到促销品后再解开文件锁,放其他用户进行操作。这样可以解决超卖的问题,但是会导致文件得I/O开销很大。
方案三:
使用redis的setnx来实现锁机制。但是并发大的情况下,锁的争夺会变多,导致响应越来越慢。(与第四种方案类似)。在数量少的情况下并不会出现问题,但是当并发达到(ab -n 1000 -c 200 就会出现请求非2XX的响应增多,1000 失败了 54) time per request 127.575
public function index()
{
//测试并发超卖现象
if (Redis::setnx(self::KEY, 1)) {//拿到了锁
$this->buy();
} else {
usleep(100000);//等会再去拿锁
//Log::info("未争夺到锁,睡眠100ms");
$this->index();
}
}
private function buy()
{
$shop = Shop::find(1);
if ($shop->number > 0) {
$shop->number --;
$shop->save();
}
Redis::del(self::KEY);
}
方案四:
使用redis队列来,用户过来直接入队列,然后再将操作更新到数据库
public function push()
{
//入队列
Redis::lpush(self::QUEUE, 1);
}
public function pop()
{
while (($key = Redis::rpop(self::QUEUE))) {
$shop = Shop::find(1);
if ($shop->number > 0) {
DB::update("update shop set number = number - 1 where id = 1")
}
}
}
文章来源于:https://www.cnblogs.com/dawuge/p/10480469.html