转:秒杀

  • 数据库行锁减库存

    • update table_name set num = #{num}, version = version + 1 where id = 1 and version = #{version}
    • update produce set stock = stock -1 where product_id = 1 and stock >0
    • 数据库会所行数据,排队执行
  • 单机的Redis我感觉3-4W的QPS还是能顶得住的.

  • 大量的请求进来,我们需要考虑的点就很多了,缓存雪崩,缓存击穿,缓存穿透

  • 降级、限流、熔断啥的

前端
  • 资源静态化
    • 能提前放入cdn服务器的东西都放进去
    • 减少请求服务器的压力
秒杀页面中如果商品秒杀链接要是提前暴露出去可能有人直接访问url就提前秒杀了
*  URL动态化
    *  你就通过MD5之类的加密算法加密随机的字符串去做url,然后通过前端代码获取url后台校验才能通过。 
    *  思路:
    *  1.在进行秒杀之前,先请求一个服务端地址,/getmiaoshaPath 这个地址,用来获取秒杀地址,传参为 商品id,在服务端生成随机数(MD5)作为pathId存入缓存,(缓存过期时间60s),然后将这个随机数返回给前端.
    *  2.获得该pathid,后 前端在用这个pathid拼接在Url上作为参数,去请求domiaosha服务
    *  3.后端接收到这个pathid 参数,并且与 缓存中的pathid 比较。
    *  如果通过比较,进行秒杀逻辑,如果不通过,抛出业务异常,非法请求。
/*点击秒杀之后 就访问后端 获取一个秒杀地址pathId*/
function getMiaoshaPath() {
    $.ajax({
        url :"/miaosha/getPath",
        type : "GET",
        data:{
            goodsId :$("#goodsId").val(),
            verifyCode : $("#verifyCode").val()
        },
        success:function(data){
            if (data.code ==0) {//
                var path = data.data
                domiaosha(path)
            }else {
                layer.msg(data.message)
            }
        },
        error :function () {
            layer.msg("客户端错误")
        }
    })
 
}
 
    function domiaosha(path){
        $.ajax({
            url :"/miaosha/"+path+"/do_miaosha",//安全优化,带着这个path去访问
            type : "POST",
            data:{
                goodsId :$("#goodsId").val()
            },
            success:function(data){
                if (data.code ==0) {//成功 就跳转 订单页面 并传入 orderid
                    // window.location.href= "/order_detail.htm?orderId="+data.data.id;
                    //若果返回成功,即表示收到请求,等待中
                    getMiaoshaResult($("#goodsId").val());
                }else {
                    layer.msg(data.message)
                }
            },
            error :function () {
                layer.msg("客户端错误")
            }
        })
    }

服务端代码:

