当前位置: 首页 > news >正文

redis解决常见的秒杀问题


title: redis解决常见的秒杀问题
date: 2025-03-07 14:24:13
tags: redis
categories: redis的应用

秒杀问题

每个店铺都可以发布优惠券,保存到 tb_voucher 表中;当用户抢购时,生成订单并保存到 tb_voucher_order 表中。

订单表如果使用数据库自增 ID,会存在以下问题:

  • ID 的规律太明显,容易暴露信息。
  • 单表数据量的限制,订单过多时单表很难存储得下。数据量过大后需要拆库拆表,但拆分表了之后,各表从逻辑上是同一张表,所以 id 不能一样, 于是需要保证 ID 的唯一性。

全局唯一ID

全局唯一 ID 的特点

  • 唯一性:Redis 独立于数据库之外,不论有多少个数据库、多少张表,访问 Redis 获取到的 ID 可以保证唯一。
  • 高可用:Redis 高可用(集群等方案)。
  • 高性能:Redis 速度很快。
  • 递增性:例如 String 的 INCR 命令,可以保证递增。
  • 安全性:为了增加 ID 的安全性,在使用 Redis 自增数值的基础上,在拼接一些其他信息。

全局唯一 ID 的组成(存储数值类型占用空间更小,使用 long 存储,8 byte,64 bit)

在这里插入图片描述

  • 符号位:1 bit,永远为 0,代表 ID 是正数。

  • 时间戳:31 bit,以秒为单位,可以使用 69 年。

  • 序列号:32 bit,当前时间戳对应的数量,也就是每秒可以对应 2^32 个不同的 ID。

Redis ID 自增策略:通过设置每天存入一个 Key,方便统计订单数量;ID 构造为 时间戳 + 计数器。

@Component
public class RedisIdWorker {/*** 指定时间戳(2023年1月1日 0:0:00) LocalDateTime.of(2023, 1, 1, 0, 0, 0).toEpochSecond(ZoneOffset.UTC)*/private static final long BEGIN_TIMESTAMP_2023 = 1672531200L;/*** 序列号位数*/private static final int BIT_COUNT = 32;private final StringRedisTemplate stringRedisTemplate;public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public long nextId(String keyPrefix) {// 1. 时间戳long timestamp = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) - BEGIN_TIMESTAMP_2023;// 2. 生成序列号:自增 1,Key 不存在会自动创建一个 Key。(存储到 Redis 中的 Key 为 keyPrefix:date,Value 为自增的数量)Long serialNumber = stringRedisTemplate.opsForValue().increment(keyPrefix + ":" + DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.now()));// 3. 时间戳左移 32 位,序列号与右边的 32 个 0 进行与运算return timestamp << BIT_COUNT | serialNumber;}
}

测试(300个线程生成共3w个id)

@Resource
private RedisIdWorker redisIdWorker;public static final ExecutorService ES = Executors.newFixedThreadPool(500);@Test
void testGloballyUniqueID() throws Exception {// 程序是异步的,分线程全部走完之后主线程再走,使用 CountDownLatch;否则异步程序没有执行完时主线程就已经执行完了CountDownLatch latch = new CountDownLatch(300);Runnable task = () -> {for (int i = 0; i < 100; i++) {long globallyUniqueID = redisIdWorker.nextId("sun");System.out.println("globallyUniqueID = " + globallyUniqueID);}latch.countDown();};long begin = System.currentTimeMillis();for (int i = 0; i < 300; i++) {ES.submit(task);}latch.await();long end = System.currentTimeMillis();System.out.println("Execution Time: " + (end - begin));
}

添加优惠卷

格式类似这种逻辑太简单了略

{"shopId":1,"title":"100元代金券","subTitle":"周一至周五均可使用","rules":"全场通用\n无需预约\n可无限叠加\n不兑现、不找零\n仅限堂食","payValue":8000,"actualValue":10000,"type":1,"stock":100,"beginTime":"2022-11-13T10:09:17","endTime":"2022-11-13T22:10:17"
}

秒杀下单功能

在这里插入图片描述

@Override
@Transactional
public Result seckillVoucher(Long voucherId) {//1.查询优惠卷SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//2.判断秒杀是否开始,是否结束if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("秒杀尚未开始!");}if(voucher.getEndTime().isBefore(LocalDateTime.now())){return Result.fail("秒杀已结束!");}//3.判断库存是否充足if(voucher.getStock()<=0){return Result.fail("优惠券库存不足!");}//4.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock -1").eq("voucher_id", voucherId).update();//5.创建订单if(!success){return Result.fail("优惠券库存不足!");}//6.返回订单idVoucherOrder 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);//7.订单写入数据库save(voucherOrder);//8.返回订单Idreturn Result.ok(orderId);
}

超卖问题

假设库存为 1,有线程1、2、3,时刻 t1、t2、t3、t4。

