商品秒杀系统-秒杀模块的开发【https://github.com/XCXCXCXCX/KillSystem】

在开始设计秒杀模块的时候由于对一些知识了解还不够,设计的比较复杂,想把秒杀思路改变的心路历程都记录下来。

一、秒杀思路变形记

最初思路:

    三层秒杀

    1.在tomcat维护管理每个商品库存的线程,商品库存为0后撤销线程

    2.在redis记录库存量和订单信息

    3.在mysql记录库存量和订单信息

每次发起抢购请求,在redis写入订单信息并且在tomcat维护的库存线程上进行减一的原子操作

在后台开启一个线程,定期将原子计数器中的库存数和redis中的支付成功的订单信息加入到FIFO队列写入mysql

预计,

优点:

服务器响应速度快,只要redis写入成功且原子计数器减一即返回成功

缺点:

1.开发上稍麻烦 

2.一旦tomcat上的线程阻塞或死亡用户将无法抢购

3.如果抢购的商品过多会维护过多的线程,占用系统资源导致崩溃

4.原子计数器是采用CAS操作,当多个的线程进行操作时失败操作会增多,而单线程操作处理速度又有局限性

5.组件之间的联系很重要,比如新库存数写入mysql和订单信息写入mysql本应是合并事务,如果其中一个线程出现问题都会导致数据不一致

后期思路:

    两层秒杀

    1.在redis记录库存量和订单信息

    2.在mysql持久化库存量和订单信息

每次发起抢购请求,在redis中创建订单并令库存减一,支付成功后将订单信息、库存减一合并为事务操作后加入到FIFO队列

优点:

1.开发思路上组件清晰

2.redis是单线程,能保证操作原子性,且处理速度非常可观

缺点:

1.redis的连接量有限,每次在redis中的操作开启连接和关闭连接要造成许多开销

2.如果数据写入mysql失败,将会丢失

二、后台开发

不多说直接贴代码,注释的很清楚了,controller和InitFIFOListener的具体实现如下:

    1.controller层代码

@RequestMapping("/createOrder.do")
	@ResponseBody
	public ServerResponse createOrder(Order order,HttpServletRequest request,HttpSession session) {
		if (session.getAttribute("tel_num") == null || session.getAttribute("passwd") == null) {
			return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户登录已过期");
		}
		
		//1.一个ip5分钟内只接受一次请求 
		//ps.暂未编码,编码基本思路是在Spring管理一个拦截器,重写preHandler接口
		//由于会影响到测试效果,先注释掉
		
		//2.判断商品是否存在
		Goods goods = new Goods();
		goods.setGoods_id(order.getGoods_id());
		if(goodsService.getGoodsById(goods)==null) {
			return ServerResponse.createByErrorMessage("商品不存在!");
		}
		//3. 一个用户不允许购买同一商品多次(在mysql中检查)
		//ps:在redis中的检查已经封装到createOrderInRedis方法中
		//由于会影响到测试效果,先注释掉
		/*
		String tel_num = (String)session.getAttribute("tel_num");
		int goods_id = order.getGoods_id();
		if(orderService.orderIsExist(tel_num,goods_id)) {
			return ServerResponse.createByErrorMessage("您已抢购过该商品!请稍后再试!");
		}
		*/
		//使用MD5算法加密用户信息和当前时间的字符串,构造不重复order_id
		//(1)防止生成相同的订单号导致的创建订单失败
		//(2)避免使用自增id,自增id能产生的订单数有局限性
		String order_id = MD5Util.MD5EncodeUtf8(order.getTel_num()+order.getGoods_id()+DateTime.now().toString());
		order.setOrder_id(order_id);
		//4.如果该订单关联商品库存为0,返回“商品已抢光”
		//否则继续进行
		if(!goodsService.checkGoodsStockInRedis(order)) {
			return ServerResponse.createByErrorMessage("商品库存已抢光!秒杀接口已关闭!");
		}
		//5.检查是否存在该订单
		if(orderService.orderIsExistInRedis(order)) {
			log.error("订单已经存在!");
			return ServerResponse.createByErrorMessage("订单已经存在!");
		}
		
		//6.执行创建订单操作
		try {
			
			//6.1.在redis中创建订单,若失败,返回“创建订单失败”
			if(orderService.createOrderInRedis(order) == 0) {
				log.error("订单已经存在!");
				return ServerResponse.createByErrorMessage("订单已经存在!");
			}
			
			//6.2.在redis中库存减一,若失败,撤回创建订单操作并返回“创建订单失败”
			if(goodsService.decrGoodsStock(order) == 0) {
				log.error("库存已抢光!");
				return ServerResponse.createByErrorMessage("库存已抢光!");
			}
			
		} catch (Exception e) {
			// TODO: handle exception
			log.error("redis中创建订单失败!", e);
			return ServerResponse.createByErrorMessage("创建订单失败!");
		}
		//创建定时器对象
        //Timer t=new Timer();
        //在3秒后执行MyTask类中的run方法
        //t.schedule(new MyTask(order), 360000);
		System.out.println("创建订单成功!");
		return ServerResponse.createBySuccess("创建订单成功!", order);
		
	}

其中注释的代码是之前用来计时判断创建订单操作是否失效,后来想到在redis有设置失效时间的操作,效果好太多

ps:这里给自己再提个醒!!这样启动计时器方法的非常不好,每次请求都会开启一个长时间存在的线程,可想而知,效果有多差了吧。

    2.goodsService中的相关方法

    goodsService接口类

public interface GoodsService extends BaseService<Goods>{
	PageInfo<Map<String, Goods>> select(Goods goods,int pageNum,int pageSize);
	PageInfo<Map<String, Goods>> selectById(Goods goods,int pageNum,int pageSize);
	Goods getGoodsById(Goods goods);
	
  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值