Redis - 优惠卷秒杀

场景分析

为了避免对数据库造成压力,我们在新增优惠卷的时候,可以将优惠卷的信息储存在Redis中,这样用户抢购的时候访问优惠卷信息,通过Redis读取信息。
在这里插入图片描述
抢购流程
在这里插入图片描述

业务分析

既然在新增优惠卷的时候,我们只需要id和库存信息,那么这个时候我们需要说选择Redis中Stringt数据类型,以id作为键,此时我们在进行抢劵的时候,通过id可以直接查询到对应的库存信息,进行判断。
此时我们会面临一下场景:

  1. 出现超卖问题
  2. 不能保证一人一单

超卖问题

锁结构选型:

在这里插入图片描述
读多写少的场景:
选择乐观锁。因为读操作不会加锁,可以提高系统的并发性能。写操作通过版本号或条件更新来避免冲突。
写操作频繁且并发量高的场景
选择悲观锁。悲观锁通过加锁机制可以有效避免并发冲突,保证数据的一致性和正确性。
性能和一致性权衡:
在性能和一致性之间需要权衡。如果一致性要求非常高,且系统能够接受较低的并发性能,选择悲观锁。如果系统需要高性能,并且能够接受偶尔的冲突重试,选择乐观锁。
由于这个优惠卷抢票属于,读多写少这种场景,那么我们选型的话选择乐观锁会比较合适。

数据库乐观锁解决超卖问题
超卖问题分析

在这里插入图片描述
假设我们现在库存store = 1 ,那么此时线程一 判断之后 store > 0 那么这个时候线程一开始进行库存缩减,但是此时库存还没开始在数据库中扣减,此时线程二开始查询,发现 store > 0,那么线程二也认定自己抢票成功,开始扣减库存,那么此时就会出现store 减少了两次,那么库存就变成了-1,假设在线程二查询之后,线程一库存扣减之前,线程三又进行了判断,那么此时库存就又变成了-2。这是超卖问题的一个场景。
乐观锁,版本号法:
在这里插入图片描述
版本号法:也就是说引入一个新的字段用来检测当前我查询到的version,和我修改时的version是否是同一个,这样的话保证不会出现超卖问题,但是同样,会出现大量失败情况导致错误率极高。不推荐使用
CAS方案:
比较我查询到的库存值跟我更新后的库存值判断。但是这种情况也会出现上述的那种限制,所以我们只要判断扣减前的库存是否大于0即可,那么这种情况效率高,且不会出现大幅度失败。这个主要还是依靠mysql自身的行级锁机制进行的。属于悲观锁的一种范畴。
MySQL 中的行级锁可以确保同一行的数据在同一时间只能被一个事务修改,从而避免了并发更新导致的数据不一致问题。在代码中,当多个线程同时尝试更新同一行数据时,MySQL 会自动对该行数据加锁,以确保只有一个线程能够成功执行更新操作,其他线程会被阻塞直到锁释放。
这种方案解决较上一种方案会好一点,但依旧不是好的解决方案,因为对于这种场景下,阻塞依旧是不可避免的。

 boolean voucherId1 = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock", 0).update();

一人一单问题

我们只需要用户下单之后,把用户订单信息,存入Redis中,只需要到时候读取这个信息判断有多少次下单即可。

// 这个位置采用优惠卷id 和 用户 id作为键,储存对应的信息,避免抢其他票时出现误判
String keyUserOrder = "user:order:" + voucherId + user.getId();
Integer count = 0;
try{count  = Integer.valueOf(stringRedisTemplate.opsForValue().get(keyUserOrder));if (count >= 1) {return Result.fail("用户已购买");}
}catch (Exception e){log.error("第一次抢票");
}
// 这个是抢票成功时,将订单信息保存。值自增用来判断是否是第一次购买
Long orderCount = stringRedisTemplate.opsForValue().increment(keyUserOrder, 1);
新的实现思路

既然是抢劵,那我确实无法避免这个内容。我可以说实现一个优惠卷池,将这个内容存入Redis中,抢到的话就生成订单,这样的话也没有超卖问题。只不过还得解决一人一单。效率较上面这种会更好一点。

实现步骤
  • 初始化优惠券池:将每张优惠券的信息预先存入Redis。
  • 抢券逻辑:用户请求时,从Redis中获取一张优惠券。
  • 生成订单:抢到优惠券后,生成订单并将订单信息和优惠券信息关联起来。
  • 一人一单检查:确保同一用户不能重复抢券。
代码示例

以下是一个简单的代码实现示例:
初始化优惠券池:

