Heim  >  Artikel  >  Backend-Entwicklung  >  PHP+Redis realisiert die Snap-up-Funktion

PHP+Redis realisiert die Snap-up-Funktion

小云云
小云云Original
2018-02-09 09:21:003376Durchsuche

Zuvor haben wir Ihnen die Codefreigabe von PHP und Redis zur Implementierung der Mall-Flash-Sale-Funktion mitgeteilt. In diesem Artikel wird hauptsächlich der Implementierungscode für den PHP+Redis-Message-Queue-Rush-Kauf vorgestellt, auf den sich interessierte Freunde beziehen können Ich hoffe, es kann allen helfen.

Funktionsimplementierung:

1. Basierend auf der Redis-Warteschlange, um einen Überverkauf bei hoher Parallelität zu verhindern
2. Basierend auf einer MySQL-Transaktion mit exklusiver Sperre, um einen Überverkauf bei hoher Parallelität zu verhindern

Workflow basierend auf der Redis-Warteschlange:

1. Der Administrator erstellt eine Redis-Produktinventarwarteschlange basierend auf dem Inventar in der Warentabelle
2. Der Kunde greift auf die Flash-Sale-API zu
3. Der Webserver fragt zunächst den verbleibenden Inhalt des Inventarschlüssels aus der Redis-Produktinventarwarteschlange ab
4. Wenn in der Redis-Warteschlange noch Inventar vorhanden ist, erstellen Sie eine Bestellung in MySQL, entfernen Sie das Inventar, und der Snap-up ist erfolgreich
5. Es gibt keinen verbleibenden Bestand in der Redis-Warteschlange. Es wird angezeigt, dass der Bestand nicht ausreicht und der Eilkauf fehlschlägt.

Basierend auf MySQL-Transaktion und exklusivem Sperrworkflow:

1. Öffnen Sie die Transaktion
2. Fragen Sie den Lagerbestand ab und zeigen Sie ihn an: SELECT * FROM goods WHERE id = 1 FOR UPDATE
3. Inventar entfernen, implizit Schreibsperre setzen (exklusive Sperre): UPDATE waren SET counts = counts – 1 WHERE id = 1
5. Festschreiben, Sperre aufheben

Hinweis: Sie können die Freigabe festlegen Sperren Sie im zweiten Schritt, andernfalls kann es zu einem Deadlock kommen.

Code:


<?php
/**********************************************
* 抢购模块
*
* @author liubin
* @date 2016-02-10
*
* ab -n 1000 -c 100 http://192.168.16.73/Seckill/buy.php
*
*/
class seckill extends common
{

