swoole定时器+异步任务处理订单超时

版权声明:本文为博主原创文章,如若转载,请注明出处。 https://blog.csdn.net/qq_37837134/article/details/88396320

背景:一个紧俏的商品,短时间内抢购,需要支付,技术方案,

问题:1、防止超卖

           2、防止重复购买

           3、订单超时处理

总结:订单限额,一人一单,订单超时处理

本方案在不使用专业的队列的情况下,简单实现上述三个问题

前端:

1、ajax 发送请求,加锁,API异步完成,释放锁

var lock_status = true;

    $('.btutton').click(function () {

        txt=$("input").val();
        if(lock_status){
            //上锁
            lock_status = false;
            $.post("http://www.zyhuadu.com",{suggest:txt},function(result){
                //回调完成 释放锁
                lock_status = true;
            });
        }
    })

后端:

技术栈:redis + swoole

第一阶段:

1、限制人数阀值

2、限制重复报名

技术方案:

1.1 、采用redis的计数器实现,具有原子性,保证不超过阀值;

1.2、采用redis的 set(集合),特性:不允许重复的成员

第二阶段:

支付问题(占位) (订单超时处理)

需求:用户报名之后,过指定时间,回访检查是否支付,若未支付则位子让出

方案一:redis 模拟实现 https://github.com/chenlinzhong/php-delayqueue

方案二:rabbitmq https://help.aliyun.com/document_detail/43349.html?spm=a2c4g.11186623.2.23.71734fed7EdaUY

方案三:Mysql+crontab 表设计时间字段,定时任务执行,查对应数据,判断状态

方案四:异步任务+swoole定时器

本文在第二阶段,选用方案四,采用Esayswoole框架,运用mysql,redis连接池,以及swoole 毫秒级定时器,异步任务

    /**
     * Explain: 订单限额,一人一单,订单超时处理
     * User: 奔跑吧笨笨
     * Date: 2019/3/11
     * Time: 3:54 PM
     */
    public function shopGoods()
    {

        $data['token'] = $this->request()->getQueryParam('token');
        $data['goods_id'] = $this->request()->getQueryParam('goods_id');

        $validate = new Validate();
        $validate->addColumn('token')->required('非法访问,请检查token');
        $validate->addColumn('goods_id')->required('请选择商品');

        if(!$validate->validate($data)){
            $this->writeJson(4000,$validate->getError()->__toString(),'error');
        }
        //2、Token 获取用户信息
        $token_obj = new Token($this->getRedis());

        $user_info = $token_obj->getToken($data['token']);
        if(!$user_info){
            $this->writeJson(4000,$user_info,'token失效');
        }

        //获取DB对象
        $goods_db = new GoodsModel($this->getDbConnection());
        $goods_order_db = new GoodsOrderModel($this->getDbConnection());

        //商品信息
        $goods_info = $goods_db->getOne($data['goods_id']);
        //商品库存
        $goods_num = $goods_order_db->goodsInventory($data['goods_id']);

        if($goods_info){
            //商品是否有库存
            if($goods_info['number'] > $goods_num){

                //用户商品是否重复下单
                $key_set = self::ORDER_REDIS_ZSET.$goods_info['id'];
                //生成用户值,同一商品
                $key_set_value = md5($goods_info['id'].$user_info['id']);
                if($this->getRedis()->sAdd($key_set,$key_set_value)){

                    $key_num = self::ORDER_REDIS_NUM.$goods_info['id'];
                    $number = $this->getRedis()->incr($key_num);

                    if($number <= $goods_info['number']){
                        $orderData['goods_name'] = $goods_info['goods_name'];
                        $orderData['goods_id'] = $goods_info['id'];
                        $orderData['number'] = 1;
                        $orderData['uid'] = $user_info['user']['id'];
                        $orderData['ctime'] = date('Y-m-d H:i:s');

                        //下单 入库
                        $result = $goods_order_db->insert($orderData);

                        if($result){
                            //设置定时器,1分钟后执行
                            \EasySwoole\Component\Timer::getInstance()->after(1*60*1000, function () use($result,$key_num,$key_set,$key_set_value){
                                $task_data['order_id'] = $result;
                                $task_data['cache_key'] = $key_num;
                                $task_data['set_cache_key'] = $key_set;
                                $task_data['set_cache_value'] = $key_set_value;
                                //投递异步任务
                                $taskClass = new TaskOrder(json_encode($task_data));
                                \EasySwoole\EasySwoole\Swoole\Task\TaskManager::async($taskClass);
                            });
                            $this->writeJson(200,$result,'success 下单成功');
                        }else{
                            $this->writeJson(4000,$orderData,'error 下单失败');
                        }
                    }else{
                        $this->getRedis()->decr($key_num);
                        $this->getRedis()->expire($key_num,self::CACHE_FAILURE_TIME);
                        $this->writeJson(4000,'','reids 计数器判断,该商品已售馨');
                    }
                }else{
                    $this->writeJson(4000,'','抱歉,您已下单');
                }
            }else{
                $this->writeJson(4000,'','该商品已售馨');
            }
        }else{
            $this->writeJson(4000,'','该商品不存在或已下架');
        }
    }

