参考:秒杀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