redis秒杀
redis优惠券秒杀
为什么订单表订单ID不采用自增长?
- id规律性太明显,容易被用户猜测到(比如第一天下订单id10,第二天下订单id100,在昨天的1天内只卖出90商品)
- 受单表数据量限制(订单数据量大,日积月累单张表就存不下去这么多数据记录)如果单张表存不了,我们使用多张表的话,表独立的,自增长的话id会出现重复,到时用户买到的商品有问题售后就麻烦了
这时我们就要使用全局ID生成器,是一种在分布式系统下生成全局唯一ID的工具,满足以下特点:唯一性、高可用、高性能、递增性、安全性
为了增加id的安全性,我们可以直接不使用redis自增的数值,而是拼接一些其他的信息
定义一个类RedisIdWorker类并且配置@Component由spring管理的bean
package com.hmdp.utils;import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;@Component
public class RedisIdWorker {@Resourceprivate StringRedisTemplate stringRedisTemplate;private static final long BEGIN_TIMESTAMP=1672531200;private static final int COUNT_BIT=32;public long nextId(String keyPrefix){// 1.生成时间戳LocalDateTime now = LocalDateTime.now();long nowSecond = now.toEpochSecond(ZoneOffset.UTC);long timestamp=nowSecond-BEGIN_TIMESTAMP;//当前时间戳// 2.生成序列号// 获取当前日期String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));// 自增长// 需要加上日期 因为自增上限是2^64,日积月累的销量越来越多,出32位容易,未来就存不下去,不能使用同一个keyLong count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);// 3.拼接并返回// 我们以为时间戳在最低位 要让他处于高位必须移位 移多少位得看给序列化号足够的位置(这里是32位)return timestamp<<COUNT_BIT|count;}/*public static void main(String[] args) {LocalDateTime time = LocalDateTime.of(2023, 1, 1, 0, 0, 0);long second = time.toEpochSecond(ZoneOffset.UTC);System.out.println(second);//1672531200}*/
}
需要加上日期,不然整个业务订单一直采用同一个key自增长,时隔几年,订单越来越多,redis单个key的值自增上限是2的64次方(好处就是每天业务订单量都是不同的key,一天的订单量不会超过2^32)
package com.hmdp;import com.hmdp.entity.Shop;
import com.hmdp.service.impl.ShopServiceImpl;
import com.hmdp.utils.CacheClient;
import com.hmdp.utils.RedisIdWorker;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;@SpringBootTest
class HmDianPingApplicationTests {@Resourceprivate CacheClient cacheClient;@Resourceprivate ShopServiceImpl shopService;@Resourceprivate RedisIdWorker redisIdWorker;private ExecutorService es = Executors.newFixedThreadPool(500);@Testpublic 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 begin = System.currentTimeMillis();for (int i = 0; i < 300; i++) {es.submit(task);//线程池异步所以使用CountDownLatch}latch.await();//等待所有countDown结束long end = System.currentTimeMillis();System.out.println("time=" + (end - begin));}@Testvoid testSaveShop() throws InterruptedException {//shopService.saveShop2Redis(1L,10L);Shop shop = shopService.getById(1L);cacheClient.setWithLogicalExpire(CACHE_SHOP_KEY + 1L, shop, 10L, TimeUnit.SECONDS);}
}
添加秒杀劵(这里我们没有编写后台管理系统,为了方便起见,使用postman测试)
{"shopId": 1,"title": "100元代金券","subTitle": "周一到周五均可使用","rules": "全场通用\\n无需预约\\n可无限叠加\\不兑现、不找零\\n仅限堂食","payValue": 8000,"actualValue": 10000,"type": 1,"stock": 100,"beginTime":"2023-02-23T19:00:00","endTime":"2023-02-23T20:00:00"
}
注意,我们点击发送请求前需要添加请求头信息 控制台打印其值即token,否则会报401错
添加成功
优惠券秒杀下单功能
package com.hmdp.service.impl;import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.time.LocalDateTime;/*** <p>* 服务实现类* </p>** @author 虎哥* @since 2021-12-22*/
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Override@Transactionalpublic Result seckillVoucher(Long voucherId) {// 1.查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2.判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {// 尚未开始return Result.fail("秒杀尚未开始!");}// 3.判断秒杀是否结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("秒杀已经结束!");}// 4.判断库存是否充足if (voucher.getStock()<1) {// 库存不足return Result.fail("库存不足!");}// 5.扣减库存// eq代表where条件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. 订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 6.2. 用户idLong userId = UserHolder.getUser().getId();voucherOrder.setUserId(userId);// 6.3. 代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);// 7.返回订单Idreturn Result.ok(orderId);}
}
超卖
版本号法+CAS法(compare and set)
// 5.扣减库存// eq代表where条件boolean success=seckillVoucherService.update().setSql("stock=stock-1")// set stock=stock-1.eq("voucher_id",voucherId).eq("stock",voucher.getStock())// where id=? and stock=?.update();
乐观锁成功率太低(因为只要库存不一致就失败)优化:就是库存大于0即可
乐观锁的方案是在更新数据时候去使用的
// 5.扣减库存// eq代表where条件boolean success=seckillVoucherService.update().setSql("stock=stock-1")// set stock=stock-1.eq("voucher_id",voucherId).gt("stock",0)// where id=? and stock>0.update();
一人一单(知识点:动态代理、事务、aop、悲观锁、锁的范围)
这里是新增数据,无法判断是否有无修改过,因为它不存在,判断是否存在,只能用悲观锁方案解决(从查询订单到判断订单到新增订单这一段逻辑加上悲观锁)
虽然userId值一样,但是每次调用toString方法都是新的对象
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency>
暴露代理对象
package com.hmdp;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan("com.hmdp.mapper")
@SpringBootApplication
public class HmDianPingApplication {public static void main(String[] args) {SpringApplication.run(HmDianPingApplication.class, args);}}
之前在方法内部添加锁可能会发生情况:开启事务执行,获取锁查询完减库存提交订单释放锁才会提交事务,函数执行完之后由spring去提交事务,锁释放意味着其他线程也可以进来了,而此时事务尚未提交,其他线程趁此时进来,去查询订单的话,刚刚新增的订单很有可能还未写进数据库,因为还未提交,查询依然不存在,产生并发问题, 可知我们刚才锁定的范围缩小了,解决方案:把整个函数锁起来,先事务提交再释放锁,那我们就要把锁放在函数外面,这时就出现事务问题消失 因为此时调用方法的直接是当前目标对象IVoucherOrderService对象,没有事务功能,事务要想生效,其实是我们spring对当前这个类进行动态代理, 拿到当前类的代理对象,用它来作事务处理
package com.hmdp.service.impl;import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.time.LocalDateTime;/*** <p>* 服务实现类* </p>** @author 虎哥* @since 2021-12-22*/
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Overridepublic Result seckillVoucher(Long voucherId) {// 1.查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2.判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {// 尚未开始return Result.fail("秒杀尚未开始!");}// 3.判断秒杀是否结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("秒杀已经结束!");}// 4.判断库存是否充足if (voucher.getStock()<1) {// 库存不足return Result.fail("库存不足!");}// 5.一人一单Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()) {// 获取代理对象(事务)IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}}// 方法加上同步锁肯定是线程安全的 this指向IVoucherOrderService// 不建议加上方法上 因为代表任何用户来了都要加上锁 串行执行 性能低// 我们只需要同一用户进来才需要加锁@Transactionalpublic Result createVoucherOrder(Long voucherId){// 5.一人一单Long userId = UserHolder.getUser().getId();// 当用户id值一样时,锁就一样// synchronized (userId.toString().intern()) {// 5.1.查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();// 5.2.判断是否存在if (count > 0) {//用户已经购买过return Result.fail("用户已经购买过一次!");}// 6.扣减库存// eq代表where条件boolean success = seckillVoucherService.update().setSql("stock=stock-1")// set stock=stock-1.eq("voucher_id", voucherId).gt("stock", 0)// where id=? and stock>0.update();if (!success) {// 扣减失败return Result.fail("库存不足!");}// 7.创建订单VoucherOrder voucherOrder = new VoucherOrder();// 7.1. 订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 7.2. 用户idvoucherOrder.setUserId(userId);// 7.3. 代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);// 8.返回订单Idreturn Result.ok(orderId);//}}
}
上述处理方案虽然解决了一人一单的并发安全问题(通过加锁可以解决单机情况下的问题),但是在集群模式下就不行了
通过加锁可以解决在单机情况下的一人一单安全问题,但是在集群模式下就不行了(每个jvm都有自己的锁监视器,集群模式下各个服务器的锁不共享)。
因此,我们的解决方案就是实现一个共享的锁监视器,即:
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。
相关文章:

redis秒杀
redis优惠券秒杀 为什么订单表订单ID不采用自增长? id规律性太明显,容易被用户猜测到(比如第一天下订单id10,第二天下订单id100,在昨天的1天内只卖出90商品)受单表数据量限制(订单数据量大&am…...

JS学习第3天——Web APIs之DOM(什么是DOM,相关API【创建、增删改查、属性操作、事件操作API】)
目录一、Web APIs介绍1、API2、Web API二、DOM1、DOM树2、获取元素3、事件基础4、操作元素属性5、节点(node)操作三、DOM操作总结(创建、增删改查、属性操作、事件操作API)1、创建2、增3、删4、改5、查6、属性操作7、事件操作四、…...

【MySQL】增删改操作(基础篇)
目录 1、新增操作(Create) 1.1 单行数据 全列插入 1.2 多行数据 全列插入 1.3 单行数据 指定列插入 2、修改操作(Update) 3、删除操作(Delete) 1、新增操作(Create) 如何给一张表新增数据呢? 新增(Create),在我们数据库中,用 ins…...

STM32—DMA
什么是DMA? DMA(Direct Memory Access,直接存储器访问) 提供在外设与内存、存储器和存储器、外设与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存的工作来…...

C语言刷题(3)——“C”
各位CSDN的uu们你们好呀,今天小雅兰的内容还是做几道题噢,好好复习一下之前的知识点,现在,就让我们开始复习吧 牛客网在线编程_编程学习|练习题_数据结构|系统设计题库 倒置字符串_牛客题霸_牛客网 BC40 竞选社长 BC41 你是天才…...

搭建Vue工程
搭建Vue工程 localhost 127.0.0.1 域名 IP 192.168.0.28 联网IP 最后都会渲染到一个页面里面,有多少个页面就有多少个页面模板。 vue里面改webpack配置 vue.config.js 配置参考 | Vue CLI /assets /api* 开发的时候用到的请求后台地址 和 项目真实部署上线的时候 请…...

C语言汉诺塔问题【图文详解】
汉诺塔1. 什么是汉诺塔2. 有关汉诺塔的有趣故事3. 利用动画来演示汉诺塔4. 如何用C语言实现汉诺塔1. 什么是汉诺塔 源于印度古老传说的益智玩具 汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造…...

1、RocketMQ概述
文章目录1 MQ概述1.1 MQ简介1.2 MQ用途1.3 常见MQ产品1.4 MQ常见协议2 RocketMQ概述2.1 RocketMQ简介2.2 RocketMQ发展历程尚硅谷RocketMQ教程-讲师:Reythor雷(老雷) 我们缺乏的不是知识,而是学而不厌的态度 1 MQ概述 1.1 MQ简介…...
【POJ 3352】Road Construction 题解(Tarjan算法求边双连通分量缩点)
描述 现在几乎是夏天,这意味着几乎是夏天的施工时间!今年,负责偏远岛热带岛屿天堂道路的好心人希望修复和升级岛上各个旅游景点之间的各种道路。 道路本身也很有趣。由于岛上的奇怪风俗,道路的安排使得它们不会在交叉路口相遇&…...

Python—单分支结构
(1)if分支语句 Python中if语句的语法结构: if <条件表达式>: 满足条件运行的代码1 满足条件运行的代码2 代码示例: age 12 if age > 18:print(去上网)if 1 1 2 and :print(我满足条件了)if 1 …...

rabbitmq添加用户,虚拟机步,设置rabbitmq配置文件
第一步,登录后台控制页面 http://ip:15672第二步,添加用户和权限 重点:选择Admin和Users 第三步,添加虚拟机 点击侧边的Virtual Hosts 第四步将虚拟机和用户搭配 注意新建好后,在虚拟机列表中,点击虚拟机…...
Codeforces Round#853 div2 A-C
Codeforces Round#853 div2 A-C 等了很久终于迎来了一场cf比赛,白天出去玩了一圈,晚上回来打比赛,这次只出了A,B题。C题思路很巧妙,赛时没做出来,看了大佬学习到了,还是很不错。 A.Serval and Mocha’s A…...

软考之操作系统知识
目录 1.进程管理-进程的概念 2.进程的三态图和五态图 3.进程的同步与互斥 4.PV操作应用 5.死锁问题 6.银行家算法 7.存储管理 8.段式存储组织 9.段页式存储组织 10.页面置换算法 11.磁盘管理 12.作业管理 13.索引文件结构 14.树型目录结构 15.空闲存储空间管理 …...
【线性代数/计算复杂性理论】积和式的指数时间算法:Ryser算法
文章目录一、积和式的定义二、Ryser算法三、代码实现一、积和式的定义 积和式(permanent)是一种和行列式长得很像的矩阵函数。在介绍积和式之前,我们先看看行列式(determinant)的定义。 首先需要引入“排列”&#x…...

代码随想录 NO52 | 动态规划_leetcode 647. 回文子串 516.最长回文子序列
动态规划_leetcode 647. 回文子串 516.最长回文子序列今天是动态规划最后一天的题了,整个过程已经接近尾声了! 647. 回文子串 确定dp数组(dp table)以及下标的含义 本题如果我们定义,dp[i] 为 下标i结尾的字符串有 dp…...

【数据挖掘】1、综述:背景、数据的特征、数据挖掘的六大应用方向、有趣的案例
目录一、背景1.1 学习资料1.2 数据的特征1.3 数据挖掘的应用案例1.4 获取数据集1.5 数据挖掘的定义二、分类三、聚类四、关联分析五、回归六、可视化七、数据预处理八、有趣的案例8.1 隐私保护8.2 云计算的弹性资源8.3 并行计算九、总结一、背景 1.1 学习资料 推荐书籍如下&a…...

【架构师】零基础到精通——康威定律
博客昵称:架构师Cool 最喜欢的座右铭:一以贯之的努力,不得懈怠的人生。 作者简介:一名Coder,软件设计师/鸿蒙高级工程师认证,在备战高级架构师/系统分析师,欢迎关注小弟! 博主小留言…...

Could not extract response: no suitable HttpMessageConverter
版本:spring-cloud-openfeign-core-2.1.1.RELEASE.jar,spring-webmvc-5.1.14.RELEASE.jar,jetty-server-9.4.41.v20210516.jar,tomcat-embed-core-9.0.48.jar 问题背景 生产服务请求下游服务时偶发抛出下面的异常,下…...

文献计量三大定律之一---洛特卡定律及普赖斯定律
科学生产率是洛特卡定律的基础,科学生产率”(Scientific Productivity))是指科学家(科研人员)在科学上所表现出的能力和工作效率,通常用其生产的科学文献的数量来衡量。 1926年,洛特卡在一篇论文中提出了科…...

2023年软考高级网络规划设计师
网络规划设计师是软考高级考试科目之一,也是比较难的科目,据官方数据统计网规每年的通过率很低,而且每年只有下半年11月份考一次,如果是直接裸考,估计很悬哦~ 但是你参加考试获得证书的过程就是一个学习网络规划系统知…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...