這裏咱們主要利用Redis
的setnx
的命令來處理高併發。php
setnx
有兩個參數。第一個參數表示鍵。第二個參數表示值。若是當前鍵不存在,那麼會插入當前鍵,將第二個參數作爲值。返回1
。若是當前鍵存在,那麼會返回0
。
建立庫存表mysql
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
設置初始庫存爲10redis
建立訂單表sql
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)
發現存在幾個訂單都是操做的同一個庫存數據,這樣就可能引發超賣的狀況。fetch
修改代碼加入redis
鎖進行數據控制this
<?php /** * Created by PhpStorm. * User: daisc * Date: 2018/7/23 * Time: 14:45 */ 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(); } /** * @function 加鎖 * @param $key 鎖名稱 * @param $expTime 過時時間 */ 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<time()) { $this->del($key); return $this->_redis->setnx($key,time()+$expTime); } return false; } } /** * @param $key 解鎖 */ 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
測試,查看測試結果spa
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
鎖是能夠有效的處理高併發的。
這裏在加鎖的時候實際上是能夠不須要判斷過時時間的,這裏咱們爲了不形成死鎖,因此加一個過時時間的判斷。當過時的時候主動刪除該鎖。