• Redis队列实现秒杀


    秒杀系统特点

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

    2、库存少,有效写少。

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

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

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

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

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

    秒杀系统——架构原则

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

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

    秒杀服务核心实现

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

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

    基本需求——扣库存方案

    1、下单减库存?

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

    2、支付减库存?

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

    3、预扣库存?

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

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

    wxml

    1. <view>
    2. <l-countdown time-type="second" time="{{expire_time}}" bind:linend="changeBtn"/>
    3. <view><image src="{{Detail.image}}" bindtap="image">image>view>
    4. <view>{{Detail.store_product.store_name}}view>
    5. <view>{{Detail.price}}view>
    6. <view>
    7. <l-button disabled="{{ disabled }}" bind:lintap="buyGoods" type="error" data-id="{{Detail.id}}">立即秒杀l-button>
    8. view>
    9. view>

    js

    1. // pages/Detail/Detail.js
    2. Page({
    3. /**
    4. * 页面的初始数据
    5. */
    6. data: {
    7. expire_time:0,
    8. disabled:false,
    9. expire_time:'',
    10. },
    11. /**
    12. * 生命周期函数--监听页面加载
    13. */
    14. onLoad(options) {
    15. let that = this
    16. let sid = options.id
    17. wx.request({
    18. url: 'http://www.wej.com/index.php/api/seckill/goodsDetail', //仅为示例,并非真实的接口地址
    19. data: {
    20. sid
    21. },
    22. header: {
    23. 'content-type': 'application/json' // 默认值
    24. },
    25. success (res) {
    26. console.log(res.data)
    27. let newdate = Math.round(new Date().getTime() / 1000).toString()
    28. let expire_time = res.data.data.start_time - newdate
    29. console.log(expire_time)
    30. that.setData({
    31. Detail:res.data.data,
    32. expire_time:expire_time
    33. })
    34. if(expire_time > 0){
    35. that.setData({
    36. disabled:true
    37. })
    38. }else{
    39. that.setData({
    40. disabled:false
    41. })
    42. }
    43. }
    44. })
    45. },
    46. buyGoods(c){
    47. clearTimeout(this.TimeID);
    48. this.TimeID = setTimeout(() => {
    49. let goods_id = c.currentTarget.dataset.id
    50. wx.request({
    51. url: 'http://www.wej.com/index.php/api/seckill/snap_up', //仅为示例,并非真实的接口地址
    52. data: {
    53. goods_id
    54. },
    55. header: {
    56. 'content-type': 'application/json' // 默认值
    57. },
    58. success (res) {
    59. console.log(res.data)
    60. }
    61. })
    62. }, 1000);
    63. },
    64. changeBtn(){
    65. console.log('秒杀开始')
    66. this.setData({
    67. disabled:false
    68. })
    69. },
    70. image(c){
    71. wx.previewImage({
    72. current: this.data.Detail.image, // 当前显示图片的 http 链接
    73. urls: [this.data.Detail.image] // 需要预览的图片 http 链接列表
    74. })
    75. },
    76. })

    json

    1. {
    2. "usingComponents": {
    3. "l-countdown":"/dist/countdown",
    4. "l-button":"/dist/button"
    5. }
    6. }
    1. namespace App\Http\Controllers;
    2. use App\Models\AddressInfo;
    3. use App\Models\StoreOrder;
    4. use App\Server\Snowflake;
    5. use Illuminate\Support\Facades\DB;
    6. use Illuminate\Support\Facades\Redis;
    7. class Seckill extends Controller
    8. {
    9. /**
    10. * 数据预热
    11. * @return \Illuminate\Http\JsonResponse
    12. */
    13. public function activity()
    14. {
    15. $result = \App\Models\Seckill::with('StoreProduct')
    16. ->get()->toArray();
    17. foreach ($result as $val){
    18. //生成对应商品库存队列
    19. $goods = "activity_goods_".$val['product_id'];
    20. for ($i=0; $i < $val['stock']; $i++) {
    21. Redis::lpush($goods,1);
    22. }
    23. }
    24. return response()->json(['code' => 20000, 'msg' => '查询成功', 'data' => $result]);
    25. }
    26. /**、
    27. * 秒杀列表
    28. * @return \Illuminate\Http\JsonResponse
    29. */
    30. public function activityList()
    31. {
    32. $result = \App\Models\Seckill::with('StoreProduct')
    33. ->get()->toArray();
    34. return response()->json(['code' => 20000, 'msg' => '查询成功', 'data' => $result]);
    35. }
    36. /**
    37. * 秒杀商品详情
    38. * @return \Illuminate\Http\JsonResponse
    39. */
    40. public function goodsDetail()
    41. {
    42. $goods_id = request()->get('sid');
    43. $result = \App\Models\Seckill::with(['StoreProduct'])
    44. ->where('product_id',$goods_id)
    45. ->first();
    46. return response()->json(['code' => 20000, 'data' => $result, 'msg' => '查询成功']);
    47. }
    48. /**
    49. * 校验库存
    50. * @return \Illuminate\Http\JsonResponse
    51. */
    52. public function snap_up(){
    53. //用户ID
    54. $userID = 1;
    55. //商品ID
    56. $goodsID = request()->get('goods_id');
    57. //对应商品库存队列
    58. $goods = "activity_goods_".$goodsID;
    59. //对应商品抢购成功用户集合 {1,3,4}
    60. $robSuccessUser = "success_user".$goodsID;
    61. //进行判断当前用户是否在抢成功的队列里面
    62. $result = Redis::sismember($robSuccessUser,$userID);
    63. //如果你在这里面,就抢完了
    64. if ($result) {
    65. //如果抢购成功 返回状态码,进行下单
    66. return response()->json(['code' => 20000, 'data' => '', 'msg' => '已经抢购过了']);
    67. }
    68. //减库存,把队列里面的数据从左边 头
    69. $count = Redis::lpop($goods);
    70. if (!$count) {
    71. //如果抢购成功 返回状态码,进行下单
    72. return response()->json(['code' => 20001, 'data' => '', 'msg' => '已经抢光了哦']);
    73. }
    74. //把当前这个秒杀的uid存储到抢购成功的队列里set
    75. $success = Redis::sadd($robSuccessUser, $userID);
    76. if(!$success){
    77. //已经在成功队列里了,加回库存,防止的是同个用户并发请求
    78. Redis::lpush($goods, 1);
    79. //如果抢购成功 返回状态码,进行下单
    80. return response()->json(['code' => 20002, 'data' => '', 'msg' => '已经抢购过了']);
    81. }
    82. //如果抢购成功 返回状态码,进行下单
    83. return response()->json(['code' => 20000, 'data' => '', 'msg' => '秒杀成功']);
    84. }
    85. /**
    86. * 生成订单
    87. * @return false|\Illuminate\Http\JsonResponse|string
    88. */
    89. public function createOrder(){
    90. //用户ID
    91. $userID = request()->get('userID');
    92. //商品ID
    93. $goodsID = request()->get('goods_id');
    94. //地址ID
    95. $address_id = request()->get('address_id');
    96. //对应商品抢购成功用户集合
    97. $robSuccessUser = "success_user".$goodsID;
    98. //进行判断当前用户是否在抢成功的队列里面
    99. $result = Redis::sismember($robSuccessUser,$userID);
    100. //如果你在这里面,就抢完了
    101. if (!$result) {
    102. //如果抢购成功 返回状态码,进行下单
    103. return response()->json(['code' => 20003, 'data' => '', 'msg' => '手慢了!']);
    104. }
    105. DB::beginTransaction();
    106. try{
    107. //减库存
    108. $shopData = \App\Models\Seckill::with('StoreProduct')
    109. ->where('product_id',$goodsID)
    110. ->first()->toArray();
    111. $shopStock = \App\Models\Seckill::where('product_id',$goodsID)->update(['stock' => $shopData['stock'] - 1]);
    112. if (!$shopStock) return json_encode(['code' => 50000,'msg' => '下单失败','data' => []]);
    113. //地址
    114. $address_info = AddressInfo::find($address_id)->toArray();
    115. //生成订单
    116. Snowflake::machineId($userID);
    117. $order_id = substr(date('Ymd'),2).'-'.Snowflake::createOnlyId();
    118. $data = [
    119. 'order_id' => $order_id,
    120. 'uid' => $userID,
    121. 'real_name' => $address_info['real_name'],
    122. 'user_phone' => $address_info['phone'],
    123. 'user_address' => $address_info['detail'],
    124. 'pay_price' => $shopData['store_product']['price'],
    125. 'pay_time' => time()
    126. ];
    127. $orderAdd = StoreOrder::insert($data);
    128. if (!$orderAdd) return json_encode(['code' => 50000,'msg' => '下单失败','data' => []]);
    129. DB::commit();
    130. //下单成功,跳转支付页面
    131. return response()->json(['code' => 20000, 'data' => '', 'msg' => '下单成功!']);
    132. }catch (\Exception $e){
    133. DB::rollBack();
    134. return response()->json(['code' => 50000, 'data' => '', 'msg' => $e->getMessage()]);
    135. }
    136. }
    137. }

  • 相关阅读:
    Kubernetes:(十六)Ingress的概念和原理
    touchGFX综合学习十四、基于cubeMX、正点原子H750开发版、RGB4.3寸屏移植touchGFX完整教程+工程(二)
    kubernetes helm
    常见钓鱼手法及防范
    一条Select语句在MySQL-Server层的执行过程
    偶数科技入选 IDC 中国分布式数据库报告,获 Innovator 殊荣
    [Python]Django 配置
    Kotlin语法入门-密封类和密封接口(11)
    P5505 [JSOI2011]分特产(容斥多重集组合数)
    电源硬件设计----电源组件基本知识
  • 原文地址:https://blog.csdn.net/code_nutter/article/details/126023103