// 初始化优惠券池,将100张优惠券信息存入Redis
String voucherPoolKey = "voucher:pool";
for (int i = 1; i <= 100; i++) {// 假设优惠券信息是一个简单的字符串,这里可以是更复杂的对象stringRedisTemplate.opsForList().leftPush(voucherPoolKey, "voucher_" + i);
}

抢券逻辑

public Result grabVoucher(Long voucherId, Long userId) {// Redis中的键String voucherPoolKey = "voucher:pool";String userOrderKey = "user:order:" + voucherId + ":" + userId;// 检查用户是否已经抢过if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(userOrderKey))) {return Result.fail("用户已购买");}// 从Redis中弹出一张优惠券String voucher = stringRedisTemplate.opsForList().rightPop(voucherPoolKey);if (voucher == null) {return Result.fail("优惠券已抢完");}// 抢券成功,生成订单// 这里假设订单生成成功,实际应用中需要加入订单生成逻辑String orderInfo = "order_for_" + voucher + "_by_user_" + userId;// 记录用户抢券信息stringRedisTemplate.opsForValue().set(userOrderKey, orderInfo);return Result.ok("抢券成功", orderInfo);
}

订单生成
实际的订单生成过程可能涉及复杂的业务逻辑和数据库操作,这里简化为成功后记录用户抢券信息。

一人一单的实现
在用户抢券前,检查用户是否已经抢过,通过Redis键的存在性判断。

// 检查用户是否已经抢过
if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(userOrderKey))) {return Result.fail("用户已购买");
}
优点
  • 高效处理并发:Redis的高吞吐量和低延迟使得这种方案在高并发场景下表现出色。
  • 避免超卖:优惠券从Redis中弹出后即减少,确保不会超卖。
  • 简单实现一人一单:通过Redis键值对存储和检查用户抢券信息,轻松实现一人一单的限制。
  • 简化订单生成:在抢券成功后,立即生成订单并将信息存储在Redis中,减少了数据库操作的复杂性。

或者我感觉这个并发场景比较大的情况下,使用RabbitMQ削峰填谷,做限流也可以。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯-班级活动

题目描述 小明的老师准备组织一次班级活动。班上一共有 ( n ) 名&#xff08;( n ) 为偶数&#xff09;同学&#xff0c;老师想把所有的同学进行分组&#xff0c;每两名同学一组。为了公平&#xff0c;老师给每名同学随机分配了一个 ( n ) 以内的正整数作为 id&#xff0c;第 …

基于Kafka的日志采集

目录 前言 架构图 资源列表 基础环境 关闭防护墙 关闭内核安全机制 修改主机名 添加hosts映射 一、部署elasticsearch 修改limit限制 部署elasticsearch 修改配置文件 启动 二、部署filebeat 部署filebeat 添加配置文件 启动 三、部署kibana 部署kibana 修…

Google Find My Device:科技守护,安心无忧

在数字化的时代&#xff0c;我们的生活与各种智能设备紧密相连。而 Google Find My Device 便是一款为我们提供安心保障的实用工具。 一、Find My Decice Netword的定义 谷歌的Find My Device Netword旨在通过利用Android设备的众包网络的力量&#xff0c;帮助用户安全的定位所…

深入编程逻辑:从分支到循环的奥秘

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、编程逻辑的基石&#xff1a;分支与循环 分支逻辑详解 代码案例&#xff1a;判断整数是…

从 0 开始本地部署大语言模型

1、准备 ● Ollama&#xff1a;ollama.com ● Docker&#xff1a;https://docs.openwebui.com/ 2、下载 Ollama 进入 Ollama 官网&#xff0c;点击 Download 。 下载完成后&#xff0c;双击安装&#xff0c;什么都不需要勾选&#xff0c;直接下一步即可。安装完成&#xf…

【Qt】Qt组件设置背景图片

1. 方法1&#xff08;paintEvent方式&#xff09; 使用paintEvent()实现 1. .h文件中添加虚函数 protected:void paintEvent(QPaintEvent *event) override;添加虚函数方法&#xff1a; 选中父类&#xff0c;点击鼠标右键点击重构点击 Insert Virtual Funtion of Base Class…

【CCIE | 网络模拟器】部署 EVE-NG

目录 1. 环境准备2. 下载 EVE-NG 镜像3. 安装 EVE-NG 虚拟机3.1 创建 eve-ng 虚拟机3.2 选择存储3.3 定义虚拟机计算资源&#xff08;1&#xff09;开启CPU虚拟化功能&#xff08;2&#xff09;精简置备磁盘 3.4 检查虚拟机设置 4. 安装系统4.1 选择系统语言4.2 选择系统键盘类…

专业渗透测试 Phpsploit-Framework(PSF)框架软件小白入门教程(十二)

