解决 Redis 秒杀超卖场景的高并发


Posted in Redis onApril 12, 2022

1 什么是秒杀

秒杀最直观的定义:在高并发场景下而下单某一个商品,这个过程就叫秒杀

解决 Redis 秒杀超卖场景的高并发

【秒杀场景】

  • 火车票抢票
  • 双十一限购商品
  • 热度高的明星演唱会门票

2 为什么要防止超卖

早起的12306购票,刚被开发出来使用的时候,12306会经常出现 超卖 这种现象,也就是说车票只剩10张了,却被20个人买到了,这种现象就是超卖!

还有在高并发的情况下,如果说没有一定的保护措施,系统会被这种高流量造成宕机

  • 库存100件 你卖了1000件 等着亏钱吧!
  • 防止黑客
  • 假如我们网站想下发优惠给群众,但是被黑客利用技术将下发给群众的利益收入囊中
  • 保证用户体验
  • 高并发场景下,网页不能打不开、订单不能支付 要保证网站的使用!

3 单体架构常规秒杀

3.1 常规减库存代码

/**
 * @Author oldlu
 */
@Service
@Transactional  //控制事务
public class OrderServiceImpl implements OrderService {

    @Autowired
    private StockMapper stockMapper;

    private OrderMapper orderMapper;

    //在非并发情况下无问题
    @Override
    public Integer kill(Integer id) {
        //根据商品id校验库存是否还存在
        Stock stock = stockMapper.checkStock(id);
        //当已售和库存相等就库存不足了
        if(stock.getSale().equals(stock.getCount())){
            throw new RuntimeException("库存不足!");
        }else{
            //扣除库存  (已售数量+1)
            stock.setSale(stock.getSale()+1);
            stockMapper.updateSale(stock);   //更新信息
            //创建订单
            Order order = new Order();
            order.setSid(stock.getId()).setName(stock.getName()).setCreateDate(new Date());
            orderMapper.createOrder(order); //创建订单
            return order.getId();   //mybatis主键生成策略 直接返回创建的id
        }
    }
}

测试controller

/**
 * @Author oldlu
 */
@RestController
@RequestMapping("/stock")
public class StockController {
    @Autowired
    private OrderService orderService;
    //开发秒杀方法
    @GetMapping("/kill/{id}")
    public String kill(@PathVariable("id") Integer id){
        System.out.println("秒杀商品的ID=====================>"+id);
        try {
            //根据秒杀商品id调用秒杀业务
            Integer orderId = orderService.kill(id);
            return "秒杀成功,订单ID为:"+String.valueOf(orderId);
        }catch (Exception e){
            e.printStackTrace();
            return e.getMessage();
        }
    }
}

正常情况看不会有什么问题,就是你访问一下库存少一个

3.2 模拟高并发

解决 Redis 秒杀超卖场景的高并发

解决 Redis 秒杀超卖场景的高并发

3.3 超卖现象

解决 Redis 秒杀超卖场景的高并发

解决 Redis 秒杀超卖场景的高并发

3.4 分析原因

线程不安全,方法就是加锁,单机简单加锁即可解决,如果是分布式集群模式搭建那就要考虑分布式锁

4 简单实现悲观乐观锁解决单体架构超卖

4.1 悲观锁

/**
 * @Author oldlu
 */
@RestController
@RequestMapping("/stock")
public class StockController {

    @Autowired
    private OrderService orderService;

    //开发秒杀方法
    @GetMapping("/kill/{id}")
    public String kill(@PathVariable("id") Integer id){
        System.out.println("秒杀商品的ID=====================>"+id);
        try {
            //使用悲观锁
            synchronized (this){
                //根据秒杀商品id调用秒杀业务
                Integer orderId = orderService.kill(id);
                return "秒杀成功,订单ID为:"+String.valueOf(orderId);
            }
        }catch (Exception e){
            e.printStackTrace();
            return e.getMessage();
        }
    }

}

这样效率很差会造成线程阻塞,线程排队问题,对用户的体验不是很好,必须处理完一个才能继续.

4.2 乐观锁

解决 Redis 秒杀超卖场景的高并发

/**
     * 扣除库存
     * @param stock
     */
    public void updateSale(Stock stock){
        //扣除库存  (已售数量+1)
        stock.setSale(stock.getSale()+1);
        stockMapper.updateSale(stock);   //更新信息
    }

/**
 * 扣除库存
 * @param stock
 */
public void updateSale(Stock stock){
    //在sql层面完成销量+1 和 版本号 +1 并且根据商品id和版本号同时查询更新的商品
    Integer updRows = stockMapper.updateSale(stock);   //更新信息
    if(updRows == 0){   //代表没有拿到版本号
        throw new RuntimeException("抢购失败,请重试!");
    }
}

也就是没更新成功说明已经秒杀完了, 相对悲观锁而言乐观锁保证了一定的效率,而不像悲观锁那样会造成线程阻塞使用乐观锁需要使用版本号,在操作数据的时候要对版本号进行更新

4.3 redis锁setnx

解决 Redis 秒杀超卖场景的高并发

但是上述代码在高并发,可能其他线程会释放别人的锁

解决 Redis 秒杀超卖场景的高并发

4.4 使用Redision

https://github.com/redisson/redisson

解决 Redis 秒杀超卖场景的高并发

解决 Redis 秒杀超卖场景的高并发

5 分布式锁的解决方案

实现分布式锁的解决方案

6 采用缓存队列防止超卖

高并发缓存队列防止溢出解决方案

到此这篇关于Redis高并发场景下秒杀超卖解决的文章就介绍到这了!

Redis 相关文章推荐
浅谈Redis存储数据类型及存取值方法
May 08 Redis
基于Redis实现分布式锁的方法(lua脚本版)
May 12 Redis
Redis可视化客户端小结
Jun 10 Redis
Redis做数据持久化的解决方案及底层原理
Jul 15 Redis
Redis分布式锁Redlock的实现
Aug 07 Redis
Redis 常见使用场景
Aug 30 Redis
Jedis操作Redis实现模拟验证码发送功能
Sep 25 Redis
CentOS8.4安装Redis6.2.6的详细过程
Nov 20 Redis
redis击穿 雪崩 穿透超详细解决方案梳理
Mar 17 Redis
Redis全局ID生成器的实现
Jun 05 Redis
浅谈Redis变慢的原因及排查方法
Jun 21 Redis
如何使用注解方式实现 Redis 分布式锁
Jul 23 Redis
redis 解决库存并发问题实现数量控制
Redis超详细讲解高可用主从复制基础与哨兵模式方案
redis复制有可能碰到的问题汇总
Apr 03 #Redis
 Redis 串行生成顺序编码的方法实现
浅谈Redis 中的过期删除策略和内存淘汰机制
一文搞懂Redis中String数据类型
Apr 03 #Redis
使用Redis做预定库存缓存功能
You might like
Search Engine Friendly的URL设计
2006/10/09 PHP
php实现的仿阿里巴巴实现同类产品翻页
2009/12/11 PHP
PHP5与MySQL数据库操作常用代码 收集
2010/03/21 PHP
PHP pathinfo()获得文件的路径、名称等信息说明
2011/09/13 PHP
114啦源码(114la)不能生成地方房产和地方报刊问题4级页面0字节的解决方法
2012/01/12 PHP
php实现字符串首字母大写和单词首字母大写的方法
2015/03/14 PHP
PHP实现过滤掉非汉字字符只保留中文字符
2015/06/04 PHP
php中各种定义变量的方法小结
2017/10/18 PHP
php curl获取https页面内容,不直接输出返回结果的设置方法
2019/01/15 PHP
详解如何实现Laravel的服务容器的方法示例
2019/04/15 PHP
JS调用CS里的带参方法实例
2013/08/01 Javascript
JQuery教学之性能优化
2014/05/14 Javascript
JQuery实现动态表格点击按钮表格增加一行
2014/08/24 Javascript
Javascript模拟加速运动与减速运动代码分享
2014/12/11 Javascript
原生js实现弹出层登录拖拽功能
2016/12/05 Javascript
详解vue-router基本使用
2017/04/18 Javascript
JS给按钮添加跳转功能类似a标签
2017/05/30 Javascript
vue.js删除动态绑定的radio的指定项
2017/06/02 Javascript
JavaScript之排序函数_动力节点Java学院整理
2017/06/30 Javascript
vue项目中使用tinymce编辑器的步骤详解
2018/09/11 Javascript
Vue 电商后台管理项目阶段性总结(推荐)
2020/08/22 Javascript
[01:08]DOTA2次级职业联赛 - Wings 战队宣传片
2014/12/01 DOTA
Python open读写文件实现脚本
2008/09/06 Python
如何使用 Pylint 来规范 Python 代码风格(来自IBM)
2018/04/06 Python
Python基础教程之异常详解
2019/01/10 Python
Python 函数绘图及函数图像微分与积分
2019/11/20 Python
Python 读取 YUV(NV12) 视频文件实例
2019/12/09 Python
Pytorch之parameters的使用
2019/12/31 Python
Python 转移文件至云对象存储的方法
2021/02/07 Python
工作自荐信
2013/12/11 职场文书
检讨书怎么写
2015/05/07 职场文书
《风筝》教学反思
2016/02/23 职场文书
Python深度学习之实现卷积神经网络
2021/06/05 Python
详解Python自动化之文件自动化处理
2021/06/21 Python
Pytest中conftest.py的用法
2021/06/27 Python
不想升级Win11?教你彻底锁定老版Windows系统的方法(附下载地址)
2022/09/23 数码科技