问题描述

在众多抢购活动中,在有限的商品数量的限制下如何保证抢购到商品的用户数不能大于商品数量,也就是不能出现超卖的问题;还有就是抢购时会出现大量用户的访问,如何提高用户体验效果也是一个问题,也就是要解决秒杀系统的性能问题。

解决超卖的方案

每一个用户只能抢购一件商品的限制;在数据库减库存时加上库存数量判断,库存数量为0时阻止秒杀订单的生成。

数据库加唯一索引:防止用户重复购买
SQL加库存数量判断:防止库存变为负数

解决性能问题

使用Redis缓存预减库存,减少数据库的访问。因为缓存的访问速度比数据库访问快得多。
使用内存标记,减少Redis缓存的访问次数。
使用队列等异步手段,请求先入队缓冲,异步下单,将数据写入数据库,增强用户体验。
性能解决方案
总体思路就是要减少对数据库的访问,尽可能将数据缓存到Redis缓存中,从缓存中获取数据。

在系统初始化时,将商品的库存数量加载到Redis缓存中;
接收到秒杀请求时,在Redis中进行预减库存,当Redis中的库存不足时,直接返回秒杀失败,否则继续进行第3步;
将请求放入异步队列中,返回正在排队中;
服务端异步队列将请求出队,出队成功的请求可以生成秒杀订单,减少数据库库存,返回秒杀订单详情。
用户在客户端申请秒杀请求后,进行轮询,查看是否秒杀成功,秒杀成功则进入秒杀订单详情,否则秒杀失败。

缺陷

由于是通过异步队列写入数据库中,可能存在数据不一致。

实现

系统初始化,将库存加载到Redis缓存中

Controller 实现 InitializingBean 接口,并重写 afterPropertiesSet方法,这样可以保证系统初始化的时候讲物品库存加载到Redis缓存中
@Overridepublic 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);// 设置商品是否卖完的内存标志,初始化时为false,没有卖完}}

预减库存,入队

 //利用map定位某件商品是否已经卖完,卖完了就可以不用去访问Redis缓存了,分别放置<商品ID,是否卖完标志>private HashMap<Long, Boolean> localOverMap = new HashMap<>();public Result<Integer> miaosha(Model model, MiaoshaUser user,@RequestParam("goodsId")long goodsId) {model.addAttribute("user", user);if(user == null) {return Result.error(CodeMsg.SESSION_ERROR);}//做了一个内存标记,减少Redis缓存访问,一旦对于商品库存没有了,over为真,将直接返回秒杀失败,不进行后面的Redis缓存访问。boolean over = localOverMap.get(goodsId);if (over) {return Result.error(CodeMsg.MIAO_SHA_OVER);}//从Redis缓存中进行预减库存long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, "" + goodsId);// 预减库存时库存不足,直接返回秒杀失败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); }//入队,进入异步队列MiaoshaMessage mm = new MiaoshaMessage();mm.setUser(user);mm.setGoodsId(goodsId);//在异步队列中写入用户信息和对应商品ID,因为服务端可以根据用户ID和商品ID定位用户是否秒杀过对应商品,并且服务端需要根据商品ID去查询数据库中对应商品的库存是否足够。sender.sendMiaoshaMessage(mm);//并非直接返回下单结果,而是分步骤进行,相当于将提交操作变成两段式,先申请后确认。//申请之后进入排队中,确认是否可以秒杀成功由服务端进行确认。return Result.success(0);//0代表正在排队中}

RabbitMQ实现异步队列