本系列课程&#xff0c;将重点讲解Phpsploit-Framework框架软件的基础使用&#xff01; 本文章仅提供学习&#xff0c;切勿将其用于不法手段&#xff01; 接上一篇文章内容&#xff0c;讲述如何进行Phpsploit-Framework软件的基础使用和二次开发。 我们&#xff0c;继续讲一…

爽!AI手绘变插画,接单赚爆了!

我最近发现一款名叫Hyper-SD15-Scribble的AI项目&#xff0c;可以实现一键手绘变插画的功能&#xff0c;而且它搭载了字节出品的超快速生成图片的AI大模型Hyper-SD15&#xff0c;可以实现几乎实时生成图片&#xff0c;有了它&#xff0c;拿去接一些手绘商单分分钟出图&#xff…

数据结构(四)串

2024年5月26日一稿(王道P127) 定义和实现

Ant Design Vue中 a-table 嵌套子表格

需求&#xff1a;在父表格中嵌套子表格&#xff0c;当点击展开某一行时&#xff0c;有展开的关闭当前展开行。使用a-table中的expandedRowKeys 属性和expand 方法。链接&#xff1a;Ant Design Vue 一、属性说明&#xff1a; expandedRowKeys&#xff1a;这个主要是控制展开某行…

modbus开源库libmodbus的C语言使用记录(实现简单的modbus主机/丛机程序,解决libmodbus库安装出现的问题)

libmodbus简介 libmodbus 是一个开源的、跨平台的C库,用于实现Modbus通讯协议。它支持Modbus RTU(RS-232/485)和Modbus TCP协议,可以使开发者方便地在项目中集成Modbus通讯功能。libmodbus的设计目标是简单、灵活和高效,适用于各种大小的嵌入式和桌面应用。 编译运行测试…

树莓派学习笔记——树莓派的三种GPIO编码方式

1、板载编码&#xff08;Board pin numbering&#xff09;: 板载编码是树莓派上的一种GPIO引脚编号方式&#xff0c;它指的是按照引脚在树莓派主板上的物理位置来编号。这种方式对于初学者来说可能比较直观&#xff0c;因为它允许你直接根据引脚在板上的位置来编程。 2、BCM编…

C++第十九弹---string模拟实现(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、修改操作 2、迭代器操作 3、字符串操作 4、非成员函数重载操作 总结 1、修改操作 1、string& operator (const char* s); //尾部插入…

Vue移动端登录页面

使用的是vant组件&#xff0c;引用和使用组件请去官网学习&#xff0c;链接↓vant组件官网 <div class"center"><!-- 背景图片 --><div class"background"><imgsrc"/assets/background.jpg"width"100%"heigh…

2024年5月大语言模型论文推荐:模型优化、缩放到推理、基准测试和增强性能

前一篇文章总结了关于计算机视觉方面的论文&#xff0c;这篇文章将要总结了2024年5月发表的一些最重要的大语言模型的论文。这些论文涵盖了塑造下一代语言模型的各种主题&#xff0c;从模型优化和缩放到推理、基准测试和增强性能。 大型语言模型(llm)发展迅速&#xff0c;跟上…

MySQL 8.4.0 LTS 变更解析:I_S 表、权限、关键字和客户端

↑ 关注“少安事务所”公众号&#xff0c;欢迎⭐收藏&#xff0c;不错过精彩内容~ MySQL 8.4.0 LTS 已经发布 &#xff0c;作为发版模型变更后的第一个长期支持版本&#xff0c;注定要承担未来生产环境的重任&#xff0c;那么这个版本都有哪些新特性、变更&#xff0c;接下来少…

Spark-RDD-常用算子(方法)详解

Spark概述 Spark-RDD概述 Spark RDD 提供了丰富的方法来对数据进行转换和操作。 对 RDD&#xff08;Resilient Distributed Dataset&#xff09;的操作可以分为两大类&#xff1a;转换算子&#xff08;Transformations&#xff09;和行动算子&#xff08;Actions&#xff09;…

在aspNetCore中 使用System.Text.Json的定制功能, 将定制化的json返回给前端

C# 默认大写, 而大部分的前端默认小写, 这时候可以如此配置: builder.Services.AddControllers().AddJsonOptions((opt) > {opt.JsonSerializerOptions.PropertyNamingPolicy System.Text.Json.JsonNamingPolicy.CamelCase;opt.JsonSerializerOptions.WriteIndented true…

lspci 显示当前设备的PCI总线信息

lspci 显示当前设备的PCI总线信息 lspci 显示当前设备的PCI总线信息显示当前主机的所有PCI总线信息&#xff1a;以数字方式显示PCI厂商和设备代码同时显示数字方式还有设备代码信息以树状结构显示PCI设备的层次关系&#xff1a;更多信息 lspci 显示当前设备的PCI总线信息 lspc…