欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

秒杀系统的接口优化

程序员文章站 2022-06-20 08:36:56
...

思路:减少数据库访问
1、系统初始化,把商品库存加载到redis
2、收到请求在redis预减库存,库存不足,直接返回,
3. 可以在redis里面做内存标记(这一步是很大的优化,只要库存减成零,后面的请求无论是100个还是一万个都是直接失败,压力很小),否则进入3 下一步
4、异步下单:如果有库存,不是直接连接数据库写入,而是对RabbitMQ操作,请求入队,立即返回排队中,类似我们在12306买火车票,并不会马上返回下单成功或者失败,而是显示排队中
5、客户端正常做轮询,判断是否秒杀成功。 服务端在入队之后,会请求出队,生成订单 减少库存

实现方法:

redis预减库存

系统初始化的时候,查询商品数量,把商品库存数量加载到redis缓存里面,

/**
	 * 系统初始化
	 * */
	public void afterPropertiesSet() throws Exception {
		List<GoodsVo> goodsList = goodsService.listGoodsVo();
		if(goodsList == null) {
			return;
		}
		for(GoodsVo goods : goodsList) {
			redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount());
			localOverMap.put(goods.getId(), false);
		}
	}

执行秒杀的时候,不是直接在数据库中减库存,为了提高并发能力,在redis缓存里预减库存,如果库存不足,就返回指定的错误,如果库存充足,执行预减库存操作。
根据user和goodsid到redis里面判断订单是否已经存在,判断是否重复秒杀,如果重复了就返回不能能重复秒杀。

         //预减库存
    	long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);//10
    	if(stock < 0) {
    		 localOverMap.put(goodsId, true);
    		return Result.error(CodeMsg.MIAO_SHA_OVER);
    	}
    	//判断是否已经秒杀到了
    	MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
    	if(order != null) {
    		return Result.error(CodeMsg.REPEATE_MIAOSHA);
    	}

RabbitMQ消息队列把下单异步化

如果没有,就执行入队操作,把user和goodsid信息发送出去。调用rabbitmq的sender方法,rabbitmq使用默认的direct模式。然后返回一个状态,表示排队中。

//入队
    	MiaoshaMessage mm = new MiaoshaMessage();
    	mm.setUser(user);
    	mm.setGoodsId(goodsId);
    	sender.sendMiaoshaMessage(mm);
    	return Result.success(0);//排队中

秒杀到之后,发送消息,在接收端MqReciiver里面,判断库存,判断如果不是重复秒杀,就调用MiaoshaService里面的方法,往下对数据库进行 减库存,下订单,写入秒杀订单。

@RabbitListener(queues=MQConfig.MIAOSHA_QUEUE)
		public void receive(String message) {
			log.info("receive message:"+message);
			MiaoshaMessage mm  = RedisService.stringToBean(message, MiaoshaMessage.class);
			MiaoshaUser user = mm.getUser();
			long goodsId = mm.getGoodsId();
			
			//判断库存
			GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
	    	int stock = goods.getStockCount();
	    	if(stock <= 0) {
	    		return;
	    	}
	    	//判断是否已经秒杀到了
	    	MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
	    	if(order != null) {
	    		return;
	    	}
	    	//减库存 下订单 写入秒杀订单
	    	miaoshaService.miaosha(user, goods);
		}

MiaoshaService里面,减库存,下订单,写入秒杀订单是一个事务操作,要么全部成功,要么全部失败,所以需要给方法添加@Transactional注解。 数据库写入订单之后会把订单信息写入redis里面来。

@Transactional
	public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
		//减库存 下订单 写入秒杀订单
		boolean success = goodsService.reduceStock(goods);
		if(success) {
			//order_info maiosha_order
			return orderService.createOrder(user, goods);
		}else {
			setGoodsOver(goods.getId());
			return null;
		}
	}

在页面里做轮询,查询秒杀结果,如果秒杀失败,直接返回失败。如果成功,就接着查询是否下单成功,如果没成功就接着轮询,如果成功,点击查看订单,就返回订单信息。

内存标记优化:

在MIaoshaController里吗,新建一个HashMap,把商品id和标记值初始化为false存进去。
在预减库存的时候,如果商品库存小于零,做一个标记true,后续的请求不再去访问redis,直接返回失败,好处是可以减少系统开销。

private HashMap<Long, Boolean> localOverMap =  new HashMap<Long, Boolean>();
/**
	 * 系统初始化
	 * */
	public void afterPropertiesSet() throws Exception {
		List<GoodsVo> goodsList = goodsService.listGoodsVo();
		if(goodsList == null) {
			return;
		}
		for(GoodsVo goods : goodsList) {
			redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount());
			localOverMap.put(goods.getId(), false);
		}
	}
//内存标记,减少redis访问
    	boolean over = localOverMap.get(goodsId);
    	if(over) {
    		return Result.error(CodeMsg.MIAO_SHA_OVER);
    	}
    	//预减库存
    	long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);//10
    	if(stock < 0) {
    		 localOverMap.put(goodsId, true);
    		return Result.error(CodeMsg.MIAO_SHA_OVER);
    	}
相关标签: 秒杀