Home 秒杀

</span>
    <span class="fr">
    <span class="fl" th:if="${session.user == null}">你好,请<a href="/login.html" style="color:#ff4e00;">登录</a>&nbsp;<a href="/register" style="color:#ff4e00;">免费注册</a>&nbsp;&nbsp;</span>
    <span class="fl" th:if="${session.user != null}"><a href="/user/userInfo" >欢迎回来,<span th:text="${session.user.userName}"></span></a>&nbsp;|&nbsp;<a href="/orders/list">我的订单</a>&nbsp;</span>
    <span class="fl" th:if="${session.user != null} and ${session.user.role != 0}">|&nbsp;<a href="/admin/adminIndex">后台管理&nbsp;</a></span>
    <span class="fl" th:if="${session.user != null}">|&nbsp;<a href="/admin/adminIndex">秒杀商品&nbsp;</a></span>
    <span class="fl" th:if="${session.user != null}">|&nbsp;<a href="/user/logout">注销</a></span>
</span>

注意:多条件下使用 th:if 格式为
 <span class="fl" th:if="${session.user != null} and ${session.user.role != 0}">|&nbsp;<a href="/admin/adminIndex">后台管理&nbsp;</a></span>

前面的学习中,我们完成了防止超卖商品和抢购接口的限流,已经能够防止大流量把我们的服务器直接搞炸,这篇文章中,我们要开始关心一些细节问题,我们现在设计的系统中还有一些问题

1.我们应该在一定时间内执行秒杀处理,不能在任意时间都接收秒杀请求,如何加入时间验证?

2.对于现有的接口,暴露了我们的接口地址,然后通过脚本抢购怎么办?

3.秒杀开始之后如何限制单个用户的请求频率,即单位时间内的访问次数?

 

此节内容解决:

  • 限时抢购
  • 抢购接口隐藏
  • 单用户限制频率(单位时间内限制访问次数)

 

限时抢购的实现

使用Redis来记录秒杀商品的时间,对秒杀过期的请求进行拒绝处理

 

秒杀请求被拦截:

数据库无改变,即未卖出

Redis 抢购时间可以自己设置,通过传参

通过乐观锁防止超卖+令牌桶限流

 

//开发一个秒杀方法 乐观锁防止超卖,令牌桶限流
@GetMapping("/killtoken")
public  String killtoken(Integer id){
    LOGGER.info("秒杀商品的 ID = " + id);
    //加入令牌桶的限流措施
    //注意:限流之后商品不能百分百的卖掉,有些请求被抛弃,保留一小部分的商品
    if(!rateLimiter.tryAcquire(2,TimeUnit.SECONDS)){
        return "抢购失败,当前秒杀活动过于火爆,请重试!";
    }
    try {//根据秒杀商品的 ID 调用秒杀业务
        int orderId = orderService.kill(id);
        return "秒杀成功!订单ID为:" + orderId;
    }catch (Exception e){
        e.printStackTrace();
        return e.getMessage();
    }
}

会出现商品剩余的情况,因为在接口限流时有一部分请求被抛弃


查看数据库卖出的商品数量:


如果想多卖一点怎么办呢?
1.并发请求加多(Jmeter测试)
2.尝试获取令牌桶的时间+1s
3.增加令牌桶初始大小

  

引入依赖 guava

<!-- Google接口限流 guava  RateLimter 令牌桶实现-->
<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>30.1.1-jre</version>
</dependency>

既然是接口限流,那么一般将限流放在控制器Controller

@GetMapping("/sale")
public String sale(Integer id){
    //1.没有获取到 token 请求 直到获取到 token 令牌
    LOGGER.info("等待的时间:" + rateLimiter.acquire());//试图拿到令牌
    //2.设置一个等待的时间,如果在等待的时间内获取到了 token 令牌,则处理业务,如果在等待时间内没有获取到相应的 token 则抛弃请求
    if(!rateLimiter.tryAcquire(5, TimeUnit.SECONDS)){
        System.out.println("当前请求被限流,直接抛弃,无法调用后续秒杀逻辑......");
        return "抢购失败!";
    }
    System.out.println("处理业务..................");
    return "测试令牌桶";
}

令牌桶原理:先获取令牌,再执行业务逻辑