使用Redis类库处理一般的抢购(秒杀)活动示例

1、创建抢购活动Redis类库文件

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

namespace appaseservice;


use mikkle	p_redisRedisHashInfoBase;
use thinkException;

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	p_worker;

use mikkle	p_masterException;
use mikkle	p_masterLog;

/**
 * title  定时队列类
 * Class TimingWorkerBase
 * @package mikkle	p_worker
 * 创建定时队列类并继承使用方法
 * class Test extends TimingWorkerBase
 * {
 * protected function runHandle($data)
 * {
 * Log::notice(  "测试".RandNumCenter::getTimeString()  );
 * }
 * }
 *
 * 添加方法定时队列方法
 *  appworkerTest::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;
        }
    }
}
原文地址:https://www.cnblogs.com/swmin/p/9950913.html