// 配置文件,构建异步队列
@Configuration
public class MQConfig {public static final String MIAOSHA_QUEUE = "miaosha.queue";// Direct模式@Beanpublic Queue miaoShaQueue(){return new Queue(MIAOSHA_QUEUE,true);}
}
// 发送
public class MQSender {@AutowiredAmqpTemplate amqpTemplate;public void sendMiaoshaMessage(MiaoshaMessage mm) {// 将对象转化为String进行发送String msg = RedisService.beanToString(mm);log.info("send message:"+msg);amqpTemplate.convertAndSend(MQConfig.MIAOSHA_QUEUE, msg);}
}
// 接收到请求,进行处理
public class MQReceiver {// 监听一个名为MQConfig.MIAOSHA_QUEUE的队列@RabbitListener(queues = MQConfig.MIAOSHA_QUEUE)public void receive(String message) {log.info("receive message:"+message);// 将接收到的String转化回Object对象,可以从中获取相应信息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);}
}

服务端操作数据库进行 减少库存 下订单 秒杀订单

// SQL加库存数量判断:利用stock_count > 0限制,防止出现超卖,只有stock_count > 0时才能更新库存操作@Update("update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0")public int reduceStock(MiaoshaGoods g);
// 判断是否可以对数据库进行减库存
public boolean reduceStock(GoodsVo goods) {MiaoshaGoods g = new MiaoshaGoods();g.setGoodsId(goods.getId());int res = goodsDao.reduceStock(g);return res > 0;
}//减少库存 下订单 秒杀订单 这三个操作必须是原子性,利用@Transactional注解将这3个操作放在一个事务里面,保证同时成功,否则失败
@Transactional
public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {//判断是否可以减少库存boolean success = goodsService.reduceStock(goods);// 库存足够if (success) {//下订单,秒杀订单return orderService.creatOrder(user, goods);} else { //无法减少库存,说明该对应商品库存不足了return null;}
}

其他类似解决方案说明

解决方案1

将存库MySQL迁移到Redis中,所有的写操作放到内存中,由于Redis中不存在锁故不会出现互相等待,并且由于Redis的写性能和读性能都远高于MySQL,这就解决了高并发下的性能问题。然后通过队列等异步手段,将变化的数据异步写入到DB中。

优点:解决性能问题

缺点:没有解决超卖问题,同时由于异步写入DB,存在某一时刻DB和Redis中数据不一致的风险。

解决方案2

引入队列,然后将所有写DB操作在单队列中排队,完全串行处理。当达到库存阀值的时候就不在消费队列,并关闭购买功能。这就解决了超卖问题。

优点:解决超卖问题,略微提升性能。

缺点:性能受限于队列处理机处理性能和DB的写入性能中最短的那个,另外多商品同时抢购的时候需要准备多条队列。

解决方案3

将提交操作变成两段式,先申请后确认。然后利用Redis的原子自增操作(相比较MySQL的自增来说没有空洞),同时利用Redis的事务特性来发号,保证拿到小于等于库存阀值的号的人都可以成功提交订单。然后数据异步更新到DB中。

优点:解决超卖问题,库存读写都在内存中,故同时解决性能问题。

缺点:由于异步写入DB,可能存在数据不一致。另可能存在少买,也就是如果拿到号的人不真正下订单,可能库存减为0,但是订单数并没有达到库存阀值。

秒杀问题解决(超买超卖 性能)相关推荐

  1. kdj超卖_最全的KDJ(超买超卖)指标实用技巧(图解)

    来源| 天信投资(ID: TXTZ1996) 每一个成熟的操作者都有一套自己的交易系统,而对于大部分操作者来说,指标是一个完善的操作系统中必不可少的组成要素.很多人一提到指标就比较不屑,认为那是骗人的 ...

  2. kdj超卖_kdj超买超卖是什么意思?kdj指标里说的“超买区,超卖区”什么意思?...

    在分析股票的走势以及股票买卖点的时候,我们经常会使用一些指标来作为工具进行分析,比如KDJ指标等,最近有人问kdj超买超卖是什么意思?对此小编给大家搜集了有关KDJ指标和超买超卖指标的一些内容. kd ...

  3. kdj超卖_一个判断股票超买超卖现象的指标——KDJ,简单明了,准确且省心

