库存超卖—解决方案3 redis队列

参考:秒杀java代码 - 简单版本v2_祁_z-CSDN博客

解决方案1 乐观锁版本的缺点是:
    如某商品有100库存,但是有10000人抢购的话就会有1w个请求去数据库进行查询库存(效率低且数据库可以宕机)。

redis队列解决:
    1. 在后台提前生成好商品对应的库存数量保存到redis中,采用list命令

    2. 用户点击抢购接口后,先去redis中查询是否能获取到数据,获取到数据就代表抢到了商品继续走流程代码,反之代表商品已售完。

流程:
1. 在后台把商品库存保存到redis中,采用list命令(获取特性:从左获取并移除),有多少库存就for循环多少次添加到list中,保存格式:(库存id,for循环中的自增id),
2. 用户请求抢购接口
2.1 使用redis的setnx命令限制用户5秒内只能请求一次
2.2 获取redis中提前存好的库存,能获取到就代表抢购成功,执行以下流程,反之返回该商品已售完。
2.3 去数据库修改库存数量,采用乐观锁防止库存<0,sql:update 库存数量 set 库存数量 = 库存数量 - 1 where 库存id = #{库存id} and 库存数量 > 0
2.4 修改库存成功后,异步保存当前'用户id'及'库存id'保存到用户抢购成功记录表中,
2.5 前端定时实时ajax请求后台“查询用户抢购成功记录表接口”是否抢购成功。

注意:
v1版本和当前版本的最后一步“前端采用定时实时查询当前用户是否抢购成功”这一功能是为了最终版本做准备,v1和当前版本可忽略这一步,直接返回抢购成功、失败即可。

在v1版本基础上进行修改
//1. 后台生成商品库存到redis:http://127.0.0.1:81/addInventoryToRedis?inventoryId=110&quantity=100
//2. 抢购接口:http://127.0.0.1:81/subtractInventory?phone=18713901666&inventoryId=110
//3. 查询是否抢购成功:http://127.0.0.1:81/getInventoryLog?phone=18713901666&inventoryId=110

1. 添加生成库存到redis中的接口:

    /**
       http://127.0.0.1:81/addInventoryToRedis?inventoryId=110&quantity=100
     * 提前在后台管理中把库存数量放入redis中,采用list命令(特性从左获取并移除)
     * @param inventoryId 库存id
     * @param quantity 库存数量
     * @return
     */
    @RequestMapping("addInventoryToRedis")
    public String addInventoryToRedis(Integer inventoryId,int quantity){
        for (int i = 1; i <= quantity; i++) {
            redisTemplate.opsForList().leftPush(inventoryId+"", i);
        }
        // 从左获取,并删除
        // Object leftPop = redisTemplate.opsForList().leftPop(inventoryId+"");
        // System.out.println(leftPop);
        return "ok";
    }
2. 业务流程代码:

package com.chuangqi.defense.controller;
 
import java.util.Date;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.chuangqi.defense.mapper.Test_01Mapper;
import lombok.extern.log4j.Log4j2;
 
/**
 1.抢购:http://127.0.0.1:81/subtractInventory?phone=18713901060&inventoryId=110
 2.查询是否抢购成功:http://127.0.0.1:81/getInventoryLog?phone=18713901060&inventoryId=110
 *
 * @author qizhentao
 * @version V1.0
 */
@Log4j2
@RestController
public class Test_01 {
 
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private Test_01Mapper mapper;
    
    /**
     * 抢购接口
     *  1.减库存
     *  2.记录抢购成功用户
     * @param inventoryId
     * @param phone
     * @return
     */
    @RequestMapping("subtractInventory")
    public String add(Integer inventoryId, String phone){
        // 1.限制同一用户5秒内只能请求一次,使用setNX命令:已存在返回false,不存在就保存并返回true,超时时间5000毫秒
        Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(phone, inventoryId, 5000, TimeUnit.MILLISECONDS);
        if(!setIfAbsent){
            return "操作频繁,请稍后再尝试抢购...";
        }
 
            // 2. 从redis获取令牌,能获取到数据就代表抢到了商品从而执行以下流程,否则返回售无。
        Object leftPop = redisTemplate.opsForList().leftPop(inventoryId+"");
        if(leftPop == null){
            log.info("未抢到{}该商品的用户有{}个", inventoryId, ++not);
            return "该商品已售无...";
        }
        
        // 3.减库存
        int i = mapper.subtractInventory(inventoryId, phone);
        if(i == 1){
            // 4.异步添加用户抢购成功记录表
            addInventoryLog(inventoryId, phone);
            return "ok";
        }else{
            return "no";
        }
    }
    
    /**
     * 减库存后,添加用户抢购成功记录表中
     *     1. 并且添加到redis中,
     *  2. 在前台实时查询是否抢购成功的时候去查询redis即可
     * @param inventoryId
     * @param phone
     * @return
     */
    @Async
    public boolean addInventoryLog(int inventoryId, String phone){
        // 1.添加数据到数据库
        int i = mapper.addInventoryLog(inventoryId, phone, new Date());
        // 2.也可同步到redis中
        // redisTemplate.opsForValue().set(phone, inventoryId);
        return i == 1 ? true : false;
    }
    
    /**
     * 前台定时ajax实时查询是否抢购成功
     * @param inventoryId
     * @param phone
     * @return
     */
    @RequestMapping("getInventoryLog")
    public String getInventoryLog(int inventoryId, String phone){
        // 查询数据库,也可改为查询redis
        Integer id = mapper.getInventoryLog(inventoryId, phone);
        // redisTemplate.opsForValue().get(inventoryId)
        if(id != null){
            return "恭喜抢购成功!";
        }else{
            return null;
        }
    }
 
}

————————————————
版权声明:本文为CSDN博主「祁_z」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36881887/article/details/100141474

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值