Redis的优惠券秒杀问题(五)全局唯一ID 以及 秒杀下单

news2024/11/18 17:41:26

Redis的优惠券秒杀问题(五)全局唯一ID 以及 秒杀下单

关于优惠秒杀问题的Redis实现章节总览

全局唯一ID 

场景分析 

不能用自增的原因

id的规律性太明显

受单表数据量的限制

全局唯一ID的条件

全局唯一ID的Redis实现

代码实现

单元测试 

其它全局唯一ID的生成策略 

秒杀下单 

场景分析 

优惠券秒杀的下单功能的实现

代码实现 

存在问题


Redis的优惠券秒杀问题(五)全局唯一ID 以及 秒杀下单

关于优惠秒杀问题的Redis实现章节总览

我们要讲述的问题大致如下所示,根据黑马程序员视频教程,会分离出Redis关于秒杀问题的核心知识点进行讲解!

  • 全局唯一ID
  • 实现优惠券秒杀下单  
  • 超卖问题  
  • 一人一单
  • 分布式锁  
  • Redis优化秒杀  
  • Redis消息队列实现异步秒杀

全局唯一ID 

场景分析 

首先,我们依照黑马的项目来进行分析,在什么情况下要使用到这个全局唯一ID。

在黑马点评这个项目中,使用的商品其实也就是优惠券

当用户抢购时,就会生成订单并保存到 tb_voucher_order 这张表中 

CREATE TABLE `tb_voucher_order`  (
  `id` bigint NOT NULL COMMENT '主键',
  `user_id` bigint UNSIGNED NOT NULL COMMENT '下单的用户id',
  `voucher_id` bigint UNSIGNED NOT NULL COMMENT '购买的代金券id',
  `pay_type` tinyint UNSIGNED NOT NULL DEFAULT 1 COMMENT '支付方式 1:余额支付;2:支付宝;3:微信',
  `status` tinyint UNSIGNED NOT NULL DEFAULT 1 COMMENT '订单状态,1:未支付;2:已支付;3:已核销;4:已取消;5:退款中;6:已退款',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
  `pay_time` timestamp NULL DEFAULT NULL COMMENT '支付时间',
  `use_time` timestamp NULL DEFAULT NULL COMMENT '核销时间',
  `refund_time` timestamp NULL DEFAULT NULL COMMENT '退款时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;

但是,这张SQL表里面的主键id,是不可以使用自增的!!! 

不能用自增的原因

id的规律性太明显

如果使用自增的话,用户可以根据两笔订单的ID,来判断这段时间内订单的量。 

受单表数据量的限制

订单的数据量一般很大,一天可能会有几百万,如果使用自增ID,就很难分库分表了!

全局唯一ID的条件

全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:

全局唯一ID的Redis实现

为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其它信息 

符号位1bit,永远为0 

时间戳31bit,以秒为单位,可以使用69

序列号32bit,秒内的计数器,支持每秒产生2^32个不同ID 

代码实现

RedisIdWorker-Redis全局ID生成器工具类

/**
 * Redis的全局ID生成器
 */
@Component
public class RedisIdWorker {

    /**
     * 开始时间戳
     * 2022.1.1的时间戳
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;

    /**
     * 序列号的位数
     */
    private static final int COUNT_BITS = 32;

    private StringRedisTemplate stringRedisTemplate;

    // 构造器注入
    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 生成全局ID
     * Long 类型 8个字节 64个bit
     * 符号位(1bit) + 时间戳(31bit) + 序列号(32bit)
     * @param keyPrefix
     * @return
     */
    public long nextId(String keyPrefix) {
        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        // ID的时间戳
        long timestamp = nowSecond - BEGIN_TIMESTAMP;

        // 2.生成序列号
        // 2.1.获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));

        // 2.2.自增长
        // icr表示自增长,keyPrefix表示业务类型,一天一个key
        // 例如: icr:order:2022:11:16
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);

        // 3.拼接并返回 (位运算)
        return timestamp << COUNT_BITS | count;
    }
}

