Redis 优化秒杀(异步秒杀)
目录
为什么需要异步秒杀
异步优化的核心逻辑是什么?
阻塞队列的特点是什么?
Lua脚本在这里的作用是什么?
异步调用创建订单的具体逻辑是什么?
为什么要用代理对象proxy调用createVoucherOrder方法?
对于代码的详细解释:
SECKILL_ORDER_EXECUTOR 是什么?
@PostConstruct 是什么?
VoucherOrderHandler 是什么?
VoucherOrderHandler 调用的handleVoucherOrder:
数据库操作的注意点有哪些?
seckillVoucher 方法:
单线程线程池、阻塞队列、seckillVoucher 和 VoucherOrderHandler 的协作过程总结
方法调用流程总览
方法逻辑一览表
完整代码
在秒杀场景中,我们可以将库存存入 Redis,并通过 Lua 脚本来判断用户是否有秒杀资格,同时实现一人一单的限制。由于 Redis 的单线程特性和 Lua 脚本的原子性保障,能够避免多个线程交叉执行 Redis 命令导致的并发问题。同时,使用阻塞队列将订单请求进行缓冲,当线程尝试从队列中获取订单时,如果队列为空,线程会被阻塞,直到有新订单加入队列,线程才会被唤醒并处理订单,从而实现高效的生产者-消费者模型。
为什么需要异步秒杀
1. 防止数据库压力过载
- 异步秒杀通过将订单请求写入阻塞队列,削峰填谷,避免将瞬时高并发请求直接传递到数据库。
- 消费者线程从队列中按顺序取出订单进行处理,减少数据库同时处理的请求量。
2. 提升系统响应速度
- 秒杀请求在异步架构中:
- 同步部分:快速返回秒杀结果(例如秒杀资格校验)。
- 异步部分:订单的具体处理(如扣减库存、保存订单)放到后台处理。
- 这种分离让用户能快速得到响应,而系统后台有更多时间处理复杂的订单逻辑。
异步优化的核心逻辑是什么?
问:为什么需要异步优化秒杀订单? 答:在高并发场景中,秒杀会同时产生大量订单请求。如果直接将请求交给数据库处理,容易导致数据库压力过大,从而系统崩溃。异步优化通过使用阻塞队列将订单请求排队,避免直接对数据库产生瞬时高负载。
问:如何实现异步处理? 答:将订单信息保存到阻塞队列中,使用单线程(线程池中的线程)从队列中按顺序取出订单进行处理。这样可以削峰填谷,减轻数据库压力。
阻塞队列的特点是什么?
问:阻塞队列的作用是什么? 答:阻塞队列是线程安全的队列,支持生产者-消费者模型。在代码中,生产者是seckillVoucher
方法,它将订单信息加入阻塞队列;消费者是VoucherOrderHandler
线程,它从队列中取出订单进行处理。
问:为什么使用阻塞队列? 答:阻塞队列的特点是,如果队列为空,消费者线程会阻塞等待;如果队列满了,生产者线程会阻塞等待。这样可以很好地协调生产者和消费者的速度,避免资源浪费或超负荷。
Lua脚本在这里的作用是什么?
问:为什么使用Lua脚本操作Redis? 答:Lua脚本在Redis中是原子执行的。使用Lua脚本可以保证秒杀资格验证和库存扣减的原子性,避免并发问题。
问:Lua脚本验证了什么? 答:
- 用户是否重复下单(通过Redis中存储的用户信息判断)。
- 秒杀库存是否充足(通过Redis中存储的库存数量判断)。
-- 参数
-- 优惠券id
local voucherId = ARGV[1]
-- 用户id
local userId = ARGV[2]-- 数据key
local stockKey = 'seckill:stock:'.. voucherId
local orderKey = 'seckill:order:'.. voucherId-- 检查库存是否足够
if (tonumber(redis.call('get', stockKey)) <= 0) thenreturn 1 -- 库存不足
end-- 检查用户是否重复下单
if (redis.call('sismember', orderKey, userId) == 1) thenreturn 2 -- 重复下单
end-- 减少库存并记录订单
redis.call('incrby', stockKey, -1)
redis.call('sadd', orderKey, userId)
return 0
将秒杀券的库存以String形式存入Redis
@Override@Transactionalpublic void addSeckillVoucher(Voucher voucher) {// 保存优惠券save(voucher);// 保存秒杀信息SeckillVoucher seckillVoucher = new SeckillVoucher();seckillVoucher.setVoucherId(voucher.getId());seckillVoucher.setStock(voucher.getStock());seckillVoucher.setBeginTime(voucher.getBeginTime());seckillVoucher.setEndTime(voucher.getEndTime());seckillVoucherService.save(seckillVoucher);// 保存秒杀 库存 存入Redis当中stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY + voucher.getId(),voucher.getStock().toString());}
异步调用创建订单的具体逻辑是什么?
@Override
public Result seckillVoucher(Long voucherId) {Long userId = UserHolder.getUser().getId();// 1. 校验秒杀资格Long res = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString());if (res != 0) {// 秒杀资格校验失败return Result.fail(res == 1 ? "库存不足" : "重复下单");}// 2. 生成订单信息VoucherOrder voucherOrder = new VoucherOrder();long orderID = redisIdWorker.nextId("order");voucherOrder.setId(orderID);voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);// 3. 将订单信息放入阻塞队列orderTasks.add(voucherOrder);// 获取代理对象proxy = (IVoucherOrderService) AopContext.currentProxy();return Result.ok(orderID);
}
问:seckillVoucher
方法中发生了什么? 答:这是异步调用的入口逻辑,分为以下几个步骤:
- 验证秒杀资格:
- 使用Lua脚本操作Redis,确保原子性。
- 判断用户是否重复下单,或者库存是否不足。
- 如果秒杀资格验证失败,则直接返回错误信息。
- 生成订单信息:
- 使用
RedisIdWorker
生成订单ID。 - 将订单信息(用户ID、代金券ID等)封装成
VoucherOrder
对象。
- 使用
- 将订单信息保存到阻塞队列:
- 调用
orderTasks.add(voucherOrder)
将订单加入阻塞队列中。
- 调用
- 返回订单ID:
- 在返回给用户订单ID时,并没有真正完成订单,而是进入队列等待处理。
为什么要用代理对象proxy
调用createVoucherOrder
方法?
问:为什么不直接调用createVoucherOrder
?
答:因为 createVoucherOrder
方法是事务方法,需要通过代理对象调用才能生效。
-
Spring 的事务机制基于 AOP(面向切面编程)实现:
- Spring 使用代理对象(动态代理或 CGLIB 代理)来拦截对事务方法的调用,并在方法执行前后添加事务管理逻辑(如开启事务、提交事务或回滚事务)。
- 如果直接调用类内部的事务方法,调用不会经过代理对象,而是直接执行原始方法,Spring 的事务管理器无法介入,导致事务逻辑失效。
-
内部调用的问题:
- 在类的内部直接调用另一个事务方法时,调用不会经过代理对象,而是通过
this
调用,因此事务拦截器不会生效,事务注解(@Transactional
)失效。
- 在类的内部直接调用另一个事务方法时,调用不会经过代理对象,而是通过
问:代理对象是如何获取的?
- 将代理对象声明为一个成员变量,通过
AopContext.currentProxy()
获取当前类的代理对象。 - 原因:
AopContext.currentProxy()
返回的是 Spring AOP 生成的当前类的代理对象,它能够拦截方法调用,从而触发事务管理逻辑。 - 在异步线程中直接调用当前类的方法时,事务不会生效,因为直接调用是通过
this
引用,而不是代理对象调用。通过成员变量保存的代理对象,即使在异步线程中调用方法,也可以确保事务逻辑有效。 - 最终,通过代理对象调用
createVoucherOrder
方法,可以正常触发 Spring 的事务管理器,确保事务功能生效。
对于代码的详细解释:
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();private final BlockingQueue<VoucherOrder> orderTasks = new LinkedBlockingQueue<>();
@PostConstruct
private void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}private class VoucherOrderHandler implements Runnable {@Overridepublic void run() {while (true) {try {// 获取队列当中的订单VoucherOrder voucherOrder = orderTasks.take();handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error("Error processing order", e);}}}}
SECKILL_ORDER_EXECUTOR
是什么?
SECKILL_ORDER_EXECUTOR
是一个 单线程线程池,用来处理秒杀订单的异步任务。
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
单线程线程池的特点是:线程池中始终只有一个线程,任务会按顺序执行,适合需要顺序处理的场景。
它的主要作用是 管理和调度线程的生命周期。具体来说:
启动和管理消费者线程:
VoucherOrderHandler
需要一个线程不断运行,用来从阻塞队列中取订单并处理。- 线程池
SECKILL_ORDER_EXECUTOR
的作用是启动这个线程,并保证这个线程的生命周期由线程池管理。
线程复用:
- 如果你手动创建线程(
new Thread()
),可能会导致系统频繁创建和销毁线程,浪费系统资源。 - 使用线程池可以复用线程,减少线程的创建和销毁开销,提高性能。
稳定性:
- 如果
VoucherOrderHandler
线程在执行中意外退出(例如抛出未捕获异常),线程池会自动接管并重新启动线程,保证任务不会中断。
在这里,SECKILL_ORDER_EXECUTOR
通过单线程的方式从阻塞队列中取出订单,按顺序处理,确保秒杀订单的处理逻辑是线程安全的。
@PostConstruct
是什么?
@PostConstruct
是 Java 的一个注解,作用是在 Spring 容器将 Bean 初始化完成后,立即执行标注的方法。换句话说,当 Spring 加载并创建了 VoucherOrderServiceImpl
实例后,会自动调用 init()
方法。
这是一个生命周期回调方法,常用于初始化逻辑,比如启动线程、加载配置等。
init()
方法的作用是什么?
- 这个方法的主要作用是 启动一个专用线程(由单线程线程池管理),用于从阻塞队列中取出订单并进行异步处理。
- 通过
SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
,将VoucherOrderHandler
提交到线程池中,线程池会启动一个线程,持续运行VoucherOrderHandler
中的逻辑。
VoucherOrderHandler
是什么?
VoucherOrderHandler
是一个内部类,它实现了 Runnable
接口,代表一个任务。
- 任务的核心逻辑是:从阻塞队列中取出订单并处理。
- 它的
run()
方法包含一个while(true)
循环,这样线程会一直运行,不断从队列中取出订单(通过orderTasks.take()
),直到程序终止。
VoucherOrderHandler
调用的handleVoucherOrder:
- 防止同一用户多次下单(重复下单)。
- 调用执行订单的具体业务逻辑的方法createVoucherOrder
(如扣减库存、保存订单等)。@Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {// 实现一人一单,我们需要先判断该用户是否已经抢过了// 根据优惠券id和用户id查询订单Long userId = UserHolder.getUser().getId();int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder).count();if (count > 0) {log.error("已经购买过,不可重复购买!");}// 扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherOrder). // eq("stock",voucher.getStock()). // 加个乐观锁,如果现在的库存和我之前查询的库存不相同,说明在我之前就有线程修改了数据库gt("stock", 0).update();if (!success) {log.error("库存不足!");}// 写入数据库 不需要再返回orderId了,因为之前在seckillVoucher已经返回了save(voucherOrder);}
数据库操作的注意点有哪些?
问:如何实现一人一单的限制? 答:在createVoucherOrder
方法中,通过查询数据库判断用户是否已经购买过对应的代金券。
问:如何扣减库存? 答:使用seckillVoucherService
执行SQL语句更新库存,并通过gt(\"stock\", 0)
确保库存大于0。
整体逻辑总结
-
触发时机: 当
VoucherOrderServiceImpl
被 Spring 加载并实例化后,@PostConstruct
注解标注的init()
方法会被调用。 -
作用:
init()
方法向线程池提交了一个VoucherOrderHandler
任务,这个任务会启动一个线程,不断从阻塞队列中取出订单并调用相关处理逻辑(handleVoucherOrder
)。
seckillVoucher
方法:
单线程线程池、阻塞队列、seckillVoucher
和 VoucherOrderHandler
的协作过程总结
seckillVoucher
是厨师:
- 它负责接收顾客的订单请求(秒杀请求),检查是否符合要求(库存是否足够、是否重复下单),然后生成订单(菜品)并放在桌子上(阻塞队列)。
- 核心职责:生产订单,确保每个订单合法并生成完整订单信息。
BlockingQueue
是桌子:
- 它负责临时存放厨师制作好的订单(菜品),保证每个订单都按顺序排列。
- 如果桌子空了,顾客(消费者线程)只能等;如果桌子满了,厨师(生产者线程)也需要暂停制作。
- 核心职责:缓冲区,用于在生产和消费之间解耦。
VoucherOrderHandler
是顾客:
- 它负责从桌子上取菜(从队列中取订单),并最终消费(处理订单,包括扣减库存、写入数据库等)。
- 如果桌子没有菜了,它会耐心等待;一旦有菜,它会立刻取走并处理。
- 核心职责:消费订单,执行订单处理逻辑。
SECKILL_ORDER_EXECUTOR
是服务员:
- 它负责启动和管理顾客(消费者线程),确保顾客始终在桌子旁边等待取菜。
- 如果顾客突然有事不要菜品了(比如异常退出),服务员会招待一个新的顾客来接替。
- 核心职责:管理消费者线程的生命周期,确保订单处理不断运行。
方法调用流程总览
- 用户发起秒杀请求,触发
seckillVoucher
方法。 seckillVoucher
验证秒杀资格并将订单放入阻塞队列。VoucherOrderHandler
(由线程池管理的消费者线程)从队列中取出订单,调用handleVoucherOrder
进行处理。handleVoucherOrder
利用分布式锁防止重复下单,并调用createVoucherOrder
完成订单的核心逻辑。createVoucherOrder
执行订单的最终处理,包括扣减库存、写入数据库等。
方法逻辑一览表
方法 | 作用 | 关键逻辑 |
---|---|---|
seckillVoucher | 秒杀请求入口,生成订单并加入阻塞队列 | 验证秒杀资格,生成订单信息,加入阻塞队列。 |
阻塞队列 (BlockingQueue ) | 存储订单信息,实现生产者与消费者的解耦 | 线程安全存储,缓冲生产者和消费者速度差异。 |
VoucherOrderHandler | 消费者线程,从队列中取订单并调用处理方法 | 从队列取订单,调用 handleVoucherOrder 。 |
handleVoucherOrder | 防止重复下单,调用核心业务逻辑 | 创建分布式锁,防止重复下单,调用 createVoucherOrder 。 |
createVoucherOrder | 执行订单的核心逻辑 | 校验订单、扣减库存、保存订单到数据库。 |
完整代码
@Service
@RequiredArgsConstructor
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;final RedisIdWorker redisIdWorker;final StringRedisTemplate stringRedisTemplate;final RedissonClient redissonClient;private static final DefaultRedisScript<Long> SECKILL_SCRIPT;static {SECKILL_SCRIPT = new DefaultRedisScript<>();SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);}private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();private IVoucherOrderService proxy;private final BlockingQueue<VoucherOrder> orderTasks = new LinkedBlockingQueue<>();@PostConstructprivate void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}private class VoucherOrderHandler implements Runnable {@Overridepublic void run() {while (true) {try {// 获取队列当中的订单VoucherOrder voucherOrder = orderTasks.take();handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error("Error processing order", e);}}}}private void handleVoucherOrder(VoucherOrder voucherOrder) {Long userId = voucherOrder.getUserId();// 1. Create lockRLock lock = redissonClient.getLock("lock:order:" + userId);// 2. Try to acquire lockboolean isLock = lock.tryLock();if (!isLock) {log.error("Duplicate order not allowed");return;}try {// 3. Create order via proxyproxy.createVoucherOrder(voucherOrder);} finally {// 4. Release locklock.unlock();}}/*** 基于异步Lua脚本保证原子性** @param voucherId* @return*/@Overridepublic Result seckillVoucher(Long voucherId) {Long userId = UserHolder.getUser().getId();// 执行Lua脚本Long res = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString());// 判断返回值是否为0if (res != 0) {// 非0 则没有秒杀资格return Result.fail(res == 1 ? "库存不足" : "重复下单");}// 从Redis当中获取下单信息long orderId = redisIdWorker.nextId("order");// TODO 为0 表示有秒杀资格 需要将下单信息保存在阻塞队列当中// 创建订单VoucherOrder voucherOrder = new VoucherOrder();// 订单idlong orderID = redisIdWorker.nextId("order");voucherOrder.setId(orderID);// 用户idvoucherOrder.setUserId(UserHolder.getUser().getId());// 代金券idvoucherOrder.setVoucherId(voucherId);// 保存到阻塞队列当中orderTasks.add(voucherOrder);// 获取代理对象proxy = (IVoucherOrderService) AopContext.currentProxy();return Result.ok(orderId);}@Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {// 实现一人一单,我们需要先判断该用户是否已经抢过了// 根据优惠券id和用户id查询订单Long userId = UserHolder.getUser().getId();int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder).count();if (count > 0) {log.error("已经购买过,不可重复购买!");}// 扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherOrder).
// eq("stock",voucher.getStock()). // 加个乐观锁,如果现在的库存和我之前查询的库存不相同,说明在我之前就有线程修改了数据库gt("stock", 0).update();if (!success) {log.error("库存不足!");}// 写入数据库save(voucherOrder);}
}
相关文章:
Redis 优化秒杀(异步秒杀)
目录 为什么需要异步秒杀 异步优化的核心逻辑是什么? 阻塞队列的特点是什么? Lua脚本在这里的作用是什么? 异步调用创建订单的具体逻辑是什么? 为什么要用代理对象proxy调用createVoucherOrder方法? 对于代码的详细…...
NFS 组件容器化部署实战指南
文章目录 前言部署NFS服务器K8S部署NFS问题记录 前言 使用nfs-client-provisioner这个应用,利用nfs server给kubernets提供作为持久化后端,并且动态提供pv。所有节点需要安装nfs-utils组件,并且nfs服务器与kubernets worker节点都能网络连通…...
LCE(Local Cascade Ensemble)预测模型和LSTM(Long Short-Term Memory)模型在效果和特点上存在显著差异
LCE(Local Cascade Ensemble)预测模型和LSTM(Long Short-Term Memory)模型在效果和特点上存在显著差异。以下是对两者的比较: 一、效果比较 LCE模型: 优势:LCE结合了随机森林和XGBoost的优势&a…...
rk3568平台Buildroot编译实践:内核rootfs定制 及常见编译问题
目录 编译前准备常规编译流程定制内核修改内核 参数并增量 保存修改rootfs并增量 保存修改rootfs包下载源rootfs软件包增删refBuildroot 是一个用于自动化构建嵌入式 Linux 系统的工具。它通过使用简单的配置文件和 Makefile,能够从源代码开始交叉编译出一个完整的、可以运行在…...
头歌python实验:网络安全应用实践-恶意流量检测
第1关:re 库的使用 本关任务:编写一个能正则匹配出 ip 地址的小程序。 re 的主要功能函数 常用的功能函数包括: compile、search、match、split、findall(finditer)、sub(subn)。 re.search 函数 re.search 扫描整个字符串并返回第一个成功的匹配。 函数语法: re…...
Linux内核编程(二十一)USB应用及驱动开发
一、基础知识 1. USB接口是什么? USB接口(Universal Serial Bus)是一种通用串行总线,广泛使用的接口标准,主要用于连接计算机与外围设备(如键盘、鼠标、打印机、存储设备等)之间的数据传输和电…...
机器学习实战——决策树:从原理到应用的深度解析
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ 决策树(Decision Tree)是一种简单而直观的分类与回归模型,在机器学习中广泛应用。它的…...
计算机网络的定义与发展历程
计算机网络的定义 计算机网络是指通过通信设备和传输介质将分布在不同地点的计算机及其相关设备(如打印机、服务器等)连接起来,按照一定的通信协议进行数据交换与资源共享的系统。计算机网络的基本功能包括:信息的传输、资源共享…...
什么是Kafka?有什么主要用途?
大家好,我是锋哥。今天分享关于【什么是Kafka?有什么主要用途?】面试题。希望对大家有帮助; 什么是Kafka?有什么主要用途? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Kafka 是一个分布式流…...
Ubuntu 24.04 LTS系统安装Docker踩的坑
一开始我跟着Docker给出的官网文档 Ubuntu | Docker Docs 流程走,倒腾了两个多小时,遇到了各种坑,最后放弃了。在我们使用脚本安装Docker命令前,我们先把已经安装的Docker全部卸载掉。 卸载Docker 1.删除docker及安装时自动安装…...
算法基础 - 冒泡排序
文章目录 1、原理演示2、示例一 冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。重复进行直到没有再需要交换的元素,这意味着数列已经排序完成。 冒泡排序法采用…...
为答疑机器人扩展问题分类与路由功能
1.意图识别 2. 构建路由模块 简单的意图识别 from chatbot import llmfrom config.load_key import load_key load_key()prompt 【角色背景】 你是一个问题分类路由器,需要识别问题的类型。 --- 【任务要求】 问题的类型目前有:公司内部文档查询、内…...
如何解决 /proc/sys/net/bridge/bridge-nf-call-iptables 文件缺失的问题
在使用 Linux 系统,尤其是在容器化环境(如 Docker、Kubernetes)中时,我们可能会遇到如下错误信息: [ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptabl…...
道品科技智慧农业与云平台:未来农业的变革之路
随着全球人口的不断增长,农业面临着前所未有的挑战。如何在有限的土地和资源上提高农业生产效率,成为了各国政府和农业从业者亟待解决的问题。智慧农业的兴起,结合云平台的应用,为农业的可持续发展提供了新的解决方案。 ## 一、智…...
芯片详细讲解,从而区分CPU、MPU、DSP、GPU、FPGA、MCU、SOC、ECU
目录 芯片的概念结构 芯片的派系划分 通用芯片(CPU,MPU,GPU,DSP) 定制芯片(FPGA,ASIC) 芯片之上的集成(MCU,SOC,ECU) 软硬件的匹…...
2025年01月09日Github流行趋势
1. 项目名称:khoj 项目地址url:https://github.com/khoj-ai/khoj项目语言:Python历史star数:22750今日star数:1272项目维护者:debanjum, sabaimran, MythicalCow, aam-at, eltociear项目简介:你…...
OpenCV计算机视觉 06 图像轮廓检测(轮廓的查找、绘制、特征、近似及轮廓的最小外接圆外接矩形)
目录 图像轮廓检测 轮廓的查找 轮廓的绘制 轮廓的特征 面积 周长 根据面积显示特定轮廓 轮廓的近似 给定轮廓的最小外接圆、外接矩形 外接圆 外接矩形 图像轮廓检测 轮廓的查找 API函数 image, contours, hierarchy cv2.findContours(img, mode, method) 代入参…...
安装完docker后,如何拉取ubuntu镜像并创建容器?
1. 先docker拉取ubuntu镜像 docker search ubuntu #搜索ubuntu 镜像 docker pull ubuntu:22.04 #拉取ubuntu 镜像 docker images #下载完成后,查看已经下载的镜像 docker run --name ubuntu_container -dit ubuntu:22.04 /bin/bash # docker container -l 2.…...
C++中的表达式
文章目录 算数操作符位操作符bitset对象或整型值的使用将位移操作符用作IO 赋值操作符赋值操作符的右结合性赋值操作具有低优先级 自增和自减操作符条件操作符sizeof操作符优先级new和delete表达式类型转换何时发生隐式转换显示转换旧式强制类型转换 C中的表达式由一个或多个操…...
直流无刷电机控制(FOC):电流模式
目录 概述 1 系统框架结构 1.1 硬件模块介绍 1.2 硬件实物图 1.3 引脚接口定义 2 代码实现 2.1 软件架构 2.2 电流检测函数 3 电流环功能实现 3.1 代码实现 3.2 测试代码实现 4 测试 概述 本文主要介绍基于DengFOC的库函数,实现直流无刷电机控制&#x…...
LabVIEW水轮发电机组振动摆度故障诊断
本文介绍了基于LabVIEW的水轮发电机组振动摆度故障诊断系统的设计与实施过程。系统在通过高效的故障诊断功能,实现水轮发电机组的振动、温度等关键指标的实时监控与智能分析,从而提高电力设备的可靠性和安全性。 项目背景 随着电力行业对设备稳定性…...
《暗时间》读书笔记
前言 2003年,刘未鹏在杂志上发表了自己的第一篇文章,并开始写博客。最初的博客较短,也较琐碎,并夹杂着一些翻译的文章。后来渐渐开始有了一些自己的心得和看法。在这8年里,作者平均每个月写1篇博客或更少,…...
【SQL】COUNT()函数 用法详解
COUNT()函数 COUNT函数用法:COUNT ( [ALL | DISTINCT] column | expression | *) ALL关键字指示统计所有值,而DISTINCT关键字强制函数仅对不同的值进行操作。 默认情况下,使用ALL选项。条件表达式 COUNT()函数中条件表达式加 OR null。例如…...
Kafka优势剖析-幂等性和事务
目录 1. 幂等性(Idempotence) 1.1 什么是幂等性? 1.2 幂等性的实现 1.2.1 生产者 ID 和序列号 1.2.2 重复消息检测 1.2.3 幂等性的优势 1.3 幂等性的配置 2. 事务支持(Transactions) 2.1 什么是事务支持&…...
内网穿透的应用-Ubuntu本地Docker部署Leantime项目管理工具随时随地在线管理项目
文章目录 前言1.关于Leantime2.本地部署Leantime3.Leantime简单实用4.安装内网穿透5.配置Leantime公网地址6. 配置固定公网地址 前言 本文主要介绍如何在本地Linux系统使用Docker部署Leantime,并结合cpolar内网穿透工具轻松实现随时随地查看浏览器页面,…...
Python 中的错误处理与调试技巧
💖 欢迎来到我的博客! 非常高兴能在这里与您相遇。在这里,您不仅能获得有趣的技术分享,还能感受到轻松愉快的氛围。无论您是编程新手,还是资深开发者,都能在这里找到属于您的知识宝藏,学习和成长…...
使用 NestJS 构建高效且模块化的 Node.js 应用程序,从安装到第一个 API 端点:一步一步指南
一、安装 NestJS 要开始构建一个基于 NestJS 的应用,首先需要安装一系列依赖包。以下是必要的安装命令: npm i --save nestjs/core nestjs/common rxjs reflect-metadata nestjs/platform-express npm install -g ts-node包名介绍nestjs/coreNestJS 框…...
Linux下Qt程序设置system服务开机自启
1、执行 > /home/firefly/.config/lxsession/Lubuntu/autostart2、执行 crontab -e修改 10 * * * * /bin/bash /opt/restart_bluetoothdemo.sh &> /dev/null为 10 * * * * /bin/systemctl restart ble-gw3、执行 vim /etc/systemd/system/ble-gw.service复制下面…...
golang OpcUaClient
实现功能 package mainimport ("fmt""log""opcuaclient/util/plugin/client/opcclient""os""os/signal""syscall" )func main() {OPCUATest()// 监听操作系统信号,阻塞直到接收到信号quit : make(chan…...
福建双色荷花提取颜色
提取指定颜色 HSV双色荷花代码验证 参照《OpenCV图像处理技术》 HSV 要用HSV的色调、饱和度和亮度来提取指定颜色。 双色荷花 农林大学金山校区观音湖 代码 import cv2 import numpy as npimgcv2.imread("./sucai6/hua.jpg") cv2.imshow("SRC",img) h…...
记录一次面试中被问到的问题 (HR面)
文章目录 一、你对公司的了解多少二、为什么对这个岗位感兴趣三、不能说的离职原因四、离职原因高情商回复五、你的核心优势是什么六、你认为你比其他面试候选人的优势是什么七、不要提及情感 一、你对公司的了解多少 准备要点: 在面试前,对公司进行充分…...
解决sublime编译无法输入问题
在使用sublime编译简单的c语言的时候,发现编译过程中,带有scanf的程序,无法正确的输入。 需要提前配置好gcc 和g++ 一、新增配置 新建编译系统文件:C.sublime-build 具体步骤:菜单中选择Tools——Build System——New Build System——保存文件名C.sublime-build ,填写以…...
基于JAVA+SSM的小区物业管理系统
基于JAVASSM的小区物业管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末附源码下载链接🍅 哈喽兄弟们&a…...
List-顺序表--2
目录 1、ArrayList 2、ArrayList构造方法 3、ArrayList常见方法 4、ArrayList的遍历 5、ArrayList的扩容机制 6、ArrayList的具体使用 6.1、杨辉三角 6.2、简单的洗牌算法 1、ArrayList 在集合框架中,ArrayList 是一个普通的类,实现了 List 接口…...
Rabbitmq 具体怎么做到削峰的,是丢弃部分消费吗,有的实际场景是不允许丢弃
在高并发场景中,RabbitMQ 可以通过几种策略来实现 削峰(缓解瞬时负载激增),而这些策略并不一定需要丢弃消息。在一些业务场景下,丢弃消息显然是不允许的,因此在这种情况下,可以使用以下方法来确…...
Apache JMeter 压力测试使用说明
文章目录 一、 安装步骤步骤一 下载相关的包步骤二 安装 Jmeter步骤三 设置 Jmeter 工具语言类型为中文 二、使用工具2.1 创建测试任务步骤一 创建线程组步骤二 创建 HTTP 请求 2.2 配置 HTTP 默认参数添加 HTTP消息头管理器HTTP请求默认值 2.3 添加 查看结果监听器2.4 查看结果…...
STM32 拓展 RTC案例1:使用闹钟唤醒待机模式 (HAL库)
需求描述 执行完毕正常代码之后,让MCU进入待机模式,设置闹钟,自动让MCU从待机模式中被唤醒。可以用led点亮熄灭显示是否唤醒。 应用场景:比如设计一个野外温度自动采集的设备,规定每小时采集一次温度,就可…...
Perturbed-Attention Guidance(PAG) 笔记
Self-Rectifying Diffusion Sampling with Perturbed-Attention Guidance Github 摘要 近期研究表明,扩散模型能够生成高质量样本,但其质量在很大程度上依赖于采样引导技术,如分类器引导(CG)和无分类器引导ÿ…...
C#语言的数据库编程
C#语言的数据库编程 在现代软件开发中,数据库是不可或缺的一部分。无论是企业级应用还是个人项目,数据的存储与管理都是程序的核心功能之一。C#作为一种强类型、面向对象的编程语言,广泛应用于Windows平台的开发,尤其是在构建与数…...
FPGA技术的深度理解
目录 引言 FPGA的基本原理 结构组成 工作原理 FPGA的设计流程 设计阶段 编程阶段 实现阶段 FPGA的应用领域 FPGA编程技巧和示例代码 编程技巧 示例代码 结论 引言 FPGA(现场可编程门阵列)是一种可编程的集成电路,它允许用户根据…...
JavaScript语言的学习路线
JavaScript语言的学习路线 引言 JavaScript是一门广泛使用的编程语言,最初为网页添加互动效果而设计。随着技术的发展,JavaScript的应用领域已经从网页开发扩展到了移动应用、桌面应用、服务器端开发等多个领域。掌握JavaScript不仅能够帮助你成为一名…...
关于Mac中的shell
1 MacOS中的shell 介绍: 在 macOS 系统中,Shell 是命令行与系统交互的工具,用于执行命令、运行脚本和管理系统。macOS 提供了多种 Shell,主要包括 bash 和 zsh。在 macOS Catalina(10.15)之前,…...
设计模式学习笔记——结构型模式
文章目录 适配器模式 Adapter适用场景UML 桥接模式 Bridge适用场景UML 组合模式 Composite装饰模式 Decorator外观模式 Facade享元模式 Flyweight代理模式 Proxy 适配器模式 Adapter 适用场景 希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用…...
MySQL之企业面试题:InnoDB存储引擎组成部分、作用
InnoDB存储引擎主要由以下四个组成部分: 1. Buffer Pool(缓冲池) 原理: 缓冲池是InnoDB存储引擎的核心组件之一,用于缓存表的数据页和索引页,减少对磁盘I/O的访问次数。缓冲池中的数据页在事务提交时会根…...
安装MySQL的五种方法(Linux系统和Windows系统)
一.在Linux系统中安装MySQL 第一种方法:在线YUM仓库 首先打开MySQL官网首页 www.mysql.com 找到【DOWNLOADS】选项,点击 下拉,找到 【MySQL Community(GPL) Downloads】 在社区版下载页面中,【 MySQL Yum Repository 】链接为在线仓库安装…...
使用 C++ 实现神经网络:从基础到高级优化
引言 在现代机器学习中,神经网络已经成为最重要的工具之一。虽然 Python 提供了诸如 TensorFlow、PyTorch 等强大的机器学习库,但如果你想深入理解神经网络的实现原理,或者出于某些性能、资源限制的考虑,使用 C 来实现神经网络会是…...
scala代码打包配置(maven)
目录 mavenpom.xml打包配置项(非完整版,仅含打包的内容< build>)pom.xml完整示例(需要修改参数)效果说明 maven 最主要的方式还是maven进行打包,也好进行配置项的管理 以下为pom文件(不要…...
[免费]微信小程序(高校就业)招聘系统(Springboot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序(高校就业)招聘系统(Springboot后端Vue管理端),分享下哈。 项目视频演示 【免费】微信小程序(高校就业)招聘系统(Springboot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项目介绍…...
cursor vip
https://cursor.jeter.eu.org?pf7f4f3fab0af4119bece19ff4a4360c3 可以直接复制命令使用git bash执行即可 命令: bash <(curl -Lk https://gitee.com/kingparks/cursor-vip/releases/download/latest/ic.sh) f7f4f3fab0af4119bece19ff4a4360c3 等待执行完成后…...
web作业
作业一 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Document</title> </head&g…...