专业的编程技术博客社区

网站首页 > 博客文章 正文

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

baijin 2024-08-21 11:24:59 博客文章 6 ℃ 0 评论

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

问题: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 毫秒级定时器,最大支持延迟一天。

点击了解更多去学习:非常使用的代码优化,怎么才能写好代码

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表