  • t1:线程1 查询库存,库存为 1;
  • t2:线程2、线程 3 查询库存,库存为 1;
  • t3:线程1 下单,库存扣减为 0。
  • t4:线程2 和 线程3 下单,库存扣减为 -2。

具体图示:
在这里插入图片描述
在这里插入图片描述

解决超卖问题

悲观锁

太简单了直接加锁保证操作数据是原子操作要串行执行

乐观锁
版本号法:

一般是在数据库表中加上一个 version 字段表示 数据被修改的次数。数据被修改时 version 值加 1。

  1. 线程 A 读取数据,同时读取到 version 值。

  2. 提交更新时,若刚才读到的 version 值未发生变化:则提交更新并且 version 值加 1。

  3. 提交更新时,若刚才读到的 version 值发生了变化:放弃更新,并通过报错、自旋重试等方式进行下一步处理。

在这里插入图片描述

CAS法(简单来说就是直接拿库存当版本号):

CAS 操作需要输入两个数值,一个旧值(操作前的值)和一个新值,操作时先比较下在旧值有没有发生变化,若未发生变化才交换成新值,发生了变化则不交换。

CAS 是原子操作,多线程并发使用 CAS 更新数据时,可以不使用锁。原子操作是最小的不可拆分的操作,操作一旦开始,不能被打断,直到操作完成。也就是多个线程对同一块内存的操作是串行的。

在这里插入图片描述

一人一单问题

在这里插入图片描述

一人一单逻辑:

  1. 发送下单请求,提交优惠券 ID。
  2. 下单前需要判断:秒杀是否开始或结束、库存是否充足
  3. 库存充足:根据优惠券 ID 和用户 ID 查询订单,判断该用户是否购买过该优惠券
  4. 该用户对该优惠券的订单不存在时,扣减库存、创建订单、返回订单 ID。

解决并发安全问题