位运算实现字符串拼接 

timestamp << COUNT_BITS | count 

将 timestamp 向左移动32位,在与 count 做“或”运算(一个为真就为真!)

单元测试 

我们可以看看并发情况下,该工具类的性能怎么样

@Resource
private RedisIdWorker redisIdWorker;

private ExecutorService es = Executors.newFixedThreadPool(500);

@Test
void testIdWorker() throws InterruptedException {

    CountDownLatch latch = new CountDownLatch(300);

    Runnable task = () -> {
        for (int i = 0; i < 100; i++) {
            long id = redisIdWorker.nextId("order");
            System.out.println("id = " + id);
        }
        latch.countDown();
    };
    long start = System.currentTimeMillis();
    for (int i = 0; i < 300; i++) {
        es.submit(task);
    }
    latch.await();
    long end = System.currentTimeMillis();

    System.out.println(end - start);
}

运行结果如下

生成3万个订单ID 

其它全局唯一ID的生成策略 

  • UUID
  • Redis自增
  • snowflake算法
  • 数据库自增

秒杀下单 

场景分析 

每个店铺都可以发布优惠券,分为平价券和特价券。平价券可以任意购买,而特价券需要秒杀抢购

tb_voucher:优惠券的基本信息,优惠金额、使用规则等(普通券+秒杀券)

