laravel+redis+mysql 并发秒杀测试

2 篇文章 0 订阅

laravel+Redis+Msysql商品秒杀与超卖

一,添加路由

// ======== 商品秒杀接口 ============= start ==================
Route::any('seckill/goods_order',   'App\SeckillController@goodsOrder')->name('seckill.goods_order'); // 抢购下单

Route::any('seckill/goods_stock',   'App\SeckillController@goodsStock')->name('seckill.goods_stock'); // redis添加库存


// ======== 商品秒杀接口 =============  end  ==================

 

二,在app\Http\Controllers\App添加SeckillController.php

<?php
namespace App\Http\Controllers\App;
/**
 * redis
 * 1、抢购下单
 * 2、redis添加库存
 */
use App\Http\Controllers\BaseController;
use App\Logics\Services\src\SeckillService;

class SeckillController extends BaseController {
    public function __construct(SeckillService $service)
    {
        $this->service = $service;
    }
    /**
     * 抢购下单
    */
    public function goodsOrder() {
        try
        {
            return $this->service->goodsOrder();
        }
        catch (\Exception $e)
        {
            return $this->result(['file'=>$e->getFile(), 'line'=>$e->getLine()], $e->getCode(), $e->getMessage());
        }
    }
    /**
     * redis添加库存
     */
    public function goodsStock() {
        try
        {
            return $this->service->goodsStock();
        }
        catch (\Exception $e)
        {
            return $this->result(['file'=>$e->getFile(), 'line'=>$e->getLine()], $e->getCode(), $e->getMessage());
        }
    }

}

 

三,在app\Logics\Models添加Seckill.php

<?php

namespace App\Logics\Models;

class Seckill extends Base
{
    protected $table = 'goods';
    //
    protected $fillable = [
        'name','imgs', 'price', 'num', 'status', 'desc','stock','sort'
    ];

    public $fields = [
        'id',
        'name','imgs', 'price', 'num', 'status', 'desc','stock','sort'
    ];

}

 

四,在app\Logics\Models添加Order.php

<?php

namespace App\Logics\Models;

class Order extends Base
{
    protected $table = 'orders';

    protected $fillable = [
        'user_id', 'type', 'money','out_trade_no', 'pay_time','desc', 'status'
    ];

    public $fields = [
        'id',
        'user_id', 'type', 'money','out_trade_no', 'pay_time','desc', 'status'
    ];

}

 

五,app\Logics\Repositories\src添加SeckillRepository.php

<?php
namespace App\Logics\Repositories\src;

use App\Logics\Models\Seckill;
use App\Logics\Repositories\Repository;
use Illuminate\Support\Facades\Cache;

class SeckillRepository extends Repository {
    public function model() {
        return Seckill::class;
    }
    // 商品列表
    public function goodsList() {

        $list =$this->model
            ->select(['id','stock'])
            ->orderBy('id', 'asc')
            ->get(); 

        return $this->toArr($list)
    }

    // 商品信息
    public function goodsInfo($goods_id) {

        $info =$this->model->where('id',$goods_id)
            ->first();

        return $info;
    }

}

 

六,app\Logics\Services\src添加SeckillService.php

<?php
/**
 * 各类操作信息接口
 * 1、任务中心任务列表
 * 2、完成任务领取奖励
 * 3、幸运币兑换
 * 等等信息接口
 */

namespace App\Logics\Services\src;

use App\Logics\Models\Order;
use App\Logics\Models\Seckill;
use App\Logics\Repositories\src\SeckillRepository;
use App\Logics\Services\Service;
use App\Logics\Traits\QueueTrait;
use Illuminate\Support\Facades\DB;

class SeckillService extends Service {
    use QueueTrait;
    protected $seckill;

   public function Repositories() {
      return [
         'seckill' => SeckillRepository::class,
      ];
   }

    /**
     * 抢购下单
     */
    public function goodsOrder() {
        $user_id = 1;  //用户id
        $goods_id = 11; //商品id
        $buy_num = 1; //购买数量
        $count = $this->GetStockQueue($goods_id,$buy_num);    //每次抢单
        if($count !== null){
            $do_order = $this->doOrder($goods_id, $count, $user_id);
            if($do_order === true){
                return $this->result('抢购成功',0,'ok');
            }
        }else{
            throw new \Exception('库存不足!', 2000);
        }
    }

    /**
     * redis添加库存
     */
    public function goodsStock() {
        $goods = $this->seckill->goodsList(); //商品列表
        if (!$goods) {
            throw new \Exception('商品列表为空!', 2000);
        }
        foreach($goods as $k=>$v){
            if($v['stock'] > 0){
                $stock_add = $this->AddStockQueue($v['id'],$v['stock']);
            }
        }
        return $this->result(true,0,'ok');
    }

    // 下单更新库存
    public function doOrder($goods_id, $goodsNum, $user_id)
    {
        $goods = $this->seckill->goodsInfo($goods_id); //商品信息
        $orderNo = $this->orderNo();
        $number = $goods['stock'] - $goodsNum;
        $money = $goodsNum * $goods['price'];
        if ($number < 0) {
            throw new \Exception('没有库存了!', 2000);
        }

        try {
            DB::beginTransaction();   //开启事务
            /*添加订单start*/
            $order_val =  [
                'user_id' => $user_id,
                'type' => 'balance',        
                'money' => $money,
                'out_trade_no' => $orderNo,
                'desc' => '用户购买商品生成的订单'
            ];
            $orderadd = Order::create($order_val);
            /*添加订单end*/

            /*减少商品库存start*/
            $stock_edit = Seckill::where('id',$goods_id)->update(['stock'=>$number]);
            /*减少商品库存end*/

            DB::commit();
            return true;
        } catch (\Exception $e) {
            DB::rollBack();
            $err_msg = $e->getMessage();
            return $err_msg;
        }
    }

    // 生成订单号
    public function orderNo()
    {
        return date('Ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
    }

}

 

七,app\Logics\Traits添加QueueTrait.php

 

<?php
namespace App\Logics\Traits;

use App\Logics\Repositories\src\SeckillRepository;
use Illuminate\Support\Facades\Redis;

trait QueueTrait
{
    /**
     * 添加商品库存队列
     * @param int $good_id 商品ID
     * @param array $num 库存数量
     * @return int 队列长度(库存数量)
     */
    public function AddStockQueue(int $good_id, int $num) {
        list($key, $len) = $this->GetQueueNumKey($good_id);
        $list_num = [];
        $i = 0;
        while ($i < $num) {
            $list_num[] = 1;
            $i++;
        }

        $rel = Redis::rpush($key, $list_num);
        //$this->SetDutyQueueExpireAt($good_id); // 设置队列过期时间

        return $this->queueTraitReturnRel($rel);
    }

    /**
     * 获取商品库存
     * @param int $goodsid 商品ID
     * @param int $buynum 购买数量
     * @return int 队列号
     */
    public function GetStockQueue(int $goodsid,$buynum) {
        list($key, $len) = $this->GetQueueNumKey($goodsid);
        if ($len < $buynum) {
            return $this->queueTraitReturnRel(null);
            //$this->queueTraitExitErr('数量不足!', 3000);
        }

        // 删除库存数量
        $ticket_no = Redis::lRem($key,$buynum,1);
  
        if (!$ticket_no) { // 没有获取到票号
            return $this->queueTraitReturnRel(null);
        }
        $rel = $ticket_no;

        return $this->queueTraitReturnRel($rel);
    }

    /**
     * 查询队列数量并返回
     * @param int $goods_id
     * @return [$key $len]
     */
    public function GetQueueNumKey(int $goods_id) {
        $this->setQueueDb('start');

        $key = 'goods_stock_'. $goods_id;
        $len = Redis::llen($key); // 正常返回数量;没有队列返回 0

        return [$key, $len];
    }
    /**
     * 抛出异常
     * @param string $msg
     * @param int $code
     * @return fixed
     */
    private function queueTraitExitErr($msg, $code) {
        $this->setQueueDb('end');

        throw new \Exception($msg, $code);
    }
    /**
     * 正常返回信息
     * @param $rel
     */
    private function queueTraitReturnRel($rel) {
        $this->setQueueDb('end');

        return $rel;
    }


    /**
     * 设置 队列 DB
     * @param $db
     * @return bool
     */
    protected function setQueueDb($db = 'start')
    {
        $db = $db == 'start' ? config('database.redis.seckill.database') : config('database.redis.default.database');
        return Redis::select($db);
    }

    private function getDutyInQueueTrait() {
        if (!isset($this->seckillRep) || !$this->seckillRep) {
            $this->seckillRep = new SeckillRepository();
        }
        return $this->seckillRep;
    }
}

 

八,postman并发测试

 

打开postman软件

左侧栏点击+号键,创建一个并发测试文件夹

2、主面板点击+号键,输入一个测试地址,点击save按钮保存到并发测试文件夹

3、点击三角箭头,再点击Run,弹出Collection Runner插件

4、设置并发数和延时时间,点击Run

5、运行结果会依次排序,显示结果状态和耗时

从图中可以看到百度的响应速度还是非常快的。

点击Run Summary可以看到运行结果概述:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值