1. 程式人生 > >PHP+Redis實現高併發應用商城秒殺功能

PHP+Redis實現高併發應用商城秒殺功能

開發的電子商務平臺也很多了,剛好有時間就整理一下鞏固加深理解

1、安裝redis,根據自己的php版本安裝對應的redis擴充套件(此步驟簡單的描述一下)

1.1.安裝php_igbinary.dll,php_redis.dll擴充套件此處需要注意你的php版本如圖:

1.2.php.ini檔案新增extension=php_igbinary.dll;extension=php_redis.dll兩處擴充套件

ok此處已經完成第一步redis環境搭建完成看看phpinfo

2、專案中實際使用redis

2.1.第一步配置redis引數如下,redis安裝的預設埠為6379: 

<?php
/* 資料庫配置 */
return array(
  'DATA_CACHE_PREFIX' => 'Redis_',//快取字首
  'DATA_CACHE_TYPE'=>'Redis',//預設動態快取為Redis
  'DATA_CACHE_TIMEOUT' => false,
  'REDIS_RW_SEPARATE' => true, //Redis讀寫分離 true 開啟
  'REDIS_HOST'=>'127.0.0.1', //redis伺服器ip,多臺用逗號隔開;讀寫分離開啟時,第一臺負責寫,其它[隨機]負責讀;
  'REDIS_PORT'=>'6379',//埠號
  'REDIS_TIMEOUT'=>'300',//超時時間
  'REDIS_PERSISTENT'=>false,//是否長連線 false=短連線
  'REDIS_AUTH'=>'',//AUTH認證密碼 
);
?>

2.2.實際函式中使用redis:

/**
    * redis連線
    * @access private
    * @return resource
    * @author bieanju
    */
  private function connectRedis(){
    $redis=new \Redis();
    $redis->connect(C("REDIS_HOST"),C("REDIS_PORT"));    
    return $redis;
  }

2.3. 秒殺的核心問題是在大併發的情況下不會超出庫存的購買,這個就是處理的關鍵所以思路是第一步在秒殺類的先做一些基礎的資料生成:

//現在初始化裡面定義後邊要使用的redis引數
public function _initialize(){
    parent::_initialize();
    $goods_id = I("goods_id",'0','intval');   
    if($goods_id){
      $this->goods_id = $goods_id;
      $this->user_queue_key = "goods_".$goods_id."_user";//當前商品佇列的使用者情況
      $this->goods_number_key = "goods".$goods_id;//當前商品的庫存佇列
    }
    $this->user_id = $this->user_id ? $this->user_id : $_SESSION['uid'];   
  }

2.4. 第二步就是關鍵所在,使用者在進入商品詳情頁前先將當前商品的庫存進行佇列存入redis如下:

/**
  * 訪問產品前先將當前產品庫存佇列
  * @access public
  * @author bieanju
  */
  public function _before_detail(){
    $where['goods_id'] = $this->goods_id;
    $where['start_time'] = array("lt",time());
    $where['end_time'] = array("gt",time());
    $goods = M("goods")->where($where)->field('goods_num,start_time,end_time')->find();
    !$goods && $this->error("當前秒殺已結束!");
    if($goods['goods_num'] > $goods['order_num']){
      $redis = $this->connectRedis();
      $getUserRedis = $redis->hGetAll("{$this->user_queue_key}");
      $gnRedis = $redis->llen("{$this->goods_number_key}");
      /* 如果沒有會員進來佇列庫存 */
      if(!count($getUserRedis) && !$gnRedis){      
        for ($i = 0; $i < $goods['goods_num']; $i ++) {
          $redis->lpush("{$this->goods_number_key}", 1);
        }
      }
      $resetRedis = $redis->llen("{$this->goods_number_key}");
      if(!$resetRedis){
        $this->error("系統繁忙,請稍後搶購!");
      }
    }else{
      $this->error("當前產品已經秒殺完!");
    }
      
  }

接下來要做的就是用ajax來非同步的處理使用者點選購買按鈕進行符合條件的資料進入購買的排隊佇列(如果當前使用者沒在當前產品使用者的佇列就進入排隊並且pop一個庫存佇列,如果在就丟擲,):

/**
   * 搶購商品前處理當前會員是否進入佇列
   * @access public
   * @author bieanju
   */
  public function goods_number_queue(){
    !$this->user_id && $this->ajaxReturn(array("status" => "-1","msg" => "請先登入"));
    $model = M("flash_sale");
    $where['goods_id'] = $this->goods_id;
    $goods_info = $model->where($where)->find();
    !$goods_info && $this->error("對不起當前商品不存在或已下架!"); 
    /* redis 佇列 */
    $redis = $this->connectRedis();
    /* 進入佇列 */
    $goods_number_key = $redis->llen("{$this->goods_number_key}");
    if (!$redis->hGet("{$this->user_queue_key}", $this->user_id)) {
      $goods_number_key = $redis->lpop("{$this->goods_number_key}");
    }
      
    if($goods_number_key){
      // 判斷使用者是否已在佇列
      if (!$redis->hGet("{$this->user_queue_key}", $this->user_id)) {
        // 插入搶購使用者資訊
        $userinfo = array(
          "user_id" => $this->user_id,
          "create_time" => time()
        );        
        $redis->hSet("{$this->user_queue_key}", $this->user_id, serialize($userinfo));
        $this->ajaxReturn(array("status" => "1"));
      }else{
        $modelCart = M("cart");
        $condition['user_id'] = $this->user_id;
        $condition['goods_id'] = $this->goods_id;
        $condition['prom_type'] = 1;
    $cartlist = $modelCart->where($condition)->count();
        if($cartlist > 0){
          $this->ajaxReturn(array("status" => "2"));
        }else{
          
          $this->ajaxReturn(array("status" => "1"));
          
        }
          
      }
        
    }else{
      $this->ajaxReturn(array("status" => "-1","msg" => "系統繁忙,請重試!"));
    }
  }

附加一個除錯的函式,刪除指定佇列值:

public function clearRedis(){
     set_time_limit(0);
     $redis = $this->connectRedis();
     //$Rd = $redis->del("{$this->user_queue_key}");
     $Rd = $redis->hDel("goods49",'使用者id'');
     $a = $redis->hGet("goods_49_user", '使用者id');
     if(!$a){
       dump($a);
     }
      
     if($Rd == 0){
       exit("Redis佇列已釋放!");      
     }
   }

走到此處的時候秒殺的核心基本就完了,細節還需要自己在去完善,像購物車這邊的處理還有訂單的處理,好吧開始跑程式利用apache自身的ab可以進行簡單的模擬併發測試如下:

跑起來,我擦跑步起來redis沒有任何反應,此時還少一步重要的步驟就是開啟redis服務,請根據自己的系統下一個redisbin_x32或者redisbin_x64的redis服務管理工具,點選redis-server.exe,ok至此全部完成如下圖:

轉載地址:http://www.jb51.net/article/134662.htm