1. 程式人生 > >使用Redis類庫處理一般的搶購(秒殺)活動示例

使用Redis類庫處理一般的搶購(秒殺)活動示例

1、建立搶購活動Redis類庫檔案

<?php
/**
 * Created by PhpStorm.
 */

namespace app\base\service;


use mikkle\tp_redis\RedisHashInfoBase;
use think\Exception;

class ScheduleDetail  extends RedisHashInfoBase
{
    protected $table="gopar_schedule_detail";  //資料表的
    protected $pk = "id"; //資料表的主鍵


    public
function _initialize() { //判斷資料存在 並設定檢查週期10分鐘 if (!$this->checkLock("dataExists") && !$this->checkTableDataExists()){ throw new Exception("相關產品資料不存在"); }else{ //設定檢查鎖10分鐘 $this->setLock("dataExists",600); } //如果資料不存在 初始化讀取資料
if (!$this->checkExists()){ $this->initTableData(); } } public function getScheduleCenter() { return Schedule::instance( $this->getInfoFieldValue("schedule_id")); } public function __destruct() { //設定15天自動回收redis $this
->setExpire((int)$this->getScheduleCenter()->getInfoFieldValue("end_time")+3600*24*15); } }

2、在服務層或者控制器處理搶購邏輯

    public function index($data=["user_id"=>1,"ticket_detail_id"=>1,"buy_num"=>1]){
        try {
            //檢測資料存在
            if (!$this->checkArrayValueEmpty($data,["user_id","ticket_detail_id","buy_num"])){
                throw  new  Exception($this->error);
            }

            $user_id= $data["user_id"] ;              //使用者Id
            $ticket_detail_id = $data["ticket_detail_id"] ;  //產品Id
            $buy_num = $data["buy_num"] ;   //購買數量

            $infoCenter= ScheduleDetail::instance( $ticket_detail_id );
            $scheduleDetailInfo =$infoCenter->getInfoList();
            //修改資料庫後 需要執行initTableData()方法重新初始化 推薦寫到Hook裡
           // $infoCenter->initTableData();
            if ( $infoCenter->getInfoFieldValue( "hot_schedule")){
                //熱門搶購隨機過濾隨機過濾
                if (!in_array(rand(100, 200) % 11, [1, 3, 5, 7, 9])) {
                    throw  new  Exception("搶票人數眾多 ,你被擠出搶購隊伍,還有餘票,請重新再搶");
                };
            }
            // 這裡判斷 購買數量和銷售日期 不符合就  throw  new  Exception
            if (!true){
                throw  new  Exception("這裡寫不符合原因");
            }
            if (((int)$infoCenter->getInfoFieldValue("{$user_id}_num")+$buy_num)>$scheduleDetailInfo["limit_num"] ){
                throw  new  Exception("你超過最大購買數量");
            }
            if ($infoCenter->setInfoFieldIncre("pay_num",$buy_num) >$scheduleDetailInfo["limit_num"] ){
                //
                $infoCenter->setInfoFieldIncre("pay_num", -$buy_num);
                throw  new  Exception("對不起,票已經賣光了!");
            }
            //這裡寫主邏輯 啟用事務功能建立訂單 
            //事務參見下節原始碼
            
           
            //升級已銷售數量
            $infoCenter->updateTableData(["pay_num"]);
            
            //在這裡推薦埋鉤子   處理訂單完成的後續事情

             //返回結果

        } catch (Exception $e) {
            Log::error($e->getMessage());
            return ShowCode::jsonCodeWithoutData(1008, $e->getMessage());
        }
    }


}

3.定時佇列判斷訂單是否處理完成 校準剩餘庫存

<?php
/**
 * Created by PhpStorm.
 */

namespace mikkle\tp_worker;

use mikkle\tp_master\Exception;
use mikkle\tp_master\Log;

/**
 * title  定時佇列類
 * Class TimingWorkerBase
 * @package mikkle\tp_worker
 * 建立定時佇列類並繼承使用方法
 * class Test extends TimingWorkerBase
 * {
 * protected function runHandle($data)
 * {
 * Log::notice(  "測試".RandNumCenter::getTimeString()  );
 * }
 * }
 *
 * 新增方法定時佇列方法
 *  \app\worker\Test::add(["name"=>"mikkle",],30);
 */

abstract class TimingWorkerBase extends WorkerBase
{
    protected $listName;
    protected $listData;
    protected $listNum;
    protected $lockName;

    public function _initialize($options = [])
    {
        $this->listData = "{$this->listName}_data";
        $this->listNum = "{$this->listName}_num";
    }


    /**
     *      * 快速定時任務
     *
     * 當命令列未執行 直接執行
     * description add
     * @param $data
     * @param $runTime
     * @param array $options
     * @param string $handleName
     * @return bool
     */
    static public function add($data, $runTime = 0, $handleName = "run", $options = [])
    {
        try {
            $data = json_encode($data);
            $instance = static::instance($options);
            switch (true) {
                case (self::checkCommandRun()):
                    $time = $instance->getRunTime($runTime);
                    $num = $instance->redis()->incre($instance->listNum);
                    Log::notice("添加了 $num 號定時任務");
                    $instance->redis()->zAdd($instance->listName, [$time => $num]);
                    $instance->redis()->hSet($instance->listData, $num, $data);
                    Log::notice("Timing Command service start work!!");
                    $instance->runWorker($handleName);
                    break;
                default:
                    Log::notice("Timing Command service No away!!");
                    $instance->runHandle($data);
            }
            return true;
        } catch (Exception $e) {
            Log::error($e->getMessage());
            return false;
        }
    }

    /**
     * 命令列執行的方法
     */
    static public function run()
    {
        try {
            $i = 0;
            $instance = static::instance();
            //讀取並刪除定時任務
            $workList = $instance->redis()->zRangByScore($instance->listName, 0, time());
            $instance->redis()->zDelete($instance->listName, $workList);
            //剩餘任務數
            $re = $instance->redis()->zCard($instance->listName);
            if ( $workList ){
                foreach ($workList as $num) {
                    try {
                        $redisData = $instance->redis()->hGet($instance->listData, $num);
                        if ($redisData) {
                            $data = json_decode($redisData, true);
                            $result = $instance->runHandle($data);
                            Log::notice("執行{$num}編號任務");
                            if ($instance->saveLog) {
                                $instance->saveRunLog($result, $data);
                            }
                            $instance->redis()->hDel($instance->listData, $num);
                        }
                    } catch (Exception $e) {
                        Log::error($e->getMessage());
                        $instance->redis()->zAdd($instance->listData, [(time() + 300) => $num]);
                    }
                    $i++;
                    sleep(1);
                }
            }
            if ( $re=== 0) {
                $instance->clearWorker();
            }
            echo "執行了{$i}次任務,剩餘未執行任務[{$re}]項" . PHP_EOL;
            Log::notice("執行了{$i}次任務,剩餘未執行任務[{$re}]項");
        } catch (Exception $e) {
            //Log::error($e);
            Log::error($e->getMessage());
            echo($e->getMessage());
        }
    }


    protected function getRunTime($time = 0)
    {
        $now = time();
        switch (true) {
            case ($time == 0):
                return $now;
                break;
            case (is_int($time) && 30 * 3600 * 24 > $time):
                return $now + $time;
                break;
            case (is_int($time) && $now < $time):
                return $time;
                break;
            default:
                return $now + (int)$time;
        }
    }
}