zoukankan      html  css  js  c++  java
  • 第三节:抢单流程优化2(单品限流→购买数量限制→方法幂等)

    一. 单品限流

    1. 含义

     某件商品n秒内只接受m个请求, 比如:限制商品A在2s内只接受500个下单请求。

    2.设计思路

     利用Redis自增的Api,该商品的第一个请求进来的时候设置缓存过期时间,限制内正常走业务,限制外返回限流提示;时间到了,原缓存内容消失,下一次第一个请求进来重新设置过期时间

    3.分析

     单品限流属于商品层次的限流,后面会有Nginx全局限流

    4.压测结果

    要求:1秒内该商品只能接收100个下单请求。

    代码分享:

            /// <summary>
            ///  05-单品限流
            /// </summary>
            /// <param name="userId">用户编号</param>
            /// <param name="arcId">商品编号</param>
            /// <param name="totalPrice">订单总额</param>
            /// <param name="goodNum">用户购买的商品数量</param>
            /// <returns></returns>
            public string POrder5(string userId, string arcId, string totalPrice, int goodNum = 1)
            {
                try
                {
                    //一. 业务完善优化
                    //1. 单品限流
                    {
                        int tLimits = 100;    //限制请求数量
                        int tSeconds = 1;     //限制秒数
                        string limitKey = $"LimitRequest{arcId}";//受限商品ID
                        long myLimitCount = _redisDb.StringIncrement(limitKey, 1); //key不存在则会自动创建,第一次创建返回值为1
                        if (myLimitCount > tLimits)
                        {
                            throw new Exception($"不能购买了,{tSeconds}秒内只能请求{tLimits}次");
                            //return $"不能购买了,{tSeconds}秒内只能请求{tLimits}次";
                        }
                        else if (myLimitCount == 1)
                        {
                            //设置过期时间
                            _redisDb.KeyExpire(limitKey, TimeSpan.FromSeconds(tSeconds));
                        }
    
                    }
                    #endregion
    
                    //二. 逻辑优化
                    //1. 直接自减1
                    int iCount = (int)_redisDb.StringDecrement($"{arcId}-sCount", 1);
                    if (iCount >= 0)
                    {
    
                        //2. 将下单信息存到消息队列中
                        var orderNum = Guid.NewGuid().ToString("N");
                        _redisDb.ListLeftPush(arcId, $"{userId}-{arcId}-{totalPrice}-{orderNum}");
    
                        //3. 把部分订单信息返回给前端
                        return $"下单成功,订单信息为:userId={userId},arcId={arcId},orderNum={orderNum}";
                    }
                    else
                    {
                        //卖完了
                        return "卖完了";
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }
    View Code

    测试:1s内对商品发送500个请求,异常率80%,说明指接收了100个请求,同时库存扣减和订单创建也正确。 

    二. 购买商品限制

    1. 含义

     每位用户在秒杀期间对某商品只能购买m件.

     PS:哪件商品限制购买多少件依靠DB设计,事先录好,不同商品的限制数量不同。

    2. 设计思路

     A. 同样是利用Redis自增API, 1个用户对应1件商品 存一条记录

     B. 也要设置一下过期时间,设计一个合理的数值,秒杀结束后,数据失效消失即可

     C. 配合前端购买框内的设计限制

    3. 分析

     购买商品限制可以防止黄牛大量囤货

    4. 压测结果

    要求:1件商品一个用户只能购买3件。

    代码分享:

           /// <summary>
            ///  06-限制购买数量
            /// </summary>
            /// <param name="userId">用户编号</param>
            /// <param name="arcId">商品编号</param>
            /// <param name="totalPrice">订单总额</param>
            /// <param name="goodNum">用户购买的商品数量</param>
            /// <returns></returns>
            public string POrder6(string userId, string arcId, string totalPrice, int goodNum = 1)
            {
                try
                {
                    //一. 业务完善优化
    
                   //1. 单品限流
    
                    #region 2. 限制用户购买数量
                    {
                        //表示用户商品可以购买的数量
                        //(秒杀商品表中有个limitNum字段,同步到redis中,这里从redis中读取这个限制),这里临时先写死
                        int tGoodBuyLimits = 3;  //这里先临时写死
                        string userBuyGoodLimitKey = $"userBuyGoodLimitKey-{userId}-{arcId}";
                        long myGoodLimitCount = _redisDb.StringIncrement(userBuyGoodLimitKey, goodNum);
                        if (myGoodLimitCount > tGoodBuyLimits)
                        {
                            throw new Exception($"不能购买了,一个用户只能买{tGoodBuyLimits}件");
                        }
                        else
                        {
                            //这里设置10min,表示10min后秒杀结束,用户可以继续购买了,这个缓存消失 (这里缓存是否覆盖影响不大)
                            _redisDb.KeyExpire(userBuyGoodLimitKey, TimeSpan.FromMinutes(10));
                        }
                    }
                    #endregion
    
    
                    //二. 逻辑优化
                    //1. 直接自减1
                    int iCount = (int)_redisDb.StringDecrement($"{arcId}-sCount", 1);
                    if (iCount >= 0)
                    {
    
                        //2. 将下单信息存到消息队列中
                        var orderNum = Guid.NewGuid().ToString("N");
                        _redisDb.ListLeftPush(arcId, $"{userId}-{arcId}-{totalPrice}-{orderNum}");
    
                        //3. 把部分订单信息返回给前端
                        return $"下单成功,订单信息为:userId={userId},arcId={arcId},orderNum={orderNum}";
                    }
                    else
                    {
                        //卖完了
                        return "卖完了";
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }
    View Code

     测试:模拟同一个用户发送100个请求,异常率为97%,说明该用户只能抢3件

     

    三. 方法幂等

    1. 含义

     用户在下单页面,假设网络延迟多次点击按钮,服务端仅处理第一次请求(第一次成功则成功,失败则失败),退出该页面重新进入,又可以重新点击下单了

    2. 设计思路

     A.前端生成一个requestId,规则:时间戳+arcId,存放到SessionStorage中。

     B.后端存到redis中string中,也是利用自增api,判断值是否大于1,但要设置一个过期时间,否则就一直在redis中了。

     C.前端页面:点击变灰,拿到返回结果后 或者 5s后才可以继续点击。

    PS:前端的页面业务和效果在后续业务中完善,这里单纯优化接口!!!

    3.分析

     方法幂等是防错的一种措施,防止网络延迟或用户误操作多次下单出错的问题

    4.压测结果

    要求:1个requestId只能生成一条订单记录

    代码分享:

           /// <summary>
            ///07-方法幂等
            /// </summary>
            /// <param name="userId">用户编号</param>
            /// <param name="arcId">商品编号</param>
            /// <param name="totalPrice">订单总额</param>
            /// <param name="requestId">请求ID</param>
            /// <param name="goodNum">用户购买的商品数量</param>
            /// <returns></returns>
            public string POrder7(string userId, string arcId, string totalPrice, string requestId = "125643", int goodNum = 1)
            {
                try
                {
                    //一. 业务完善优化
    
                    //1. 单品限流-同上
    
                    //2. 限制用户购买数量-同上
    
                    //3. 方法幂等-防止网络延迟多次提交问题 
                    //(也可以考虑存hash,把订单号也存进去,回头改造, 但是HashIncrement没法把value也存进去)
                    var orderNum = Guid.NewGuid().ToString("N");
                    int requestIdNum = (int)_redisDb.StringIncrement(requestId, 1);
                    if (requestIdNum == 1)
                    {
                        //仅第一次进来的时候设置过期时间,用于定期删除
                        _redisDb.KeyExpire(requestId, TimeSpan.FromMinutes(10));
                    }
                    else if (requestIdNum > 1)
                    {
                        throw new Exception($"您已经下过单了,不能重复下单");
                    }
                    else
                    {
                        throw new Exception($"其它异常。。。。");
                    }
    
                    //二. 逻辑优化
                    //1. 直接自减1
                    int iCount = (int)_redisDb.StringDecrement($"{arcId}-sCount", 1);
                    if (iCount >= 0)
                    {
    
                        //2. 将下单信息存到消息队列中
                        _redisDb.ListLeftPush(arcId, $"{userId}-{arcId}-{totalPrice}-{orderNum}");
    
                        //3. 把部分订单信息返回给前端
                        return $"下单成功,订单信息为:userId={userId},arcId={arcId},orderNum={orderNum}";
                    }
                    else
                    {
                        //卖完了
                        return "卖完了";
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }
    View Code

     测试:模拟同一个用户发送100个请求,异常率为99%,说明该用户只生成了一条订单记录

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    T-SQL查询语句
    数据库和表的管理
    数据库概念
    IRF2实验
    IFR2笔记
    校园网双网出口实验案例
    双机热备实验
    华为H3C(NAT)实验
    BGP(边界网关协议)实验
    Hybrid实验
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/13817243.html
Copyright © 2011-2022 走看看