问题: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(1601000, 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 毫秒级定时器,最大支持延迟一天。
点击理解更多去学习:非常利用的代码优化,怎么才能写好代码