  1. 单人下单(一个用户),高并发的情况下:该用户的 10 个线程同时执行到 查询该用户 ID 和秒杀券对应的订单数量,10 个线程查询到的值都为 0,即未下单。于是会出现一个用户下 10 单的情况。
  2. **此处仍需加锁,乐观锁适合更新操作,插入操作需要选择悲观锁。**若直接在方法上添加 synchronized 关键字,会让锁的范围(粒度)过大,导致性能较差。因此,采用 一个用户一把锁 的方式。

问题:能否用乐观锁执行?

不能,原因是乐观锁只能操作(修改)单个变量,而创建订单需要操作数据库(难以跟踪状态)

@Override
public CommonResult<Long> seckillVoucher(Long voucherId) {// 判断秒杀是否开始或结束、库存是否充足。SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);ThrowUtils.throwIf(seckillVoucher == null, ErrorCode.NOT_FOUND_ERROR);LocalDateTime now = LocalDateTime.now();ThrowUtils.throwIf(now.isBefore(seckillVoucher.getBeginTime()), ErrorCode.OPERATION_ERROR, "秒杀尚未开始");ThrowUtils.throwIf(now.isAfter(seckillVoucher.getEndTime()), ErrorCode.OPERATION_ERROR, "秒杀已经结束");ThrowUtils.throwIf(seckillVoucher.getStock() < 1, ErrorCode.OPERATION_ERROR, "库存不足");// 下单return this.createVoucherOrder(voucherId);
}/*** 下单(超卖 - CAS、一人一单 - synchronized)*/
@Override
@Transactional
public CommonResult<Long> createVoucherOrder(Long voucherId) {// 1. 判断当前用户是否下过单Long userId = UserHolder.getUser().getId();Integer count = this.lambdaQuery().eq(VoucherOrder::getVoucherId, voucherId).eq(VoucherOrder::getUserId, userId).count();ThrowUtils.throwIf(count > 0, ErrorCode.OPERATION_ERROR, "禁止重复下单");// 2. 扣减库存boolean result = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock", 0).update();ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "下单失败");// 3. 下单VoucherOrder voucherOrder = new VoucherOrder();voucherOrder.setUserId(userId);voucherOrder.setId(redisIdWorker.nextId("seckillVoucherOrder"));voucherOrder.setVoucherId(voucherId);result = this.save(voucherOrder);ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "下单失败");return CommonResult.success(voucherOrder.getId());
}

集群环境下的并发问题

在这里插入图片描述

分布式锁-原理

不去使用jvm内部的锁监视器,我们要在外部开一个锁监视器,让它监视所有的线程

在这里插入图片描述

常见的分布式锁

MySQL:MySQL 本身带有锁机制,但是由于 MySQL 性能一般,所以采用分布式锁的情况下,使用 MySQL 作为分布式锁比较少见。
Redis:Redis 作为分布式锁比较常见,利用 setnx 方法,如果 Key 插入成功,则表示获取到锁,插入失败则表示无法获取到锁。
Zookeeper:Zookeeper 也是企业级开发中比较好的一个实现分布式锁的方案。

MySQLRedisZookeeper
互斥利用 MySQL 本身的互斥锁机制利用 setnx 互斥命令利用节点的唯一性和有序性
高可用
高性能一般一般
安全性断开链接,自动释放锁利用锁超时时间,到期释放临时节点,断开链接自动释放
# 添加锁(NX 互斥、EX 设置 TTL 时间)
SET lock thread1 NX EX 10# 手动释放锁
DEL lock
public interface DistributedLock {/*** 获取锁(只有一个线程能够获取到锁)* @param timeout   锁的超时时间,过期后自动释放* @return          true 代表获取锁成功;false 代表获取锁失败*/boolean tryLock(long timeout);/*** 释放锁*/void unlock();
}public class SimpleDistributedLock4Redis implements DistributedLock {private static final String KEY_PREFIX = "lock:";private final String name;private final StringRedisTemplate stringRedisTemplate;public SimpleDistributedLockBased4Redis(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean tryLock(long timeout) {String threadId = Thread.currentThread().getId().toString();Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeout, TimeUnit.SECONDS);// result 是 Boolean 类型,直接返回存在自动拆箱,为防止空指针不直接返回return Boolean.TRUE.equals(result);}@Overridepublic void unlock() {stringRedisTemplate.delete(KEY_PREFIX + name);}
}
/*** VERSION3.0 - 秒杀下单优惠券(通过分布式锁解决一人一单问题)*/
@Override
public CommonResult<Long> seckillVoucher(Long voucherId) {// 判断秒杀是否开始或结束、库存是否充足。...// 下单SimpleDistributedLock4Redis lock = new SimpleDistributedLock4Redis("order:" + UserHolder.getUser().getId(), stringRedisTemplate);boolean tryLock = lock.tryLock(TTL_TWO);ThrowUtils.throwIf(!tryLock, ErrorCode.OPERATION_ERROR, "禁止重复下单");try {VoucherOrderService voucherOrderService = (VoucherOrderService) AopContext.currentProxy();return voucherOrderService.createVoucherOrder(voucherId);} finally {lock.unlock();}
}

误删问题

# 线程 1 获取到锁后执行业务,碰到了业务阻塞。
setnx lock:order:1 thread01# 业务阻塞的时间超过了该锁的 TTL 时间,触发锁的超时释放。超时释放后,线程 2 获取到锁并执行业务。
setnx lock:order:1 thread02# 线程 2 执行业务的过程中,线程 1 的业务执行完毕并且释放锁,但是释放的是线程 2 获取到的锁。(线程 2:你 TM 放我锁是吧!)
del lock:order:1# 线程 3 获取到锁(此时线程 23 并行执行业务)
setnx lock:order:1 thread03

在这里插入图片描述

解决方案:在线程释放锁时,判断当前这把锁是否属于自己,如果不属于自己,就不会进行锁的释放(删除)。

# 线程 1 获取到锁后执行业务,碰到了业务阻塞。
setnx lock:order:1 thread01# 业务阻塞的时间超过了该锁的 TTL 时间,触发锁的超时释放。超时释放后,线程 2 获取到锁并执行业务。
setnx lock:order:1 thread02# 线程 2 执行业务的过程中,线程 1 的业务执行完毕并且释放锁。但是线程 1 需要判断这把锁是否属于自己,不属于自己就不会释放锁。
# 于是线程 2 一直持有这把锁直到业务执行结束后才会释放,并且在释放时也需要判断当前要释放的锁是否属于自己。
del lock:order:1# 线程 3 获取到锁并执行业务
setnx lock:order:1 thread03

在这里插入图片描述

基于 Redis 的分布式锁的实现(解决误删问题)

  1. 相较于最开始分布式锁的实现,只需要增加一个功能:释放锁时需要判断当前锁是否属于自己。(而集群环境下不同 JVM 中的线程 ID 可能相同,增加一个 UUID 区分不同 JVM)

  2. 因此通过分布式锁存入 Redis 中的线程标识包括:UUID (服务器id)+ 线程 ID(线程id)。UUID 用于区分不同服务器中线程 ID 相同的线程,线程 ID 用于区分相同服务器的不同线程。

    public class SimpleDistributedLockBasedOnRedis implements DistributedLock {private String name;private StringRedisTemplate stringRedisTemplate;public SimpleDistributedLockBasedOnRedis(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}private static final String KEY_PREFIX = "lock:";// ID_PREFIX 在当前 JVM 中是不变的,主要用于区分不同 JVMprivate static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";/*** 获取锁*/@Overridepublic boolean tryLock(long timeoutSeconds) {// UUID 用于区分不同服务器中线程 ID 相同的线程;线程 ID 用于区分同一个服务器中的线程。String threadIdentifier = ID_PREFIX + Thread.currentThread().getId();Boolean isSucceeded = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadIdentifier, timeoutSeconds, TimeUnit.SECONDS);return Boolean.TRUE.equals(isSucceeded);}/*** 释放锁(释放锁前通过判断 Redis 中的线程标识与当前线程的线程标识是否一致,解决误删问题)*/@Overridepublic void unlock() {// UUID 用于区分不同服务器中线程 ID 相同的线程;线程 ID 用于区分同一个服务器中的线程。String threadIdentifier = THREAD_PREFIX + Thread.currentThread().getId();String threadIdentifierFromRedis = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);// 比较 Redis 中的线程标识与当前的线程标识是否一致if (!StrUtil.equals(threadIdentifier, threadIdentifierFromRedis)) {throw new BusinessException(ErrorCode.OPERATION_ERROR, "释放锁失败");}// 释放锁标识stringRedisTemplate.delete(KEY_PREFIX + name);}
    }

用Lua脚本解决原子性问题

分布式锁的原子性问题

  1. 线程 1 获取到锁并执行完业务,判断锁标识一致后释放锁,释放锁的过程中阻塞,导致锁没有释放成功,并且阻塞的时间超过了锁的 TTL 释放,导致锁自动释放。

  2. 此时线程 2 获取到锁,执行业务;在线程 2 执行业务的过程中,线程 1 完成释放锁操作。

  3. 之后,线程 3 获取到锁,执行业务,又一次导致此时有两个线程同时在并行执行业务

因此,需要保证 unlock() 方法的原子性,即判断线程标识的一致性和释放锁这两个操作的原子性。

Redis 提供了 Lua 脚本功能,在一个脚本中编写多条 Redis 命令,确保 Redis 多条命令执行时的原子性。

unlock操作

private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;static{//写成静态代码块,类加载就可以完成初始定义,就不用每次释放锁都去加载这个,性能提高咯UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));//设置脚本位置UNLOCK_SCRIPT.setResultType(Long.class);
}public void unlock(){//调用lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + name),ID_PREFIX + Thread.currentThread().getId());}

Lua脚本

-- 锁的key
-- local key = KEYS[1]
-- 当前线程标识
-- local threadId = ARGV[1]
-- 获取锁中的线程标识
if(redis.call('get',KEYS[1]) == ARGV[1]) then return redis.call('del',KEYS[1])
end return 0

相关文章:

redis解决常见的秒杀问题

title: redis解决常见的秒杀问题 date: 2025-03-07 14:24:13 tags: redis categories: redis的应用 秒杀问题 每个店铺都可以发布优惠券&#xff0c;保存到 tb_voucher 表中&#xff1b;当用户抢购时&#xff0c;生成订单并保存到 tb_voucher_order 表中。 订单表如果使用数据…...

Springboot3自定义starter笔记

场景&#xff1a;抽取聊天机器人场景&#xff0c;它可以打招呼。 效果&#xff1a;任何项目导入此 starter 都具有打招呼功能&#xff0c;并且问候语中的人名需要可以在配置文件中修改。 创建自定义 starter 项目&#xff0c;引入 spring-boot-starter 基础依赖。 <dependen…...

Modern C++(一)基本概念

1、基本概念 1.1、注释 注释在翻译阶段3会被替换为单个空白字符从程序中移除 1.2、名字与标识符 标识符是一个由数字、下划线、大小写字符组成的任意长度序列。有效的标识符首个字符必须是以A-Z、a-z、下划线开头&#xff0c;。有效的标识符其他字符可以是0-9、A-Z、a-z、下…...

Apache HttpClient 5 用法-Java调用http服务

Apache HttpClient 5 核心用法详解 Apache HttpClient 5 是 Apache 基金会推出的新一代 HTTP 客户端库&#xff0c;相比 4.x 版本在性能、模块化和易用性上有显著提升。以下是其核心用法及最佳实践&#xff1a; 一、添加依赖 Maven 项目&#xff1a; <dependency><…...

Python中plotext 库详细使用(命令行界面中直接绘制各种图形)

更多内容请见: python3案例和总结-专栏介绍和目录 文章目录 plotext概述1.1 plotext介绍1.2 安装二、基本用法2.1 简单绘图2.2 散点图2.3 折线图2.4 条形图2.5 直方图2.6 标题和坐标轴标签2.7 网格和坐标轴2.8 颜色和样式2.9 多图叠加三、高级功能3.1 多图绘制3.2 对数坐标3.3…...

【Java Web】速通JSON

参考笔记&#xff1a;JavaWeb 速通JSON_java webapi调用传json与head-CSDN博客 目录 1.JSON基本介绍 2.JSON串的格式 3.JSON在客户端/浏览器的使用 3.1 JavaScript对象和JSON串的相互转换 3.2 案例演示 4.JSON在服务端的使用 4.1 基本说明 4.2 应用场景 4.2.1 JSON字…...

Ubuntu 20.04 LTS 中部署 网页 + Node.js 应用 + Nginx 跨域配置 的详细步骤

Ubuntu 20.04 LTS 中部署 网页 Node.js 应用 Nginx 跨域配置 的详细步骤 一、准备工作1、连接服务器2、更新系统 二、安装 Node.js 环境1、安装 Node.js 官方 PPA&#xff08;用于获取最新稳定版&#xff09;&#xff1a;2、安装 Node.js 和 npm&#xff08;LTS 长期支持版本…...

java中XML的使用

文章目录 什么是XML特点XML作用XML的编写语法基本语法特殊字符编写 约束XML的书写格式DTD文档schema文档属性命名空间XML命名空间的作用 解析XML的方法​​DOM解析XMLDOM介绍DOM解析包&#xff1a;org.w3c.dom常用接口DOM解析包的使用保存XML文件添加DOM节点修改/删除DOM节点 S…...

Spark SQL 之 Analyzer

Spark SQL 之 Analyzer // Special case for Project as it supports lateral column alias.case p: Project =>val resolvedNoOuter = p.projectList.map(resolveExpressionByPlanChildren(_, p...

Java - Junit框架

单元测试&#xff1a;针对最小的功能单元(方法)&#xff0c;编写测试代码对该功能进行正确性测试。 Junit&#xff1a;Java语言实现的单元测试框架&#xff0c;很多开发工具已经集成了Junit框架&#xff0c;如IDEA。 优点 编写的测试代码很灵活&#xff0c;可以指某个测试方法…...

麒麟系统ARM64架构部署mysql、jdk和java项目

麒麟系统ARM64架构部署mysql、jdk和java项目 一、mysql8的安装 操作步骤&#xff1a; 先下载mysql安装包 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 由于官网里&#xff0c;mysql5.7以及更低版本不支持arm版本的&#xff0c;只能安装mysql8。…...

修复“ImportError: DLL load failed while importing lib: 找不到指定的程序”笔记

#工作记录 一、问题描述 在运行CosyVoice_For_Windows项目时&#xff0c;出现以下报错&#xff1a; Traceback (most recent call last): File "D:\ProgramData\anaconda3\envs\CosyVoice\Lib\pydoc.py", line 457, in safeimport module __import__(path) …...

vllm量化03—INT4 W4A16

本系列基于Qwen2.5-7B&#xff0c;学习如何使用vllm量化&#xff0c;并使用benchmark_serving.py、lm_eval 测试模型性能和评估模型准确度。 测试环境为&#xff1a; OS: centos 7 GPU: nvidia l40 driver: 550.54.15 CUDA: 12.3本文是该系列第3篇——INT4 W4A16 一、量化 f…...

VScode各文件转化为PDF的方法

文章目录 代码.py文件.ipynb文本和代码夹杂的文件方法 1:使用 VS Code 插件(推荐)步骤 1:安装必要插件步骤 2:安装 `nbconvert`步骤 3:间接导出(HTML → PDF)本文遇见了系列错误:解决方案:问题原因步骤 1:降级 Jinja2 至兼容版本步骤 2:确保 nbconvert 版本兼容替代…...

AI日报 · 2025年5月15日|GPT-4.1 登陆 ChatGPT

AI日报 2025年5月15日&#xff5c;GPT-4.1 登陆 ChatGPT 1、OpenAI 在 ChatGPT 全面开放 GPT-4.1 与 GPT-4.1 mini 北京时间 5 月 14 日晚&#xff0c;OpenAI 在官方 Release Notes 中宣布&#xff1a;专为复杂代码与精细指令场景打造的 GPT-4.1 正式加入 ChatGPT&#xff0…...

高效管理多后端服务:Nginx 配置与实践指南

在现代的 Web 开发和运维中&#xff0c;一个系统往往由多个后端服务组成&#xff0c;每个服务负责不同的功能模块。例如&#xff0c;一个电商网站可能包括用户服务、订单服务和支付服务&#xff0c;每个服务都运行在独立的服务器或容器中。为了高效地管理这些服务并提供统一的访…...

从代码学习深度学习 - 实战 Kaggle 比赛:图像分类 (CIFAR-10 PyTorch版)

文章目录 前言1. 读取并整理数据集1.1 读取标签文件1.2 划分训练集和验证集1.3 整理测试集1.4 执行数据整理2. 图像增广2.1 训练集图像变换2.2 测试集(和验证集)图像变换3. 读取数据集3.1 创建 Dataset 对象3.2 创建 DataLoader 对象4. 定义模型4.1 获取 ResNet-18 模型4.2 损…...

什么是路由器环回接口?

路由器环回接口&#xff08;LoopbackInterface&#xff09;是网络设备中的一种逻辑虚拟接口&#xff0c;不依赖物理硬件&#xff0c;但在网络配置和管理中具有重要作用。以下是其核心要点&#xff1a; 一、基本特性 1.虚拟性与稳定性 环回接口是纯软件实现的逻辑接口&#x…...

【高频面试题】LRU缓存

文章目录 1 相关前置知识&#xff08;OS&#xff09;2 面试题 16.25. LRU 缓存2.1 题面2.2 示例2.3 解法1 &#xff08;双端队列哈希表&#xff09;思路 2.4 解法2思路 3 参考 1 相关前置知识&#xff08;OS&#xff09; 为什么需要页面置换算法&#xff1a;当进程运行时&…...

Golang

本文来源 &#xff1a;腾讯元宝 Go语言&#xff08;又称Golang&#xff09;是由Google开发的一种现代编程语言&#xff0c;自2009年发布以来&#xff0c;因其简洁性、高性能和内置并发支持而广受欢迎。以下是关于Go语言的核心特点和优势的总结&#xff1a; 1. ​​核心特点​​…...

20250515配置联想笔记本电脑IdeaPad总是使用独立显卡的步骤

20250515配置联想笔记本电脑IdeaPad总是使用独立显卡的步骤 2025/5/15 19:55 百度&#xff1a;intel 集成显卡 NVIDIA 配置成为 总是用独立显卡 百度为您找到以下结果 ?要将Intel集成显卡和NVIDIA独立显卡配置为总是使用独立显卡&#xff0c;可以通过以下步骤实现?&#xff…...

『已解决』Python virtualenv_ error_ unrecognized arguments_--wheel-bundle

&#x1f4e3;读完这篇文章里你能收获到 &#x1f40d; 了解 virtualenv 参数错误的原因及解决方案&#x1f4e6; 学习如何正确配置 Python 虚拟环境 文章目录 前言一、问题描述1.1 错误现象1.2 影响范围 二、问题分析2.1 根本原因 三、解决方案3.1 兼容处理3.2 完整解决方案 …...

使用 Apache POI 生成 Word 文档

创建一个包含标题、段落和表格的简单文档。 步骤 1:添加依赖 确保你的项目中已经添加了 Apache POI 的依赖。如果你使用的是 Maven,可以在 pom.xml 中添加以下内容: <dependency><groupId>org.apache.poi</groupId>...

表记录的检索

1.select语句的语法格式 select 字段列表 from 表名 where 条件表达式 group by 分组字段 [having 条件表达式] order by 排序字段 [asc|desc];说明&#xff1a; from 子句用于指定检索的数据源 where子句用于指定记录的过滤条件 group by 子句用于对检索的数据进行分组 ha…...

【PX4飞控】在 Matlab Simulink 中使用 Mavlink 协议与 PX4 飞行器进行交互

这里列举一些从官网收集的比较有趣或者实用的功能。 编写 m 脚本与飞行器建立 UDP 连接&#xff0c;并实时可视化 Mavlink 消息内容&#xff0c;或者读取脚本离线分析数据。不光能显示 GPS 位置或者姿态等信息的时间曲线&#xff0c;可以利用 Matlab Plot 功能快速定制化显示一…...

文章复现|(1)整合scRNA-seq 和空间转录组学揭示了子宫内膜癌中 MDK-NCL 依赖性免疫抑制环境

https://www.frontiersin.org/journals/immunology/articles/10.3389/fimmu.2023.1145300/full 目标&#xff1a;肿瘤微环境(TME)在子宫内膜癌(EC)的进展中起着重要作用。我们旨在评估EC的TME中的细胞群体。 方法&#xff1a;我们从GEO下载了EC的单细胞RNA测序(scRNA-seq)和空…...

自用Vscode 配置c++ debug环境

前言 使用vscode配置c debug环境的好处 1、可以借助vscode方便轻量的扩展和功能 2、避免了传统使用gdb 复杂按键以及不够直观的可视化 3、方便一次运行&#xff0c;断点处查看变量&#xff0c;降低找bug难度 4、某大公司项目采用类似配置&#xff0c;经过实践检验 配置c运行环…...

STM32单片机内存分配详细讲解

单片机的内存无非就两种&#xff0c;内部FLASH和SRAM&#xff0c;最多再加上一个外部的FLASH拓展。在这里我以STM32F103C8T6为例子讲解FLASH和SRAM。 STM32F103C8T6具有64KB的闪存和20KB的SRAM。 一. Flash 1.1 定义 非易失性存储器&#xff0c;即使在断电后&#xff0c;其所…...

从算力困境到创新突破:GPUGEEK如何重塑我的AI开发之旅

目录 从算力困境到创新突破&#xff1a;GPUGEEK如何重塑我的AI开发之旅开发者的算力挣扎&#xff1a;一个不得不面对的现实AI算力市场的尴尬现状&#xff1a;为什么我们需要另辟蹊径1. 资源分配失衡与价格壁垒2. 技术门槛与环境复杂性 GPUGEEK深度剖析&#xff1a;不只是又一个…...

基于OpenCV的人脸微笑检测实现

文章目录 引言一、技术原理二、代码实现2.1 关键代码解析2.1.1 模型加载2.1.2 图像翻转2.1.3 人脸检测 微笑检测 2.2 显示效果 三、参数调优建议四、总结 引言 在计算机视觉领域&#xff0c;人脸检测和表情识别一直是热门的研究方向。今天我将分享一个使用Python和OpenCV实现…...

2025认证杯数学建模第二阶段A题小行星轨迹预测思路+模型+代码

2025认证杯数学建模第二阶段思路模型代码&#xff0c;详细内容见文末名片 一、问题重述 1.1 问题背景 在浩瀚无垠的宇宙中&#xff0c;近地小行星&#xff08;NEAs&#xff09;宛如一颗颗神秘的“太空子弹”&#xff0c;其轨道相对接近地球&#xff0c;给我们的蓝色星球带来…...

Uniapp 安卓实现讯飞语音听写(复制即用)

在移动应用开发中&#xff0c;语音交互功能能够极大提升用户体验&#xff0c;让操作更加便捷自然。讯飞语音听写技术凭借其高准确率和稳定性&#xff0c;成为众多开发者的选择。本文将详细介绍如何在 Uniapp 项目中&#xff0c;实现安卓端的讯飞语音听写功能&#xff0c;帮助你…...

【FileZilla】 从事件类型到消息类型的函数形参类型转化

本篇其实是前篇【Filezilla】 dispatch函数重载的例子-CSDN博客的一个补充&#xff0c;其中涉及到【FileZilla】事件调用机制代码解析-CSDN博客中的事件分发机制时钩子函数的参数传递怎么实现的。跟【FileZilla】sftp协议的数据传输上传和下载-CSDN博客同样&#xff0c;用事件是…...

python打卡day26

函数、参数、变量 知识点回顾&#xff1a; 函数的定义变量作用域&#xff1a;局部变量和全局变量函数的参数类型&#xff1a;位置参数、默认参数、不定参数传递参数的手段&#xff1a;关键词参数传递参数的顺序&#xff1a;同时出现三种参数类型时 def function_name(parameter…...

RPC框架源码分析学习(二)

RPC框架源码分析与原理解读 前言 在分布式系统开发中&#xff0c;远程过程调用(RPC)是一项基础且关键的技术。通过对KVstorageBaseRaft-cpp项目RPC模块的源码分析&#xff0c;我深入理解了RPC框架的工作原理和实现细节。本文将从程序员视角分享我的学习心得。 框架概述 本项…...

算法分析:蛮力法

一、实验目的 1 掌握蛮力法的设计思想(利用计算机去穷举所有的可能解,再从中依次找出可行解) 2 掌握蛮力法的具体实现和时间复杂度分析 3 理解蛮力法的常见特性 实验要求&#xff1a;先用伪代码描述利用蛮力法解决的算法解决方案&#xff0c;再用程序实现&#xff0c;计算时间…...

构建RAG混合开发---PythonAI+JavaEE+Vue.js前端的实践

写在前文&#xff1a;之所以设计这一套流程&#xff0c;是因为 Python在前沿的科技前沿的生态要比Java好&#xff0c;而Java在企业级应用层开发比较活跃&#xff1b; 毕竟许多企业的后端服务、应用程序均采用Java开发&#xff0c;涵盖权限管理、后台应用、缓存机制、中间件集成…...

游戏引擎学习第280天:精简化的流式实体sim

回顾并为今天的内容做铺垫 今天的任务是让之前关于实体存储方式的改动真正运行起来。我们现在希望让实体系统变得更加真实和实用&#xff0c;能够支撑我们游戏实际所需的功能。这就要求我们对它进行更合理的实现和调试。 昨天我们基本让代码编译通过了&#xff0c;但实际上还…...

小程序映射逻辑处理

onLoad: function (options) { // 如果直接从options获取数据 this.setData({ jielunpin:发羽音, birthStr: 1944-01-01 }); // 处理诊断结论 this.processJielunpin(); // 添加一个处理诊断结论的函数 processJielunpin: function() { // 获取jielunpin和birthSt…...

亚马逊,temu测评采购低成本养号策略:如何用一台设备安全批量管理买家账号

只要能够巧妙规避平台的检测和风控措施&#xff0c;测评便可安全进行。 自养号测评&#xff0c;它更便于卖家掌控&#xff0c;且能降低风险。现在很多卖家都是自己养号&#xff0c;自己养号都是精养&#xff0c;不是自动的机刷&#xff0c;买家账号掌握在自己手里&#xff0c;更…...

TCP实现安全传输的核心机制 + TCP的报文讲解(全程图文讲解)

目录 一、TCP的协议和数据报格式 二、TCP常见的核心机制 1. 确认应答 2. 超时重传 3. 连接管理 三次握手&#xff08;建立连接&#xff09; 四次挥手&#xff08;断开连接&#xff09; 常见的状态和整体的传输流程 4. 滑动窗口 5. 流量控制 6. 拥塞控制 7. 延迟应…...

【测试工具】selenium和playwright如何选择去构建自动化平台

构建UI自动化平台服务&#xff0c;在底层选择自动化框架&#xff0c;selenium和playwright这两个如何选择 在构建UI自动化平台服务时&#xff0c;选择底层自动化框架&#xff08;如 Selenium 和 Playwright&#xff09;是一个非常关键的决策&#xff0c;直接影响平台的性能、可…...

ES常识8:ES8.X如何实现热词统计

文章目录 一、数据采集与存储设计1. 确定需记录的字段2. 设计搜索日志索引 二、数据写入与采集三、热门搜索词统计&#xff08;核心逻辑&#xff09;1. 基础版&#xff1a;近 7 天热门搜索词&#xff08;按出现次数排序&#xff09;2. 进阶版&#xff1a;加权热门词&#xff08…...

可解释性AI 综述《Explainable AI for Industrial Fault Diagnosis: A Systematic Review》

一、研究背景与动因&#xff08;Background & Motivation&#xff09; 随着工业4.0与工业5.0的发展&#xff0c;工业生产越来越依赖自动化与智能化手段&#xff0c;以实现高效、预测性维护与运行优化。在这一背景下&#xff0c;人工智能&#xff08;AI&#xff09;与机器学…...

数据可视化-----子图的绘制及坐标轴的共享

目录 绘制固定区域的子图 &#xff08;一&#xff09;、绘制单子图 subplot()函数 Jupyter Notebook的绘图模式 &#xff08;二&#xff09;、多子图 subplots()--可以在规划好的所有区域中一次绘制多个子图 &#xff08;三&#xff09;、跨行跨列 subplot2grid()---将整…...

nginx 配置

proxy_pass 结尾斜杠规则 不带斜杠‌&#xff1a;保留原始请求路径。 location /api {proxy_pass http://backend; # 转发到 http://backend/api }带斜杠‌&#xff1a;剥离 location 匹配的路径前缀。 location /api/ {proxy_pass http://backend/; # 转发到 http://back…...

《从零开始入门递归算法:搜索与回溯的核心思想 + 剑指Offer+leetcode高频面试题实战(含可视化图解)》​

一.递归 1.汉诺塔 题目链接&#xff1a;面试题 08.06. 汉诺塔问题 - 力扣&#xff08;LeetCode&#xff09; 题目解析&#xff1a;将A柱子上的盘子借助B柱子全部移动到C柱子上。 算法原理&#xff1a;递归 当A柱子上的盘子只有1个时&#xff0c;我们可以直接将A上的盘子直…...

船舶制造业数字化转型:驶向智能海洋新航道

在全球海洋经济蓬勃发展的当下&#xff0c;船舶制造业作为海洋产业的重要支柱&#xff0c;正面临着前所未有的机遇与挑战。船舶制造周期长、技术复杂&#xff0c;从设计图纸到最终交付&#xff0c;涉及成千上万的零部件和复杂的工艺流程&#xff0c;传统制造模式已难以满足市场…...

SpringBoot 自动装配流程

Spring Boot 的自动装配&#xff08;Auto Configuration&#xff09;是其最核心的特性之一&#xff0c;它让你能“开箱即用”&#xff0c;极大简化了配置。下面是 Spring Boot 自动装配的整体流程&#xff08;从启动到生效&#xff09; 的详细解析&#xff1a; ✅ 一、整体流程…...

Vue 3 实现后端 Excel 文件流导出功能(Blob 下载详解)

&#x1f4a1; 本文以告警信息导出为例&#xff0c;介绍 Vue 3 中如何通过 Axios 调用后端接口并处理文件流&#xff0c;实现 Excel 自动下载功能。 &#x1f4d1; 目录 一、前言 二、后端接口说明 三、前端实现思路 四、导出功能完整代码 五、常见问题处理 六、效果展示 …...