码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • Java设计与实现“秒杀”活动之抢粽子【完整版】


    “五月榴花妖艳烘,绿杨带雨垂垂重。五月新丝缠角粽,金盘送。生绡画扇盘双凤。正是浴兰时节动。”正值端午佳节,实习公司也是例行放假三天以及给每一位员工发放了节日小礼品 ??。过完端午又将迎来618活动专场,秒杀抢单活动也是此起彼伏,从而产生刺激性消费。由此不仅引出一个内心的小疑惑,商品的秒杀又是怎么实现的呢?如果商品售卖量超过了秒杀的库存数又该如何解决呢?

    ?? 博客首页:派 大 星

    欢迎关注 点赞 ??收藏 留言

    ?? 本文由派大星原创编撰

    ?? 系列专栏:项目从0搭建

    ?? 本系列项目从设计到实现源码全部开源免费学习使用,一起追向理想,欢迎各位大佬监督打卡开发!


    在这里插入图片描述


    目录

      • ???难度分析
      • ???项目回复(秒杀)
        • ???最终效果演示
        • ???技术选型
        • ???项目需求分析
        • ???项目搭建
          • ① 创建Maven项目
          • ② 编写配置文件
          • ③ 编写Redis配置类
          • ④ 编写Controller前端控制器
            • 1. 跳转到秒杀抢购页面的接口
            • 2. 实现秒杀抢购业务
            • 3. 那么为什么使用事务呢?
            • 5. 浅识乐观锁
            • 6. 实际代码实现
            • 7. 前端实现
            • 8. 测试秒杀抢购结果
          • ⑤ ? ?注意事项(测试秒杀前了解) ? ?

    ??难度分析

    何为秒杀,简单的理解为时间很短、速度很快。举一个常见的 ??:比如某宝的618活动场景,当大量的用户在短时间内涌入,瞬间流量巨大也就是高并发场景。而秒杀活动其实是一个特别考验后台数据库以及缓存的业务,对于数据库、缓存的性能要求极高,如果系统的某个应用出现延迟反应,则就会出现用户的点击频繁,最坏的情况会造成雪崩,从而 造成系统垮掉。对于秒杀活动其实就是两个操作:

    • 商品库存减一
    • 秒杀成功后将用户添加进秒杀订单

    ??项目回复(秒杀)

    ??最终效果演示

    在这里插入图片描述

    ??技术选型

    • ??SpringBoot
    • ??Thymeleaf
    • ??Redis(事务)

    ??项目需求分析

    用户抢购秒杀商品,首先判断用户是否重复秒杀,如果重复秒杀则需要提示用户不能重复秒杀商品,否则查询库存,再进行库存的判断,如果库存数量大于0,则可以继续秒杀,否则提示秒杀结束,秒杀成功后库存减一,而用户则添加进Redis缓存,最后离线同步数据库中。
    在这里插入图片描述

    ??项目搭建

    ① 创建Maven项目

    引入相关依赖,构建所需文件目录

    在这里插入图片描述

    ② 编写配置文件

    server:
      port: 8888
    
    spring:
      redis:
      	# 服务地址
        host: localhost
        # 端口
        port: 6379
        # 数据库
        database: 0
        # 超时等待时间
        connect-timeout: 10000ms
        lettuce:
          pool:
          	# 最大等待
            max-active: 8
            # 最大等待时间
            max-wait: 10000ms
            max-idle: 200
            min-idle: 5
    
      thymeleaf:
        cache: false
        prefix: classpath:/templates/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    ③ 编写Redis配置类

    @Configuration
    public class RedisConfig {
        @Bean
        public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
            RedisTemplate redisTemplate = new RedisTemplate<>();
            //key序列化
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            //value序列化
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            //hash类型key的序列化
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            //hash类型value的序列化
            redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            return redisTemplate;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    ④ 编写Controller前端控制器

    首先创建一个DoKillController类

    1. 跳转到秒杀抢购页面的接口
    @RequestMapping("/")
    public String enterIndex(){
        return "index";
    }
    
    • 1
    • 2
    • 3
    • 4
    2. 实现秒杀抢购业务

    在执行秒杀抢购时,需要对秒杀操作添加事务处理,这样让我们的操作更有条理性,使用 Redis 事务还要配合Redis的 Watch 实现乐观锁,Watch 命令可用于监视我们的库存数量 ,如果在事务执行之前库存数量被其他命令所改动,那么事务就会被打断,因此,在 Redis 中使用Watch给库存数目加乐观锁可以了。

    3. 那么为什么使用事务呢?

    想象一个场景:如果有很多人都知道你的账户,而且同时去参加618秒杀活动,假设你账户上只有10000元,你妈妈看上一件物品需要花费8000元,你女朋友看上一件物品需要花费5000元,而且你自己同时也看上了一件物品需要花费1000元,如果不采用事务处理会出现什么操作呢?

    在这里插入图片描述

    当同时去消费的时候,结果是账户上最终会为一个负数,很显然银行账户是不会允许我们出现这种亏本的情况出现的,而我们采用了事务操作,就相当于使用了乐观锁的机制。

    5. 浅识乐观锁

    在这里插入图片描述

    乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会去修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

    通俗的来讲就是保证我们在同时进行秒杀抢购时不会使得我们的账户最终出现负数的情况,保证了数据的完整性。

    6. 实际代码实现
        @RequestMapping("/doGrabZz")
        @ResponseBody
        public Boolean doGrabZz(HttpServletRequest request){
            String gid = request.getParameter("gid");
            String uid = UUID.randomUUID().toString().substring(1, 5);
            //1. 拼接key
            // 库存key
            String kcKey = "sk:"+gid+":qt";
            // 秒杀用户的key
            String userKey = "sk:"+gid+":user";
    
            //监视库存
            redisTemplate.watch(kcKey);
    
            //获取库存 如果库存为null,秒杀还未开始
            String kc = String.valueOf(redisTemplate.opsForValue().get(kcKey));
            if (kc == null){
                System.out.println("秒杀还未开始,请稍等...");
                return false;
            }
    
            //判断用户是否重复秒杀
            if (redisTemplate.opsForSet().isMember(userKey,uid)){
                System.out.println("已经秒杀成功,不能重复秒杀");
                return false;
            }
            //判断如果商品数量,库存数量小于1,秒杀结束
            if (Integer.parseInt(kc) <= 0){
                System.out.println("秒杀已经结束");
                return false;
            }
    
            List txRes = (List) redisTemplate.execute(new SessionCallback() {
                @Override
                public Object execute(RedisOperations operations) throws DataAccessException {
                    //开启事务
                    operations.multi();
                    redisTemplate.opsForValue().decrement(kcKey);
                    redisTemplate.opsForSet().add(userKey,uid);
                    //执行
                    return operations.exec();
                }
            });
            if (txRes == null || txRes.size() == 0){
                System.out.println("秒杀失败了");
                return false;
            }
            System.out.println("秒杀成功了");
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    注: 为了方便演示,此案例并没有连接数据库进行操作,而是创建一些假数据进行测试

    7. 前端实现

    在这里插入图片描述

    8. 测试秒杀抢购结果

    在这里插入图片描述

    ⑤ 注意事项(测试秒杀前了解)

    • 由于没有连接数据库进行持久化操作,所以需要在测试前在Redis中创建一些库存数据

    在这里插入图片描述

    • 使用redis使用事务时出现 ERR EXEC without MULTI

    错误示例:

    //秒杀过程 使用事务
    redisTemplate.multi();
    
    redisTemplate.opsForValue().decrement(kcKey);
    redisTemplate.opsForSet().add(userKey,uid);
    //执行
    List res = redisTemplate.exec();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    执行上述代码则会出现问题

    根据查阅官方文档可知:RedisTemplate并不支持如上代码操作:

    List txRes = (List) redisTemplate.execute(new SessionCallback() {
        @Override
        public Object execute(RedisOperations operations) throws DataAccessException {
            //开启事务
            operations.multi();
            redisTemplate.opsForValue().decrement(kcKey);
            redisTemplate.opsForSet().add(userKey,uid);
            //执行
            return operations.exec();
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    新鲜出炉的代码将会及时更新到Gitee仓库

    以上代码属于部分实现,想要了解完整版请移步 链接: 派大星的Gitee仓库

    让好奇心促使技术的成长

    在这里插入图片描述

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    java获取文件路径总结
    探索SPI:深入理解原理、源码与应用场景
    【PyQt学习篇 · ③】:QObject - 神奇的对象管理工具
    BLS签名算法
    SQL ZOO —— 6 JOIN
    R3live
    南大通用数据库-Gbase-8a-学习-42-定位与释放锁
    计算机网络 —— 运输层(UDP和TCP)
    javascript面向对象完全指北
    使用react-grid-layout和echarts-for-react实现一个支持拖拽的自定义响应式dashboard页面
  • 原文地址:https://blog.csdn.net/m0_67401545/article/details/126107651
    • 最新文章
    • 攻防演习之三天拿下官网站群
      数据安全治理学习——前期安全规划和安全管理体系建设
      企业安全 | 企业内一次钓鱼演练准备过程
      内网渗透测试 | Kerberos协议及其部分攻击手法
      0day的产生 | 不懂代码的"代码审计"
      安装scrcpy-client模块av模块异常,环境问题解决方案
      leetcode hot100【LeetCode 279. 完全平方数】java实现
      OpenWrt下安装Mosquitto
      AnatoMask论文汇总
      【AI日记】24.11.01 LangChain、openai api和github copilot
    • 热门文章
    • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
      奉劝各位学弟学妹们,该打造你的技术影响力了!
      五年了,我在 CSDN 的两个一百万。
      Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
      面试官都震惊,你这网络基础可以啊!
      你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
      心情不好的时候,用 Python 画棵樱花树送给自己吧
      通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
      13 万字 C 语言从入门到精通保姆级教程2021 年版
      10行代码集2000张美女图,Python爬虫120例,再上征途
    Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
    正则表达式工具 cron表达式工具 密码生成工具

    京公网安备 11010502049817号