    KDJ是一种指示"超买超卖"的指标,跟MACD不同,这类指标的最大特点是有上下的界限. KDJ指标,是由K,D,J三根线,以及0-100的数轴线组成的,公式我就不写了,参数一般是( ...

  4. matlab中Cci,【指标量化】超买超卖——CCI 顺势指标

    本帖最后由 TaoZzzzz 于 2018-4-16 15:30 编辑 [指标量化]超买超卖--CCI 顺势指标 1523335607(1).png (60.65 KB, 下载次数: 13) 2018 ...

  5. kdj超卖_【教你一招】KDJ超买超卖指标

    原标题:[教你一招]KDJ超买超卖指标 来源:K线密码 KDJ是一个超买超卖指标,对股价高位低位的研判.根据KDJ的取值,我们将KDJ区域分为 1.超买区:K.D.J这三值在20以下为超卖区,是买入信 ...

  6. kdj超卖_kdj超卖是什么意思?kdj超买超卖区别是什么

    kdj超卖是什么意思?kdj超买超卖区别是什么? 对于大多数股民来说,指标应用是股市投资必不可少的操作系统,指标的主要作用就是用来作参考,辅助自己进行股票投资.今天,在这里我们所要谈论的是kdj指标, ...

  7. kdj超卖_超短KDJ往往用于哪方面,超买超卖?看完这五分钟就明白了!

    一.KDJ指标的原理 随机指标KDJ一般是根据统计学的原理,通过一个特定的周期(常为9日.9周等)内出现过的最高价.最低价及最后一个计算周期的收盘价及这三者之间的比例关系,来计算最后一个计算周期的未成 ...

  8. WR威廉指标-反映市场超买超卖现象(短期)

    W&R利用震荡点来反映市场超买超卖现象,从而提出有效的信号来分析市场短期行情走势,判断市场强弱分界.W&R是较常用的短期研判指标.它可及时准确的选择出股价异动前的瞬间,即加速下跌的瞬间 ...

  9. kdj超卖_KDJ指标的超买与超卖

    KDJ指标的超买与超卖 KDJ指标超买超卖是KDJ指标最核心的技术分析方法. (1)KDJ指标超买.KDJ出现超买,说明上涨行情很难持续,股价未来有下跌的风险. 通常情况下,KDJ指标超买具有如下几个 ...

最新文章

  1. 黑帽SEO:Google为什么会屏蔽你的网站
  2. 算法:最接近的三数之和
  3. java凌晨12点_java - JAVA如果我在每天中午12点之后安排我的时间表,会发生什么? - SO中文参考 - www.soinside.com...
  4. c char*转int_C语言中的char类型也有signed和unsigned?字符也有正负之分吗?
  5. js 数组 去重 算法(转载)
  6. dropbox离线安装版下载方法
  7. Qt 5.9.1 连 MYSQL 5.7数据库
  8. 给LINUX添加一个开机执行脚本
  9. windows系统安装指南-微PE版
  10. knn闽南语是什么意思_林北是什么意思什么梗 林北梗的出处是什么
  11. 2.Java再识:使用MyEclipse开发java程序
  12. Elasticsearch:使用 Filebeat 从 Node.js Web 应用程序提取日志
  13. 数据结构:顺序表的就地逆置
  14. C++ 炼气期之基本结构语法中的底层逻辑
  15. 梅尔频谱和梅尔倒谱的初次理解和使用
  16. 还在用Windows虚拟机?快来试试Windows自带的Sandbox吧!
  17. Linux用户空间和内核空间是什么?
  18. Ubuntu 16.04系统实用插件安装方法
  19. Java 17 新功能介绍(LTS)
  20. vue-cli3.x创建项目失败

热门文章

  1. mac下如何安装mysql以及忘记密码如何重置密码
  2. cmd 查询bios序列号_取计算机特征码(网卡MAC、硬盘序列号、CPU ID、BIOS编号)
  3. 制作好的标签打印出来却不完整怎么办
  4. oracle数据库查看版本号
  5. 公共关系礼仪实务章节测试题——公共关系的类型(五)
  6. 通信信道容量、带宽的理解
  7. 怎么打云开服务器iis_云服务器怎么重启iis
  8. Kong配置service负载均衡
  9. 工商股权变更需要哪些资料
  10. 网络安全入门——从零开始