/**
     * 安全优化之 ---接口地址随机化(隐藏)
     * 1.点击秒杀之后,先访问该接口生成一个pathId,并存入redis 返回前端
     * 2.前端带着这个pathId去访问秒杀接口,如果传入的path和从redis取出的不一致,就认为 非法请求
     */
    @AccessLimit(seconds = 5,maxCount = 5,needLogin = true)
    @RequestMapping(value = "/getPath", method = RequestMethod.GET)
    @ResponseBody
    public Result<String> getPath(HttpServletRequest request,MiaoshaUser user, Model model,
                                  @RequestParam("goodsId") long goodsId,
                                  @RequestParam(value = "verifyCode")String verifyCode) {
        if (user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
 
 
        String str = UUIDUtill.uuid();
        /*随机生成 一个 pathId 返回给前端*/
        String pathId = Md5Util.md5(str + "1111");
        redisService.set(MiaoshaKey.getMiaoshaPath, "" + user.getId() + goodsId, pathId);
        

/**
     * URL md5加密
     * @param url
     * @return
     */
    public static String Md5ForUrl(String url) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(url.getBytes("UTF-8"));
            byte[] b = md5.digest();
            
            int i;
            StringBuffer buf = new StringBuffer();
            for(int offset = 0, len = b.length; offset < len; offset++) {
                i = b[offset];
                if(i < 0) {
                    i += 256;
                }
                if(i < 16) {
                    buf.append("0");
                }
                buf.append(Integer.toHexString(i));
            }
            url = buf.toString();
            System.out.println("result = " + url);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return url;
    }
  • 该操作:可以为了防止,恶意用户登陆之后,获取token的情况下,通过不断调用秒杀地址接口,来达到刷单的恶意请求。
    每次的url都不一样,只有真正点击秒杀按钮,才会根据商品和用户id生成对应的秒杀接口地址。
  • 但是,这种情况仍然不能解决 利用 按键精灵或者 机器人 频繁点击按钮的操作,为了降低点击按钮的次数,以及高并发下,防止多个用户在同一时间内,并发出大量请求,加入数学公式图形验证码等防高并发优化。
限流

限流这里应该分为前端限流和后端限流。

  • 物理控制:
    • 到秒杀前,一般按钮都是置灰的,只有时间到了,才能点击
    • 这个时候就需要前端的配合,定时去请求你的后端服务器,获取最新的北京时间,到时间点再给按钮可用状态。
    • 按钮可以点击之后也得给他置灰几秒,不然他一样在开始之后一直点的。
  • 前端限流
    • 点击一下或者两下然后几秒之后才可以继续点击,这也是保护服务器的一种手段。
  • 后端限流
    • 阿里的Sentinel、Hystrix等 可以控制线程数
    • 卖1000件商品,请求有10W,我们不需要把十万都放进来,你可以放1W请求进来,利用限流组件可以实现
Nginx
  • 是高性能的web服务器,并发也随便顶几万
  • 但是我们的Tomcat只能顶几百的并发,那负载均衡,多搞几台机
风控

风控可以根据账号行为分析出这个账号机器人的概率大不大,每个用户的行为都是会送到我们大数据团队进行分析处理,给你打上对应标签的。

后端
  • 服务单一职责:

    • 设计个能抗住高并发的系统,秒杀也开个服务,我们把秒杀的代码业务逻辑放一起。
    • 单一职责的好处就是就算秒杀没抗住,秒杀库崩了,服务挂了,也不会影响到其他的服务。(高可用)
  • Redis集群

    • Redis集群,主从同步、读写分离,我们还搞点哨兵,开启持久化直接无敌高可用!
  • 库存预热

    • 秒杀的本质,就是对库存的抢夺,每个秒杀的用户来你都去数据库查询库存校验库存,然后扣减库存,
    • 要开始秒杀前你通过定时任务或者运维同学提前把商品的库存加载到Redis中去,让整个流程都在Redis里面去做,然后等秒杀介结束,再异步的去修改库存就好了。
事务
  • 比如现在库存只剩下1个了,我们高并发嘛,4个服务器一起查询了发现都是还有1个,那大家都觉得是自己抢到了,就都去扣库存,那结果就变成了-3,是的只有一个是真的抢到了,别的都是超卖的。咋办?
  • Redis本身是支持事务的,而且他有很多原子命令的,大家也可以用LUA,还可以用他的管道,乐观锁他也知支持。
限流&降级&熔断&隔离:

万一真的顶不住了,限流,顶不住就挡一部分出去但是不能说不行,降级,降级了还是被打挂了,熔断,至少不要影响别的系统,隔离,你本身就独立的,但是你会调用其他的系统嘛。

消息队列(削峰填谷)

可以把它放消息队列,然后一点点消费去改库存

数据库
  • 数据库用MySQL只要连接池设置合理一般问题是不大的,,不过一般大公司不缺钱而且秒杀这样的活动十分频繁,单独给秒杀建立一个数据库,为秒杀服务,表的设计也是尽可能的简单点,现在的互联网架构部署都是分库的。
  • 至于表就看大家怎么设计了,该设置索引的地方还是要设置索引的,建完后记得用explain看看SQL的执行计划
分布式事务
  • TCC和最终一致性其实不是很适合,TCC开发成本很大,所有接口都要写三次,因为涉及TCC的三个阶段。
  • 最终一致性基本上都是靠轮训的操作去保证一个操作一定成功,那时效性就大打折扣了。
  • 大家觉得不那么可靠的**两段式(2PC)和三段式(3PC)**就派上用场了,他们不一定能保证数据最终一致,但是效率上还算ok。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值