Redis队列实现秒杀

秒杀系统特点

1、抢购人数远多于库存,读写并发巨大。

2、库存少,有效写少。

3、写需强一致性,商品不能卖超。

4、读强一致性要求不高。

5、稳定性难:高并发下,某个小依赖可能直接造成雪崩、流量预期难精确,过高也造成雪崩。分布式集群,机器多,出故障的概率高。

6、准确性难:库存、抢购成功数,创建订单数之间的一致性。

7、高性能难:有限成本下需要做到极致的性能。

秒杀系统——架构原则

1、稳定性:减少第三方依赖,同时自身服务部署也需做到隔离。压测、降级、限流方案、确保核心服务可用。需健康度检测机制,整个链路避免单点。

2、高性能:缩短单请求访问路径,减少IO。减少接口数,降低吞吐数据量,请求次数减少。

秒杀服务核心实现

1、怎样设计秒杀服务:满足基本需求,做到单服务极致性能。请求链路流量优化,从客户端到服务端每层优化。稳定性建设。

2、基本需求:扣库存、查库存、排队进度。(做到单服务极致性能)。查订单详情、创建订单,支付订单。(库存少抢购人数远多于库存,读写并发高)

基本需求——扣库存方案

1、下单减库存?

并发请求——>创建订单——>扣库存——>支付 这种流程不会超卖,但问题是如果有人恶意下单不支付,占用库存。

2、支付减库存?

并发请求——>创建订单——>支付——>扣库存 这种流程是支付一次扣一次库存,如果用户把商品买完了,别的用户下不了订单或者订单超卖。

3、预扣库存?

并发请求——>扣库存——>创建订单——>支付——>10分钟内不支付取消订单,加库存。

采用预扣库存方案比较好。

wxml

<view>
  <l-countdown time-type="second" time="{{expire_time}}" bind:linend="changeBtn"/>

  <view><image src="{{Detail.image}}" bindtap="image"></image></view>
  <view>{{Detail.store_product.store_name}}</view>
  <view>{{Detail.price}}</view>

    <view>
      <l-button disabled="{{ disabled }}" bind:lintap="buyGoods" type="error" data-id="{{Detail.id}}">立即秒杀</l-button>
     </view>
</view>

js

// pages/Detail/Detail.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    expire_time:0,
    disabled:false,
    expire_time:'',

  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    let that = this
    let sid = options.id
    wx.request({
      url: 'http://www.wej.com/index.php/api/seckill/goodsDetail', //仅为示例,并非真实的接口地址
      data: {
        sid
      },
      header: {
        'content-type': 'application/json' // 默认值
      },
      success (res) {
        console.log(res.data) 
        let newdate = Math.round(new Date().getTime() / 1000).toString()
        let expire_time = res.data.data.start_time - newdate
        console.log(expire_time)
        that.setData({
          Detail:res.data.data,
          expire_time:expire_time
        })
        if(expire_time > 0){
          that.setData({
            disabled:true
          })
        }else{
          that.setData({
            disabled:false
          })
        }
      }
    })


  },

  buyGoods(c){
    clearTimeout(this.TimeID);
    this.TimeID = setTimeout(() => {
      let goods_id = c.currentTarget.dataset.id
      wx.request({
        url: 'http://www.wej.com/index.php/api/seckill/snap_up', //仅为示例,并非真实的接口地址
        data: {
          goods_id
        },
        header: {
          'content-type': 'application/json' // 默认值
        },
        success (res) {
          console.log(res.data)
        }
      })
    }, 1000);

  },


changeBtn(){
  console.log('秒杀开始')
    this.setData({
      disabled:false
    })
  },




image(c){
  wx.previewImage({
    current: this.data.Detail.image, // 当前显示图片的 http 链接
    urls: [this.data.Detail.image] // 需要预览的图片 http 链接列表
  })
},


})

json

{
  "usingComponents": {
    "l-countdown":"/dist/countdown",
    "l-button":"/dist/button"
  }
}
<?php

namespace App\Http\Controllers;

use App\Models\AddressInfo;
use App\Models\StoreOrder;
use App\Server\Snowflake;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

class Seckill extends Controller
{
    /**
     * 数据预热
     * @return \Illuminate\Http\JsonResponse
     */
    public function activity()
    {
        $result = \App\Models\Seckill::with('StoreProduct')
            ->get()->toArray();
        foreach ($result as $val){
            //生成对应商品库存队列
            $goods = "activity_goods_".$val['product_id'];
            for ($i=0; $i < $val['stock']; $i++) {
                Redis::lpush($goods,1);
            }
        }
        return response()->json(['code' => 20000, 'msg' => '查询成功', 'data' => $result]);
    }