 private $_orderModel = null;
 private $_goodsModel = null;
 private $_redis = null;
 /*
  * 错误信息
 */
 protected $_error = &#39;&#39;;
 /**
  * 构造器
  *
 */
 public function __construct()
 {
  if($this->_orderModel === null){
   $this->_orderModel = new OrderModel();
  }
  if($this->_goodsModel === null){
   $this->_goodsModel = new GoodsModel();
  }
  if($this->_redis === null){
   $this->_redis = new QRedis(); 
  }
 }
 /*
  * 秒杀API
  * 
  * @author liubin
  * @date 2017-02-10
 */
 public function addQsec(){
  $gid = intval($_GET[&#39;gid&#39;]);
  $type = isset($_GET[&#39;type&#39;]) ? $_GET[&#39;type&#39;] : &#39;mysql&#39;;
  switch ($type) {
   case &#39;mysql&#39;:
    $this->order_check_mysql($gid);
    echo $this->getError();
    break;
   case &#39;redis&#39;:
    $this->order_check_redis($gid);
    echo $this->getError();
    break;
   case &#39;transaction&#39;:
    $this->order_check_transaction($gid);
    echo $this->getError();
    break;
   default:
    echo &#39;类型错误&#39;;
    break;
  }
 }
 /*
  * 获取错误信息
  * 
  * @author liubin
  * @date 2017-02-10
 */
 public function getError(){
  return $this->_error;
 }
 /*
  * 基于mysql验证库存信息
  * @desc 高并发下会导致超卖
  *
  * @author liubin
  * @date 2017-02-10
 */
 protected function order_check_mysql($gid){


  $model = $this->_goodsModel;
  $pdo = $model->getHandler();
  $gid = intval($gid);

  /*
   * 1:$sql_forlock如果不加事务,不加写锁:
   * 超卖非常严重,就不说了
   * 
   * 2:$sql_forlock如果不加事务,只加写锁:
   * 第一个会话读$sql_forlock时加写锁,第一个会话$sql_forlock查询结束会释放该行锁.
   * 第二个会话在第一个会话释放后读$sql_forlock的写锁时,会再次$sql_forlock查库存
   * 导致超卖现象产生
   *
  */
  $sql_forlock = &#39;select * from goods where id = &#39;.$gid .&#39; limit 1 for update&#39;;
  //$sql_forlock = &#39;select * from goods where id = &#39;.$gid .&#39; limit 1&#39;;
  $result = $pdo->query($sql_forlock,PDO::FETCH_ASSOC);
  $goodsInfo = $result->fetch();

  if($goodsInfo[&#39;counts&#39;]>0){

   //去库存
   $gid = $goodsInfo[&#39;id&#39;];
   $sql_inventory = &#39;UPDATE goods SET counts = counts - 1 WHERE id = &#39;.$gid;
   $result = $this->_goodsModel->exect($sql_inventory);
   if($result){
    //创订单
    $data    = [];
    $data[&#39;order_id&#39;] = $this->_orderModel->buildOrderNo();
    $data[&#39;goods_id&#39;] = $goodsInfo[&#39;id&#39;];
    $data[&#39;addtime&#39;] = time();
    $data[&#39;uid&#39;]  = 1;
    $order_rs = $this->_orderModel->create_order($data);
    if($order_rs){
     $this->_error = &#39;购买成功&#39;;
     return true;
    }
   }
  }

  $this->_error = &#39;库存不足&#39;;
  return false;

 }
 /*
  * 基于redis队列验证库存信息
  * @desc Redis是底层是单线程的,命令执行是原子操作,包括lpush,lpop等.高并发下不会导致超卖
  *
  * @author liubin
  * @date 2017-02-10
 */
 protected function order_check_redis($gid){
  $goodsInfo = $this->_goodsModel->getGoods($gid);
  if(!$goodsInfo){
   $this->_error = &#39;商品不存在&#39;;
   return false;
  }
  $key = &#39;goods_list_&#39;.$goodsInfo[&#39;id&#39;];
  $count = $this->_redis->getHandel()->lpop($key);
  if(!$count){
   $this->_error = &#39;库存不足&#39;;
   return false;
  }
  //生成订单
  $data    = [];
  $data[&#39;order_id&#39;] = $this->_orderModel->buildOrderNo();
  $data[&#39;goods_id&#39;] = $goodsInfo[&#39;id&#39;];
  $data[&#39;addtime&#39;] = time();
  $data[&#39;uid&#39;]  = 1;
  $order_rs = $this->_orderModel->create_order($data);

  //库存减少
  $gid = $goodsInfo[&#39;id&#39;];
  $sql = &#39;UPDATE goods SET counts = counts - 1 WHERE id = &#39;.$gid;
  $result = $this->_goodsModel->exect($sql);
  $this->_error = &#39;购买成功&#39;;
  return true;
 }
 /*
  * 基于mysql事务验证库存信息
  * @desc 事务 和 行锁 模式,高并发下不会导致超卖,但效率会慢点
  * @author liubin
  * @date 2017-02-10


  说明:
  如果$sql_forlock不加写锁,并发时,$sql_forlock查询的记录存都大于0,可以减库存操作.
  如果$sql_forlock加了写锁,并发时,$sql_forlock查询是等待第一次链接释放后查询.所以库存最多就是5

 */
 protected function order_check_transaction($gid){

  $model = $this->_goodsModel;
  $pdo = $model->getHandler();
  $gid = intval($gid);

  try{
   $pdo->beginTransaction();//开启事务处理


   /*
    * 1:$sql_forlock如果只加事务,不加写锁:
    * 开启事务
    * 因为没有加锁,读$sql_forlock后,并发时$sql_inventory之前还可以再读。
    * $sql_inventory之后和commit之前才会锁定
    * 出现超卖跟事务的一致性不冲突
    * 
    *
    * 2:$sql_forlock如果加了事务,又加读锁:
    * 开启事务
    * 第一个会话读$sql_forlock时加读锁,并发时,第二个会话也允许获得$sql_forlock的读锁,
    * 但是在第一个会话执行去库存操作时(写锁),写锁便会等待第二个会话的读锁,第二个会话执行写操作时,写锁便会等待第一个会话的读锁,
    * 出现死锁

    * 3:$sql_forlock如果加了事务,又加写锁:
    * 开启事务
    * 第一个会话读$sql_forlock时加写锁,直到commit才会释放写锁,并发查询不会出现超卖现象。
    *
   */

   $sql_forlock = &#39;select * from goods where id = &#39;.$gid .&#39; limit 1 for update&#39;;
   //$sql_forlock = &#39;select * from goods where id = &#39;.$gid .&#39; limit 1 LOCK IN SHARE MODE&#39;;
   //$sql_forlock = &#39;select * from goods where id = &#39;.$gid .&#39; limit 1&#39;;
   $result = $pdo->query($sql_forlock,PDO::FETCH_ASSOC);
   $goodsInfo = $result->fetch();

   if($goodsInfo[&#39;counts&#39;]>0){

    //去库存
    $gid = $goodsInfo[&#39;id&#39;];
    $sql_inventory = &#39;UPDATE goods SET counts = counts - 1 WHERE id = &#39;.$gid;
    $result = $this->_goodsModel->exect($sql_inventory);

    if(!$result){
     $pdo->rollBack();
     $this->_error = &#39;库存减少失败&#39;;
     return false;
    }

    //创订单
    $data    = [];
    $data[&#39;id&#39;]   = &#39;null&#39;;
    $data[&#39;order_id&#39;] = $this->_orderModel->buildOrderNo();
    $data[&#39;goods_id&#39;] = $goodsInfo[&#39;id&#39;];
    $data[&#39;uid&#39;]  = &#39;abc&#39;;
    $data[&#39;addtime&#39;] = time();

    $sql = &#39;insert into orders (id,order_id,goods_id,uid,addtime) values (&#39;.$data[&#39;id&#39;].&#39;,"&#39;.$data[&#39;order_id&#39;].&#39;","&#39;.$data[&#39;goods_id&#39;].&#39;","&#39;.$data[&#39;uid&#39;].&#39;","&#39;.$data[&#39;addtime&#39;].&#39;")&#39;;   
    $result = $pdo->exec($sql);
    if(!$result){
     $pdo->rollBack();
     $this->_error = &#39;订单创建失败&#39;;
     return false;
    }
    $pdo->commit();//提交
    $this->_error = &#39;购买成功&#39;;
    return true;

   }else{
    $this->_error = &#39;库存不足&#39;;
    return false;
   }
  }catch(PDOException $e){
   echo $e->getMessage();
   $pdo->rollBack();
  }


 }
 /*
  * 创建订单
  * mysql 事物处理,也可以用存储过程
  *
 */
 private function create_order($goodsInfo){
  //生成订单
  $data    = [];
  $data[&#39;order_id&#39;] = $this->_orderModel->buildOrderNo();
  $data[&#39;goods_id&#39;] = $goodsInfo[&#39;id&#39;];
  $data[&#39;addtime&#39;] = time();
  $data[&#39;uid&#39;]  = 1;
  $order_rs = $this->_orderModel->create_order($data);

  //库存减少
  $gid = $goodsInfo[&#39;id&#39;];
  $sql = &#39;UPDATE goods SET counts = counts - 1 WHERE id = &#39;.$gid;
  $result = $this->_goodsModel->exect($sql);
  return true;
 }
}
Verwandte Empfehlungen:


PHP implementiert die gleichzeitige Snap-Up-Funktion durch Sperren

So verwenden Sie JS-Code, um die Webseiten-Snap-up-Funktion zu implementieren

Wie Sie mit hohen gleichzeitigen Anforderungen für Snap-up-Funktionen umgehen php

Das obige ist der detaillierte Inhalt vonPHP+Redis realisiert die Snap-up-Funktion. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn