解决 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 11 Redis
浅谈Redis的几个过期策略
May 27 Redis
解析高可用Redis服务架构分析与搭建方案
Jun 20 Redis
Redis字典实现、Hash键冲突及渐进式rehash详解
Sep 04 Redis
详解redis在微服务领域的贡献
Oct 16 Redis
基于Redis zSet实现滑动窗口对短信进行防刷限流的问题
Feb 12 Redis
解决Redis启动警告问题
Feb 24 Redis
高并发下Redis如何保持数据一致性(避免读后写)
Mar 18 Redis
redis sentinel监控高可用集群实现的配置步骤
Apr 01 Redis
Redis基本数据类型Zset有序集合常用操作
Jun 01 Redis
Redis实现主从复制方式(Master&Slave)
Jun 21 Redis
Redis Lua脚本实现ip限流示例
Jul 15 Redis
redis 解决库存并发问题实现数量控制
Redis超详细讲解高可用主从复制基础与哨兵模式方案
redis复制有可能碰到的问题汇总
Apr 03 #Redis
 Redis 串行生成顺序编码的方法实现
浅谈Redis 中的过期删除策略和内存淘汰机制
一文搞懂Redis中String数据类型
Apr 03 #Redis
使用Redis做预定库存缓存功能
You might like
PHP 高级课程笔记 面向对象
2009/06/21 PHP
php 调用ffmpeg获取视频信息的简单实现
2017/04/03 PHP
PHP如何通过date() 函数格式化显示时间
2020/11/13 PHP
javasciprt下jquery函数$.post执行无响应的解决方法
2014/03/13 Javascript
input:checkbox多选框实现单选效果跟radio一样
2014/06/16 Javascript
精通JavaScript的this关键字
2020/05/28 Javascript
js实现数组冒泡排序、快速排序原理
2016/03/08 Javascript
非常酷炫的Bootstrap图片轮播动画
2016/05/27 Javascript
一个炫酷的Bootstrap导航菜单
2016/12/28 Javascript
ui-router中使用ocLazyLoad和resolve的具体方法
2017/10/18 Javascript
解决vue中修改export default中脚本报一大堆错的问题
2018/08/27 Javascript
详解多页应用 Webpack4 配置优化与踩坑记录
2018/10/16 Javascript
通过js随机函数Math.random实现乱序
2020/05/19 Javascript
[05:08]顺网杯ISS-DOTA2赛歌 少女偶像Lunar青春演绎
2013/12/05 DOTA
[01:16:01]VGJ.S vs Mski Supermajor小组赛C组 BO3 第一场 6.3
2018/06/04 DOTA
[03:40]DOTA2抗疫特别篇《英雄年代》
2020/02/28 DOTA
Python构建网页爬虫原理分析
2017/12/19 Python
python 初始化一个定长的数组实例
2019/12/02 Python
YUV转为jpg图像的实现
2019/12/09 Python
Pycharm中Python环境配置常见问题解析
2020/01/16 Python
pycharm 代码自动补全的实现方法(图文)
2020/09/18 Python
pycharm配置安装autopep8自动规范代码的实现
2021/03/02 Python
Booking.com荷兰:全球酒店网上预订
2017/08/22 全球购物
艺术用品:Arteza
2018/11/25 全球购物
Abbacino官网:包、钱包和女士配饰
2019/04/15 全球购物
C#里面可以避免一个类被其他类继承么?如何?
2013/09/26 面试题
应征英语教师求职信
2013/11/27 职场文书
房地产营销策划方案
2014/02/08 职场文书
简历中自我评价怎么写
2014/02/12 职场文书
公司试用期员工自我评价
2014/09/17 职场文书
违反交通安全法检讨书
2014/10/24 职场文书
2016年教师反腐倡廉心得体会
2016/01/13 职场文书
MySql存储过程之逻辑判断和条件控制
2021/05/26 MySQL
HTML CSS 一个标签实现带动画的抖音LOGO
2022/04/26 HTML / CSS
Python中使用tkFileDialog实现文件选择、保存和路径选择
2022/05/20 Python
使用CSS实现音波加载效果
2023/05/07 HTML / CSS