    /**、
     * 秒杀列表
     * @return \Illuminate\Http\JsonResponse
     */
    public function activityList()
    {
        $result = \App\Models\Seckill::with('StoreProduct')
            ->get()->toArray();
        return response()->json(['code' => 20000, 'msg' => '查询成功', 'data' => $result]);
    }

    /**
     * 秒杀商品详情
     * @return \Illuminate\Http\JsonResponse
     */
    public function goodsDetail()
    {
        $goods_id = request()->get('sid');
        $result = \App\Models\Seckill::with(['StoreProduct'])
            ->where('product_id',$goods_id)
            ->first();
        return response()->json(['code' => 20000, 'data' => $result, 'msg' => '查询成功']);
    }

    /**
     * 校验库存
     * @return \Illuminate\Http\JsonResponse
     */
    public function snap_up(){
        //用户ID
        $userID = 1;
        //商品ID
        $goodsID = request()->get('goods_id');
        //对应商品库存队列
        $goods = "activity_goods_".$goodsID;
        //对应商品抢购成功用户集合 {1,3,4}
        $robSuccessUser = "success_user".$goodsID;
        //进行判断当前用户是否在抢成功的队列里面
        $result = Redis::sismember($robSuccessUser,$userID);
        //如果你在这里面,就抢完了
        if ($result) {
            //如果抢购成功 返回状态码,进行下单
            return response()->json(['code' => 20000, 'data' => '', 'msg' => '已经抢购过了']);
        }
        //减库存,把队列里面的数据从左边 头
        $count = Redis::lpop($goods);
        if (!$count) {
            //如果抢购成功 返回状态码,进行下单
            return response()->json(['code' => 20001, 'data' => '', 'msg' => '已经抢光了哦']);
        }
        //把当前这个秒杀的uid存储到抢购成功的队列里set
        $success = Redis::sadd($robSuccessUser, $userID);
        if(!$success){
            //已经在成功队列里了,加回库存,防止的是同个用户并发请求
            Redis::lpush($goods, 1);
            //如果抢购成功 返回状态码,进行下单
            return response()->json(['code' => 20002, 'data' => '', 'msg' => '已经抢购过了']);
        }
        //如果抢购成功 返回状态码,进行下单
        return response()->json(['code' => 20000, 'data' => '', 'msg' => '秒杀成功']);
    }

    /**
     * 生成订单
     * @return false|\Illuminate\Http\JsonResponse|string
     */
    public function createOrder(){
        //用户ID
        $userID = request()->get('userID');
        //商品ID
        $goodsID = request()->get('goods_id');
        //地址ID
        $address_id = request()->get('address_id');
        //对应商品抢购成功用户集合
        $robSuccessUser = "success_user".$goodsID;
        //进行判断当前用户是否在抢成功的队列里面
        $result = Redis::sismember($robSuccessUser,$userID);
        //如果你在这里面,就抢完了
        if (!$result) {
            //如果抢购成功 返回状态码,进行下单
            return response()->json(['code' => 20003, 'data' => '', 'msg' => '手慢了!']);
        }
        DB::beginTransaction();
        try{
            //减库存
            $shopData = \App\Models\Seckill::with('StoreProduct')
                ->where('product_id',$goodsID)
                ->first()->toArray();
            $shopStock = \App\Models\Seckill::where('product_id',$goodsID)->update(['stock' => $shopData['stock'] - 1]);
            if (!$shopStock) return json_encode(['code' => 50000,'msg' => '下单失败','data' => []]);
            //地址
            $address_info = AddressInfo::find($address_id)->toArray();
            //生成订单
            Snowflake::machineId($userID);
            $order_id = substr(date('Ymd'),2).'-'.Snowflake::createOnlyId();
            $data = [
                'order_id' => $order_id,
                'uid' => $userID,
                'real_name' => $address_info['real_name'],
                'user_phone' => $address_info['phone'],
                'user_address' => $address_info['detail'],
                'pay_price' => $shopData['store_product']['price'],
                'pay_time' => time()
            ];
            $orderAdd = StoreOrder::insert($data);
            if (!$orderAdd) return json_encode(['code' => 50000,'msg' => '下单失败','data' => []]);
            DB::commit();
            //下单成功,跳转支付页面
            return response()->json(['code' => 20000, 'data' => '', 'msg' => '下单成功!']);
        }catch (\Exception $e){
            DB::rollBack();
            return response()->json(['code' => 50000, 'data' => '', 'msg' => $e->getMessage()]);
        }
    }


}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值