利用Redis鎖解決高併發問題

這裏咱們主要利用Redissetnx的命令來處理高併發。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鎖是能夠有效的處理高併發的。

這裏在加鎖的時候實際上是能夠不須要判斷過時時間的,這裏咱們爲了不形成死鎖,因此加一個過時時間的判斷。當過時的時候主動刪除該鎖。

圖片描述

相關文章
相關標籤/搜索