上面的方案解决了并发情况下超卖的问题,但其实际秒杀中大量并发情况下,这个下单过程是需要很长等待时间的,所以这里我们建议用异步和多线程实现,最好不要让程序处于阻塞状态,而是在用户一下单的时候确认用户是否符合下单条件,如果符合,则开启线程执行创建订单处理,用户则进入支付页面等待查询订单结果, 扫码支付即可。
实现过程如下图
/**
* 进入秒杀下单
*/
$scope.submitOrder = function () {
// 从地址栏获去秒杀商品Id
var seckillGoodsId = $location.search()['seckillGoodsId'];
orderService.submitOrder(seckillGoodsId).success(function (response) {
if (response.success) {
//进入支付页面
location.href = "pay.html";
} else {
alert(response.message);
}
}
);
};
/**
* 进入秒杀下单
*/
this.submitOrder = function (id) {
return $http.get("http://localhost:9007/seckillOrder/submitOrder02/"+ id);
}
import com.alibaba.dubbo.config.annotation.Reference;
import com.pyg.seckill.service.SeckillOrderService;
import com.pyg.utils.PygResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/seckillOrder")
public class SeckillOrderController {
@Reference(timeout = 1000000000)
private SeckillOrderService seckillOrderService;
/**
* 需求:提交订单
* 请求:../seckillOrder/submitOrder/'+id
* 参数:@PathVariable Long id : 秒杀商品id
*/
@RequestMapping("submitOrder/{id}")
@CrossOrigin(origins = "http://item.pinyougou.com")
public ResultInfo submitOrder02(@PathVariable Long id, HttpServletRequest request){
ResultInfo resultInfo = null;
String userId = request.getRemoteUser();
try {
seckillOrderService.submitOrder(id,userId);
resultInfo = new ResultInfo
/**
* 服务层接口
*/
public interface SeckillOrderService {
/**
* 需求:提交订单
* 参数:Long id , String userId
*/
void submitOrder(Long id, String userId);
}
import com.alibaba.dubbo.config.annotation.Service;
import com.pyg.mapper.TbSeckillOrderMapper;
import com.pyg.pojo.TbSeckillGoods;
import com.pyg.pojo.TbSeckillOrder;
import com.pyg.seckill.service.SeckillOrderService;
import com.pyg.utils.SysConstants;
import com.pyg.vo.OrderRecode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* 服务实现层
*/
@Service
public class SeckillOrderServiceImpl implements SeckillOrderService {
//注入redis模板对象
@Autowired
private RedisTemplate redisTemplate;
//注入多线程对象
@Autowired
private CreateOrder createOrder;
//注入调度对象
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
/**
* 需求:提交订单
* 参数:Long id String userId
* 1),从redis服务器中获取入库的秒杀商品
* 2),判断商品是否存在,商品库存是否小于等于0
* 3), 是否用户正在排队, 是否有未支付的订单
* 4), 是否超出秒杀人数限制
* 5),满足条件,添加入秒杀用户队列
* 6),把待处理用户详情(userId, seckillgoodsId)存储在redis服务器中
* 7),调用多线程创建秒杀订单, 此订单此时处于未支付状态
*/
public void submitOrder(Long id, String userId) {
//1),从redis服务器中获取入库的秒杀商品
TbSeckillGoods seckillGoods =
(TbSeckillGoods) redisTemplate.boundHashOps(TbSeckillGoods.class.getSimpleName()).get(id);
// 2),判断商品是否存在,商品库存是否小于等于0
if (seckillGoods == null || seckillGoods.getStockCount() < 1) {
throw new RuntimeException("已售罄");
}
// 3), 是否用户正在排队, 是否有未支付的订单,
//从用户队列获取用户对象
Boolean member = redisTemplate.boundSetOps(SysConstants.SECKILL_USER_SET + id).isMember(userId);
//如果为true,表示此用户正在排队
if (member) {
//查询用户是否有订单
Object order = redisTemplate.boundHashOps(TbSeckillOrder.class.getSimpleName()).get(userId);
//判断订单是否为空
if (order != null) {
throw new RuntimeException("您还有订单未支付!");
}
throw new RuntimeException("您正在排队中.......");
}
// 4), 是否超出秒杀人数限制
//获取抢购此商品的人数
int persons = redisTemplate.boundSetOps(SysConstants.SECKILL_USER_SET + id).members().size();
//判断商品库存是否满足抢购人数需求
if(persons >= seckillGoods.getStockCount()+200){
throw new RuntimeException("排队人数过多...");
}
// 5),满足条件,添加入秒杀用户队列
// 6),把待处理用户详情(userId, seckillgoodsId)存入list集合进行排队
redisTemplate.boundListOps(SysConstants.SECKILL_USER_QUEUE).leftPush(new OrderRecode(userId,id));
//使用set集合记录用户排队
redisTemplate.boundSetOps(SysConstants.SECKILL_USER_SET + id).add(userId);
// 7),调用多线程创建秒杀订单, 此订单此时处于未支付状态
taskExecutor.execute(createOrder);
}
}
3.2.4 创建订单类 CreateOrder.java
import com.pyg.mapper.TbSeckillGoodsMapper;
import com.pyg.pojo.TbSeckillGoods;
import com.pyg.pojo.TbSeckillOrder;
import com.pyg.utils.IdWorker;
import com.pyg.utils.SysConstants;
import com.pyg.vo.OrderRecode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class CreateOrder implements Runnable {
//注入redis模板对象
@Autowired
private RedisTemplate redisTemplate;
//注入秒杀商品mapper接口代理对象
@Autowired
private TbSeckillGoodsMapper seckillGoodsMapper;
/**
* 多线程实现下单
*/
public void run() {
//1,从Redis队列中获取用户排队信息
OrderRecode orderRecode =
(OrderRecode) redisTemplate.boundListOps(SysConstants.SECKILL_USER_QUEUE).rightPop();
//2,判断用户排队信息是否存在
if (orderRecode != null) {
// 3,如果用户存在,判断秒杀商品是否存在
Long seckillGoodsId = (Long) redisTemplate.boundListOps(SysConstants.SECKILL_GOODSID_LIST + orderRecode.getSeckillId()).rightPop();
// 4,如果秒杀商品不存在,表示秒杀商品已售罄
if (seckillGoodsId == null) {
throw new RuntimeException("已售罄");
}
}
// 秒杀商品Id
Long id = orderRecode.getSeckillId();
// 用户 userId
String userId = orderRecode.getUserId();
// 5,秒杀下单
// 5.1 从redis服务器中获取入库的秒杀产品
TbSeckillGoods seckillGoods = (TbSeckillGoods) redisTemplate.boundHashOps(TbSeckillGoods.class.getSimpleName()).get(id);
// 5.2 如果秒杀产品存在, 创建秒杀订单, 此订单此时处于未支付状态
// 5.2.1 创建秒杀订单对象
TbSeckillOrder seckillOrder = new TbSeckillOrder();
// 5.2.2 创建idWorder
IdWorker idWorker = new IdWorker();
seckillOrder.setId(idWorker.nextId());
// 5.2.3 设置秒杀订单属性值
seckillOrder.setSellerId(seckillGoods.getSellerId());
seckillOrder.setSeckillId(id);
// 5.2.4 设置状态为未支付
seckillOrder.setStatus("0");
// 5.2.5 设置用户ID, 订单price, 下单时间
seckillOrder.setUserId(userId);
seckillOrder.setMoney(seckillGoods.getCostPrice());
seckillOrder.setCreateTime(new Date());
// 5.3 把新增订单储存在 Redis 服务器中
// 参数1 : 订单唯一标识, 标识此数据时秒杀订单
// 参数2 : userId , 用来标识此订单属于哪个用户
// 参数3 : 秒杀订单数据
redisTemplate.boundHashOps(TbSeckillOrder.class.getSimpleName()).put(userId,seckillOrder);
// 5.4 下单后, 把秒杀商品库存减一
seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);
// 5.5 判断库存是否小于0 , 卖完需要同步数据库
if(seckillGoods.getStockCount() < 1){
seckillGoodsMapper.updateByPrimaryKeySelective(seckillGoods);
redisTemplate.boundHashOps(TbSeckillGoods.class.getSimpleName()).delete(seckillGoods.getId());
}else {
// 5.6 否则把库存减少(但此时没有减为0) 的秒杀商品同步 Redis
redisTemplate.boundHashOps(TbSeckillGoods.class.getSimpleName()).put(seckillGoods.getId(),seckillGoods);
}
// 5.7 已经创建订单的从Set 集合中移除
redisTemplate.boundSetOps(SysConstants.SECKILL_USER_SET + id).remove(userId);
}
}
/**
* 标识
*/
public class SysConstants {
//秒杀商品队列唯一标识常量
public static final String SECKILL_GOODSID_LIST = "SECKILL_GOODSID_LIST";
//用户排队Set集合唯一标识
public static final String SECKILL_USER_SET = "SECKILL_USER_SET";
//标识用户排队队列
public static final String SECKILL_USER_QUEUE = "SECKILL_USER_QUEUE";
}