CREATE TABLE `tb_voucher`  (
  `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
  `shop_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '商铺id',
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '代金券标题',
  `sub_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '副标题',
  `rules` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '使用规则',
  `pay_value` bigint UNSIGNED NOT NULL COMMENT '支付金额,单位是分。例如200代表2元',
  `actual_value` bigint NOT NULL COMMENT '抵扣金额,单位是分。例如200代表2元',
  `type` tinyint UNSIGNED NOT NULL DEFAULT 0 COMMENT '0,普通券;1,秒杀券',
  `status` tinyint UNSIGNED NOT NULL DEFAULT 1 COMMENT '1,上架; 2,下架; 3,过期',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;

tb_seckill_voucher:优惠券的库存、开始抢购时间,结束抢购时间。特价优惠券才需要填写这些信息(秒杀券拓展字段)

CREATE TABLE `tb_seckill_voucher`  (
  `voucher_id` bigint UNSIGNED NOT NULL COMMENT '关联的优惠券的id',
  `stock` int NOT NULL COMMENT '库存',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `begin_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '生效时间',
  `end_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '失效时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`voucher_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '秒杀优惠券表,与优惠券是一对一关系' ROW_FORMAT = COMPACT;

优惠券秒杀的下单功能的实现

  • 秒杀是否开始或结束,如果尚未开始或已经结束则无法下单
  • 库存是否充足,不足则无法下单

代码实现 

@Resource
private ISeckillVoucherService seckillVoucherService;

@Resource
private RedisIdWorker redisIdWorker;

@Override
@Transactional
public Result seckillVoucher(Long voucherId) {

    // 1. 查询优惠券
    SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
    LocalDateTime nowTime = LocalDateTime.now();

    // 2. 判断秒杀是否开始
    if (nowTime.isBefore(voucher.getBeginTime())) {
        return Result.fail("活动未开始!");
    }

    // 3. 判断秒杀是否结束
    if (nowTime.isAfter(voucher.getEndTime())) {
        return Result.fail("活动已结束!");
    }

    // 4. 判断库存
    if (voucher.getStock() < 1) {
        return Result.fail("已买完!");
    }

    // 5. 减库存
    boolean success = seckillVoucherService.update()
            .setSql("stock = stock - 1")
            .eq("voucher_id", voucherId).update();

    if (!success) {
        return Result.fail("库存不足");
    }

    // 6. 创建订单
    VoucherOrder voucherOrder = new VoucherOrder();

    // 6.1 订单id
    long orderId = redisIdWorker.nextId("order");
    voucherOrder.setId(orderId);
    // 6.2 用户id
    Long userId = UserHolder.getUser().getId();
    voucherOrder.setUserId(userId);
    // 6.3代金券id
    voucherOrder.setVoucherId(voucherId);
    save(voucherOrder);

    // 7. 返回订单
    return Result.ok(orderId);
}

存在问题

上述代码是写好了,运行起来看起来页没有什么问题,但是在多线程,高并发的场景下就会出现大问题,100%会发生超卖的情况!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/11385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【FPGA】FPGA实现SPI协议读写FLASH(一)----- M25P16操作概述

文章目录一、FLASH介绍&#xff08;M25P16&#xff09;1、M25P16概述2、SPI模式3、存储结构4、指令集5、时间参数二、M25P16工作原理三、M25P16指令操作1、页编程 (PP)2、扇区擦除和整块擦除 (SE and BE)3、写使能 (WREN)4、读ID&#xff08;RDID&#xff09;5、读状态寄存器&a…

使用c#将aj-report桌面化:1.winform嵌入浏览器

说到底,aj-report是个工具,我想大多数人还是想快速使用它来创建一个可以展示的工具。通过之前的章节,你应该可以制作自己的报表页面了,下面我们来看看怎么把aj-report包装成一个桌面能够运行的软件。 当然作为扩展开发,受开源协议限制,我们不能大规模修改aj-report的源代…

【毕业设计】深度学习图像修复算法研究与实现 - python

文章目录1 前言2 什么是图像内容填充修复3 原理分析3.1 第一步&#xff1a;将图像理解为一个概率分布的样本3.2 补全图像3.3 快速生成假图像3.4 生成对抗网络(Generative Adversarial Net, GAN) 的架构3.5 使用G(z)生成伪图像4 在Tensorflow上构建DCGANs5 最后1 前言 &#x1…

PC_OS中断/中断屏蔽字

文章目录程序中断&#x1f383;中断概念中断功能(作用)中断请求中断源中断分类外中断非屏蔽中断和可屏蔽中断陷入(内中断)硬件中断和软件中断关系整理&#x1f388;中断判优&#x1f388;中断优先级CPU响应中断的条件外中断实现思路&#x1f388;中断隐指令 及其工作①关中断②…

下一个倒下的是不是Genesis

今日&#xff0c;一个关于“Genesis今晚破产”的传言在各个社交平台传播&#xff0c;包括行业的KOL也在讨论这个事情&#xff0c;认为Genesis或存在偿付能力问题&#xff0c;该公司将于美国东部时间11月17日8&#xff1a;00am与债权人通话以解释情况。若消息属实&#xff0c;Ge…

【AGC】flutter之agconnect_crash在ios上崩溃

问题背景 flutter agconnect_crash-1.2.0300 运行在ios平台上&#xff0c;出现了如下这个崩溃 NSInvalidArgumentException: *** [NSJSONSerialization dataWithJSONObject:options:error:]: value parameter is nil 0 CoreFoundation 0x00000001830d005c 0x183037000 62678…

视频讲解vue2基础之渲染v-if/v-show/v-for/v-html

大家好&#xff0c;我是你们的老朋友lqj_本人&#xff0c;最近一周没有更新文章了&#xff0c;是因为最近学校有一些活动比赛&#xff0c;也有一部分原因就是我在录制一些关于前端方面的视频&#xff0c;涉及到的领域主要一前端&#xff0c;比如&#xff1a;H5开发&#xff0c;…

项目经理如何搞懂难缠的客户【静说】

作为乙方的项目经理&#xff0c;是否经常遇见难缠的客户&#xff0c;现环境下&#xff0c;大部分都是甲方强势&#xff0c;乙方弱势&#xff0c;双方处于一种不对等的基础上&#xff0c;项目经理如何生产&#xff0c;成功交付项目呢&#xff1f; 流程机制上如何应对&#xff1…

计算机毕业设计jsp教师课堂教学评价系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 教师课堂教学评价系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql&#xff0c…

智慧公交解决方案-最新全套文件

智慧公交解决方案-最新全套文件一、建设背景二、思路架构三、建设方案3大能力&#xff1a;1、数据驱动的智慧公交全息感知能力2、精细化精准化的公交健康诊断能力3、高品质的公交运营组织能力6大系统&#xff1a;1、公交线网健康诊断系统2、职能部门指挥决策支持系统3、公共出行…

Spring读取.xml和通过Java类配置对比

Spring读取配置文件获取容器,通过容器获得javaBean演示 1.创建一个空项目 配置项目JDK 新建module 选择Maven项目 注意路径 pom.xml文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"…

C实现扫雷小游戏(简易版)

你知道&#xff0c;有些鸟儿是注定不会被关在牢笼里的&#xff0c;它们的每一片羽毛都闪耀着自由的光辉。——《肖申克的救赎》 目录 1、设计框架 2、设计流程 2.1菜单 2.2初始化雷阵 2.3生成雷 2.4玩家输入坐标 2.5显示有多少个雷 3、所有程序的源码 3.1game.h 3.2…

Slimming剪枝方法

本文参考&#xff1a;5-剪枝后模型参数赋值_哔哩哔哩_bilibiliz https://github.com/foolwood/pytorch-slimming 一、模型剪枝理论说明 论文&#xff1a;Learning Efficient Convolutional Networks through Network Slimming &#xff08;1&#xff09;卷积后得到多个特征图…

通过逻辑回归和感知器算法对乳腺癌数据集breastCancer和鸢尾花数据集iris进行线性分类

逻辑回归和感知器算法进行线性分类 代码使用了LogisticRegression和Perceptron两种分类方法 # 使用LogisticRegreeion分类器学习和测试 lr LogisticRegression() lr.fit(X_train_scaler, y_train) y_pred_lr lr.predict(X_test_scaler)#定义感知机 perceptron Perceptron(…

N3-PEG-ALD,Azide-PEG-Aldehyde,醛基-聚乙二醇-叠氮

1、名称 英文&#xff1a;N3-PEG-ALD&#xff0c;Azide-PEG-Aldehyde 中文&#xff1a;叠氮-聚乙二醇-醛基 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a;Aldehyde / Acetal PEG Azide PEG 4、分子量&#xff1a;可定制&#xff0c;5000 N3-PEG-ALD、10000 叠氮-PEG…

用html做一个漂亮的网站【茶文化12页】期末网页制作 HTML+CSS网页设计实例 企业文化网站制作

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

【仿牛客网笔记】项目进阶,构建安全高效的企业服务——Spring Security

https://spring.io/projects/spring-security 认证判断用户有没有登录。 授权 是访问有没有访问的权限 Spring MVC 的核心组件是DispatcherServlet&#xff0c;所有的组件都是交给DispatcherServlet处理&#xff0c;然后将请求分发给控制器&#xff0c;具体由某个控制器控制请求…

如何在微信上制作小程序?【制作小程序的方式】

很多人想知道如何在微信上制作小程序&#xff0c;毕竟小程序现在已经成为了我们每天都会使用到的轻应用。在微信上制作小程序之前&#xff0c;要先了解自己想要做出什么的小程序&#xff0c;才能下手开展小程序制作。那么下面就介绍三种如何在微信上制作小程序的方式。 1、懂编…

彻底搞懂SwaggerKnife4j使用方法

&#xff08;一&#xff09;、准备 1、SpringBoot项目 说明&#xff1a;前后端分离前提下&#xff0c;前端是一个独立项目&#xff0c;因此该SpringBoot无需添加Thymeleaf依赖&#xff01; 2、vo类 Employee类&#xff1a; package com.soft.vo;import java.util.Date;publ…

Hadoop与Spark的使用,HBase分布式数据库安装及操作实验

docker pull harisekhon/hbase:1.3 docker run -d --name hbase001 -p 16010:16010 harisekhon/hbase:1.3 进入环境 docker exec -it hbase001 bash hbase shell 按照一个特定的值来查找 hbase(main):003:0> scan students,{FILTER>"ValueFilter(,binary:20202…