⚠️:采用了mysql 和redis 双重判断,因为最近项目mysql 主从同步出现异常,导致读从库没有限制住,造成超卖,故优化为此方案。

<?php
/**
 * Created by PhpStorm.
 * User: 奔跑吧笨笨
 * Date: 2019-03-10
 * Time: 15:58
 */

namespace App\Task;

use App\Model\User\UserModelWithDb;
use EasySwoole\EasySwoole\Swoole\Task\AbstractAsyncTask;

use Swoole\Coroutine\Redis;
use App\Utility\Pool\RedisPool;
use EasySwoole\EasySwoole\Config;
use App\Utility\Pool\MysqlObject;
use App\Utility\Pool\MysqlPool;
use EasySwoole\Component\Pool\PoolManager;
use App\Model\Goods\GoodsModel;
use App\Model\Goods\GoodsOrderModel;

class TaskOrder extends AbstractAsyncTask
{
    function run($taskData, $taskId, $fromWorkerId,$flags = null)
    {
        $taskData = json_decode($taskData,true);
        //查询支付状态,并修改订单
        $timeout = Config::getInstance()->getConf('web.MYSQL.POOL_TIME_OUT');
        $mysqlObject = PoolManager::getInstance()->getPool(MysqlPool::class)->getObj($timeout);
        $goods_order_db = new GoodsOrderModel($mysqlObject);
        $order_info = $goods_order_db->getOne($taskData['order_id']);

        if($order_info){
            if($order_info['pay_status'] == 0){
                //未支付,订单状态修改为2,且计数器-1
                $result = $goods_order_db->updatePayStatus($taskData['order_id'],2);
                if($result){
                    //计数器同步
                    $redis_timeout = Config::getInstance()->getConf('web.REDIS.POOL_TIME_OUT');
                    $redis = PoolManager::getInstance()->getPool(RedisPool::class)->getObj($redis_timeout);
                    $key = $taskData['cache_key'];
                    $redis->decr($key);

                    //重复下单 队列清除
                    $set_key = $taskData['set_cache_key'];
                    $set_key_value = $taskData['set_cache_value'];
                    $redis->srem($set_key,$set_key_value);
                }
            }
        }

        //回收mysql连接句柄
        PoolManager::getInstance()->getPool(MysqlPool::class)->recycleObj($goods_order_db);
        //回收redis连接句柄
        PoolManager::getInstance()->getPool(RedisPool::class)->recycleObj($redis);
        // TODO: Implement run() method.
    }

    function finish($result, $task_id)
    {
        echo "task模板任务完成\n";
        return 1;
        // TODO: Implement finish() method.
    }


}

以上伪代码,测试使用。

注意:swoole 毫秒级定时器,最大支持延迟一天。

特此感谢:swoole开发组成员 ,swoole的出现拓宽了PHP的道路。

我为人人,人人为我,美美与共,天下大同。

猜你喜欢

转载自blog.csdn.net/qq_37837134/article/details/88396320
今日推荐