黑马点评秒杀优化
异步优化秒杀业务
回顾之前的内容黑马点评 秒杀优惠券集群下一人一单超卖问题-CSDN博客,为了处理并发情况下的线程安全和数据一致性的问题,我们已经完成了查询优惠券信息、判断秒杀是否开始和结束、检查库存、用户ID加锁、创建订单和扣减库存。
尽管之前的代码已经解决了基础问题,但在高并发情景下仍有瓶颈,接下来就需要对性能和稳定性做进一步的提升。所以接下来将使用异步优化秒杀业务。
为什么需要异步优化
1. 同步阻塞导致吞吐量低
-
问题:用户请求需等待所有数据库操作完成(如订单写入、库存扣减),线程长时间阻塞,系统吞吐量受限。
-
优化目标:
-
立即响应:用户请求快速返回,耗时操作异步执行。
-
提升吞吐量:将数据库压力转移至后台,Tomcat 线程快速释放,可处理更多请求。
-
2. 数据库成为性能瓶颈
-
问题:每次请求直接操作数据库,高并发时连接池耗尽、慢查询堆积。
-
优化目标:
-
削峰填谷:通过消息队列缓存请求,平滑流量高峰。
-
异步写库:批量处理订单,减少事务提交次数,降低数据库负载。
-
3. 用户体验差
-
问题:用户需等待秒杀所有逻辑完成(通常200ms~1s),高并发时易超时或失败。
-
优化目标:
-
快速反馈:用户点击后立即返回“请求已受理”,订单状态后续异步通知(如短信或站内信)。
-
补充:
同步:任务按顺序执行,必须等待前一个任务完成后,才能开始下一个任务。
问题:
所有操作都在同一个线程中完成,用户必须等待所有步骤结束才能得到响应。
高并发时,数据库压力大,响应时间变长,用户体验差。
异步:任务提交后立即返回,后续操作由其他线程或服务在后台处理。
优势:
用户无需等待数据库操作完成,响应速度极快(毫秒级)。
数据库压力被削峰填谷,避免高并发直接冲击。
优化任务如下:
阻塞队列Java BlockingQueue
1. 定义:阻塞队列是一种线程安全的数据结构,能平衡生产者与消费者的处理速度,避免资源耗尽或数据丢失。它支持以下操作:
- 入队(Put):队列满时,生产者线程会被阻塞,直到队列有空位。
- 出队(Take):队列空时,消费者线程会被阻塞,直到队列有新数据。
2. 在秒杀业务中的使用:
- 生产者:Tomcat 服务,快速校验后将订单任务写入队列。
- 消费者:后台线程或服务,从队列中取出任务并处理(扣库存、写订单)。
seckill.lua:
-- 1.参数列表
-- 优惠券id
local voucherId = ARGV[1]
-- 用户id
local userId = ARGV[2]-- 2.数据key
-- 库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 订单key
local orderKey = 'seckill:order:' .. voucherId-- 3.脚本业务逻辑
-- 判断库存是否充足 get stockKey
if(tonumber(redis.call('get', stockKey)) <= 0) then-- 库存不足,返回1return 1
end
-- 判断用户是否已下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then-- 用户重复下单,返回2return 2
end-- 扣减库存,并判断扣减是否成功 INCRBY stockKey -1
redis.call("incrby", stockKey, -1)
-- 下单,保存用户id到集合 SADD orderKey userId
redis.call("sadd", orderKey, userId)
return 0
改造后的VoucherServiceImpl.java:
@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate RedissonClient redissonClient;/*** 加载Lua脚本:执行脚本保证原子性操作*/private static final DefaultRedisScript<Long> SECKILL_SCRIPT;//静态初始化块static {SECKILL_SCRIPT = new DefaultRedisScript<>(); //创建实例SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua")); //设置脚本文件位置SECKILL_SCRIPT.setResultType(Long.class); //设置返回类型为Long}/*** 创建阻塞队列:异步下单,提高效率*/private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);/*** 创建单线程线程池*/private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();/*** 在VoucherServiceImpl类初始化时执行线程池*/@PostConstructprivate void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}/*** 线程池异步执行优惠券订单任务*/private class VoucherOrderHandler implements Runnable {@Overridepublic void run() {while (true) {try {//1.获取阻塞队列中的订单信息VoucherOrder voucherOrder = orderTasks.take();//2.创建订单handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error("处理订单异常", e);}}}}/*** 创建订单** @param voucherOrder*/private void handleVoucherOrder(VoucherOrder voucherOrder) {//1.获取用户id,这里是多线程不能在ThreadLocal中直接获得当前用户idLong userId = voucherOrder.getUserId();//2.创建锁对象(锁键设计:"lock:order:" + userId 以用户ID为粒度,不同用户请求可并行)RLock lock = redissonClient.getLock(LOCK_ORDER_KEY + userId);//3.获取锁(无参表示只尝试获取锁一次)boolean isLock = lock.tryLock();//4.判断获取锁是否成功if (!isLock) {// 获取锁失败,直接返回失败或者重试log.error("不允许重复下单");return;}try {//获取锁成功,创建订单proxy.createVoucherOrder(voucherOrder);} finally {//释放锁lock.unlock();}}private IVoucherOrderService proxy;/*** 抢购秒杀优惠券** @param voucherId* @return*/public Result seckillVoucher(Long voucherId) {//获取用户idLong userId = UserHolder.getUser().getId();//1.执行lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString());//2.判断结果是否为0//2.1不为0,代表没有抢购资格// result为1表示库存不足,result为2表示用户已下单int r = result.intValue();if (r != 0) {switch (r) {case 1:return Result.fail("库存不足");case 2:return Result.fail("不能重复下单");/*case 3:return Result.fail("活动结束");*/}}//2.2 result为0,用户具有秒杀资格,将订单信息(订单id,优惠券id,用户id)保存到阻塞队列中,实现异步下单//3.创建订单(在订单表tb_voucher_order插入一条数据)VoucherOrder voucherOrder = new VoucherOrder();//3.1 订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//3.2 用户idvoucherOrder.setUserId(userId);//3.3 代金券idvoucherOrder.setVoucherId(voucherId);// 3.4放入阻塞队列orderTasks.add(voucherOrder);//4.获取代理对象,使用代理对象调用第三方事务方法,防止事务失效proxy = (IVoucherOrderService) AopContext.currentProxy(); //获取当前类的代理对象//5.返回订单idreturn Result.ok(orderId);}/*** 通过数据库查询确保“一人一单”** @param voucherOrder*/@Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {//5.一人一单Long userId = voucherOrder.getUserId();//5.1查询数据库中是否已经存在该用户抢购该优惠券的订单int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();//5.2判断是否存在if (count > 0) {//用户已经购买过了,返回失败信息log.error("用户已购买!");return;}//6.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1") //set stock = stock - 1.eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0) //where id = ? and stock > 0 数据库层面的乐观锁,避免超卖.update();if (!success) {//库存扣减失败log.error("库存不足!");return;}//7.创建订单(在订单表tb_voucher_order插入一条数据)//插入到订单信息表save(voucherOrder);}
}
使用阻塞队列存在的问题
1. 内存限制与数据丢失风险
-
内存溢出(OOM):
阻塞队列存储在JVM内存中,当瞬时流量极大(如百万级请求),队列可能迅速占满内存,导致服务崩溃。 -
数据丢失:
若服务宕机或重启,内存中的队列数据全部丢失,未处理的订单请求无法恢复。
2. 单机瓶颈
-
无法水平扩展:
阻塞队列基于单机内存,无法跨多台服务器共享。当单机处理能力不足时,无法通过增加节点扩展吞吐量。 -
消费者能力受限:
消费者线程池受限于单机CPU和内存资源,无法灵活应对流量高峰。
3. 可靠性不足
-
无持久化机制:
消息仅存在于内存,缺乏磁盘持久化,无法应对服务崩溃或断电。 -
缺乏重试与死信处理:
若消息处理失败(如数据库异常),无法自动重试或转移至死信队列,需手动实现补偿逻辑。
消息队列优化
消息队列是一种在分布式系统中用于在不同组件或服务之间传递消息的中间件。其核心机制是生产者将消息发送到队列,消费者从队列中取出消息进行处理。这种模式实现了系统间的异步通信和解耦。
Redis消息队列
Redis不仅是一个高性能的内存数据库,还提供了多种数据结构(如List、Pub/Sub、Stream)来实现轻量级的消息队列。
1. 基于List的简单队列
- 核心命令:
- LPUSH / RPUSH:生产者向列表左/右侧插入消息。
- BRPOP / BLPOP:消费者阻塞式从列表右/左侧取出消息。
- 特点:
- 简单高效,支持阻塞等待。
- 消息只能被消费一次,无确认机制(消息取出后即删除,若处理失败会丢失)。
- 示例:
# 生产者发送消息
LPUSH my_queue "message1"# 消费者阻塞等待消息(超时时间10秒)
BRPOP my_queue 10
2. 发布订阅模式(Pub/Sub)
- 核心命令:
- PUBLISH:发布消息到频道。
- SUBSCRIBE:订阅频道接收消息。
- 特点:
- 支持广播,实时性高。
- 消息无持久化(订阅者离线时丢失消息),无法回溯历史消息。
- 示例:
# 订阅者订阅频道
SUBSCRIBE my_channel# 生产者发布消息
PUBLISH my_channel "Hello, World!"
3. 基于Streams的可靠队列(推荐)
Redis 5.0+ 支持 Streams 数据类型,提供更完整的消息队列功能:
消息持久化:消息存储在 Stream 中,可重复读取。
消费者组(Consumer Group):支持多消费者协同处理,消息确认机制(ACK)。
Pending Entries List (PEL):跟踪处理中的消息,避免丢失。
- 核心命令:
- XADD:添加消息到 Stream。
- XREADGROUP:消费者组读取消息。
- XACK:确认消息处理完成。
- 示例:
# 创建消息流
XADD mystream * field1 "value1" field2 "value2"# 创建消费者组
XGROUP CREATE mystream mygroup 0# 消费者从组内读取消息(阻塞模式)
XREADGROUP GROUP mygroup consumer1 BLOCK 1000 COUNT 1 STREAMS mystream ># 处理完成后确认消息
XACK mystream mygroup 1629164893000-0
3.1 单消费者模式(Single Consumer)
- 定义:单个消费者直接从一个 Stream 中读取消息,每条消息只能被消费一次。
- 特点:
- 独立消费:消费者通过
XREAD
命令主动拉取消息。 - 无状态管理:需手动记录已处理消息的ID,避免重复消费。
- 简单轻量:适合单实例、低并发的场景。
- 独立消费:消费者通过
- 潜在问题:
- 消息重复消费:若消费者崩塌,重启后需手动记录上次处理的位置(通过LASTID)
- 无负载均衡:无法通过多实例提升处理能力。
- 示例:
# 生产者发送消息到 Stream
XADD mystream * field1 "value1" field2 "value2"# 消费者读取消息(阻塞式,最多读取2条)
XREAD COUNT 2 BLOCK 5000 STREAMS mystream 0
# 输出:
# 1) 1) "mystream"
# 2) 1) 1) "1678459545000-0"
# 2) 1) "field1" 2) "value1" 3) "field2" 4) "value2"
3.2 消费者组模式(Consumer Group)
- 定义:多个消费者组成一个逻辑组,协同消费同一个 Stream 中的消息。每条消息仅被组内一个消费者处理。
- 核心机制:
- 消息分配:Redis 自动将消息分发给组内不同的消费者。
- 故障转移:若某个消费者宕机,未确认(Pending)的消息会被重新分配给其他消费者。
- 消息确认(ACK):消费者处理完成后需发送ACK,否则消息会重新入队。
- 核心优势:
- 负载均衡:组内多个消费者并行处理消息,提升吞吐量。
- 消息可靠性:通过 ACK 机制和 Pending Entries List(PEL)确保消息必达。
- 自动重试:未确认的消息会被重新分配给其他消费者。
- 示例:
# 创建消费者组(关联到 Stream)
XGROUP CREATE mystream mygroup 0# 消费者加入组并消费消息(消费者1)
XREADGROUP GROUP mygroup consumer1 COUNT 1 BLOCK 5000 STREAMS mystream >
# 输出:
# 1) 1) "mystream"
# 2) 1) 1) "1678459545000-0"
# 2) 1) "field1" 2) "value1" 3) "field2" 4) "value2"# 处理完成后发送ACK确认
XACK mystream mygroup 1678459545000-0# 另一个消费者加入组(消费者2)
XREADGROUP GROUP mygroup consumer2 COUNT 1 BLOCK 5000 STREAMS mystream >
消费者组配置示例
# 创建消费者组(若Stream不存在则自动创建)
XGROUP CREATE mystream mygroup $ MKSTREAM# 消费者1加入组并消费消息
XREADGROUP GROUP mygroup consumer1 COUNT 10 BLOCK 5000 STREAMS mystream ># 消费者2加入组并消费消息
XREADGROUP GROUP mygroup consumer2 COUNT 10 BLOCK 5000 STREAMS mystream ># 查看未确认的消息(Pending List)
XPENDING mystream mygroup
实现业务:
修改代码:
1. 在redis中创建消息队列
# 创建队列(消费者组模式)
XGROUP CREATE stream.orders g1 0 MKSTREAM
2. 修改lua脚本
-- 1.参数列表
-- 优惠券id
local voucherId = ARGV[1]
-- 用户id
local userId = ARGV[2]
-- 订单id
local orderId = ARGV[3]-- 2.数据key
-- 库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 订单key
local orderKey = 'seckill:order:' .. voucherId-- 3.脚本业务逻辑
-- 判断库存是否充足 get stockKey
if(tonumber(redis.call('get', stockKey)) <= 0) then-- 库存不足,返回1return 1
end
-- 判断用户是否已下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then-- 用户重复下单,返回2return 2
end-- 扣减库存,并判断扣减是否成功 INCRBY stockKey -1
redis.call("incrby", stockKey, -1)
-- 下单,保存用户id到集合 SADD orderKey userId
redis.call("sadd", orderKey, userId)
-- 发送消息到队列中 XADD stream.orders * k1 v1 k2 v2...
redis.call("xadd", "stream.orders", "*", "userId", userId, "voucherId", voucherId, "id", orderId)
return 0
3.修改业务代码,实现消息队列异步优化
/*** 优惠券订单任务处理线程:不断从消息队列中获取消息,并创建订单*/private class VoucherOrderHandler implements Runnable {/*** 队列名称*/String queueName = "stream.orders";@Overridepublic void run() {while (true) {try {//1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.orders >(‘>’代表未消费)List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(queueName, ReadOffset.lastConsumed()));//2.判断消息获取是否成功if (list == null || list.isEmpty()) {//2.1如果获取失败,说明没有消息,继续下一次循环获取消息continue;}//3.如果获取成功,解析消息中的订单信息//将消息转成VoucherOrder对象MapRecord<String, Object, Object> record = list.get(0);Map<Object, Object> values = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);//3.1 创建订单完成下单handleVoucherOrder(voucherOrder);//4.ACK确认 SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName, "g1", record.getId());} catch (Exception e) {//处理异常消息log.error("处理订单异常", e);handlePendingList();}}}/*** 处理pending-list中的消息*/private void handlePendingList() {while (true) {try {//1.获取pending-list中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 STREAMS stream.orders 0('0'代表已消费但未确认)List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1),StreamOffset.create(queueName, ReadOffset.from("0")));//2.判断消息获取是否成功if (list == null || list.isEmpty()) {//2.1如果获取失败,说明pending-list中没有异常消息,直接结束循环break;}//3.如果获取成功,解析消息中的订单信息//将消息转成VoucherOrder对象MapRecord<String, Object, Object> record = list.get(0);Map<Object, Object> values = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);//3.1 创建订单完成下单handleVoucherOrder(voucherOrder);//4.ACK确认 SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName, "g1", record.getId());} catch (Exception e) {//处理异常消息log.error("处理pending-list中的订单信息失败,{}", e.getMessage());try {Thread.sleep(20);} catch (InterruptedException ex) {ex.printStackTrace();}}}}}
相关文章:
黑马点评秒杀优化
异步优化秒杀业务 回顾之前的内容黑马点评 秒杀优惠券集群下一人一单超卖问题-CSDN博客,为了处理并发情况下的线程安全和数据一致性的问题,我们已经完成了查询优惠券信息、判断秒杀是否开始和结束、检查库存、用户ID加锁、创建订单和扣减库存。 尽管之前…...
宇树机器狗go2—slam建图(1)点云格式
0.前言 上一篇番外文章教大家如何在宇树机器狗go2的gazebo仿真环境中实现简单的导航运动,本期文章会教大家如何让宇树的机器狗go2在仿真环境中进行slam建图时经常会遇到的一些点云格式,在后续的slam建图和slam算法解析的时候会经常与这些点云信息打交道…...
支持中文对齐的命令行表格打印python库——tableprint
文章目录 快速入门 还在为表格中含有中文,命令行打印无法对齐而苦恼吗? 还在为冗长的数据添加代码而抓狂吗? tableprint来了!!!,它完美的解决了上述两个问题,快来试试吧!…...
力扣-hot100(无重复字符的最长子串)
3. 无重复字符的最长子串 中等 给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。暴力直观解法一࿱…...
javaNIO详解
Java NIO(Non-blocking I/O)详解 Java NIO(New I/O)是 Java 1.4 引入的高性能 I/O 框架,相比传统的 BIO(Blocking I/O),它提供了非阻塞、多路复用、零拷贝等能力,适用于…...
高精度算法(加、减、乘、除、阶乘和)
归纳编程学习的感悟, 记录奋斗路上的点滴, 希望能帮到一样刻苦的你! 如有不足欢迎指正! 共同学习交流! 🌎欢迎各位→点赞 👍 收藏⭐ 留言📝 唯有主动付出,才有丰富的果…...
在复杂性的迷宫里寻找路标 —— 读《人月神话》有感
初读《人月神话》时,正值参与的第一个大型项目陷入泥潭:需求像不断膨胀的气球,团队规模从 10 人扩充到 30 人,进度却像被灌了铅的钟表,指针越来越沉重。布鲁克斯在书中写下的 "向进度落后的项目增加人力ÿ…...
OpenFeign终极指南:超时控制、重试策略、拦截器与自定义Starter
目录 前言 使用 引入依赖 开启feign 编写feign客户端 效果 日志 超时配置 重试机制 拦截器 Fallback兜底返回 引入依赖 编写兜底实现 连接池 引入依赖 开启连接池 制作OpenFeign Starter 编写配置类 自动装配 前言 在RPC框架中,有openFeign和Du…...
如何使用flatten函数在Terraform 中迭代嵌套map
简介 flatten 接受一个列表,并用列表内容的扁平序列替换列表中的任何元素。 > flatten([["a", "b"], [], ["c"]]) ["a", "b", "c"] > flatten([[["a", "b"], []], [&quo…...
数字电子技术基础(五十)——硬件描述语言简介
目录 1 硬件描述语言简介 1.1 硬件描述语言简介 1.2 硬件编程语言的发展历史 1.3 两种硬件描述的比较 1.4 硬件描述语言的应用场景 1.5 基本程序结构 1.5.1 基本程序结构 1.5.2 基本语句和描述方法 1.5.3 仿真 1 硬件描述语言简介 1.1 硬件描述语言简介 硬件描述语…...
【PCIE配置空间】
1 PCIE配置空间 1.1 软件如何知道PCIE设备是Swith,RC还是EP? –软件通过读取寄存器信息。 PCIE配置空间• PCIE寄存器;--PCIE配置协议规定必须实现的空间。--PCIE存在两种配置空间Type0/Type1;--Type0配置空间EP设备必须实现;-…...
Python爬虫实战:获取网易新闻数据
一、引言 随着互联网的飞速发展,网络上蕴含着海量的信息资源。新闻数据作为其中的重要组成部分,对于舆情分析、市场研究、信息传播等多个领域具有重要价值。网易新闻作为国内知名的新闻平台,拥有丰富多样的新闻内容。使用 Python 的 Scrapy 框架进行网易新闻数据的爬取,可…...
哲学家就餐问题(避免死锁)
解决方案: 策略:奇偶哲学家拿筷子顺序不同,破坏循环等待。 流程: 偶数哲学家先左后右。 奇数哲学家先右后左。 分析: 无死锁,哲学家交替进餐,不同拿筷顺序避免循环等待。 实验总结 遇到的…...
下采样(Downsampling)
目录 1. 下采样的定义与作用 2. 常见下采样方法 (1) 池化(Pooling) (2) 跨步卷积(Strided Convolution) (3) 空间金字塔池化(SPP) 3. PyTorch 实现示例 …...
OrbisGIS:基于Java开发的开源GIS软件
大家好,今天为大家介绍的软件是OrbisGIS:一款基于JAVA开发的开源的地理信息系统软件。下面,我们将从软件的主要功能、支持的系统、软件官网等方面对其进行简单的介绍。 OrbisGIS官网网址为:http://orbisgis.org/,Orbis…...
linux socket编程之udp(实现客户端和服务端消息的发送和接收)
目录 一.创建socket套接字(服务器端) 二.bind将prot与端口号进行绑定(服务器端) 2.1填充sockaddr_in结构 2.2bind绑定端口 三.直接通信(服务器端) 3.1接收客户端发送的消息 3.2给客户端发送消息 四.客户端通信 4.1创建socket套接字 4.2客户端bind问题 4.3直接通信即可…...
八大排序之直接插入排序
今天我们来学习八大排序中的直接插入排序。话不多说,直接上动图。 直接插入排序的主要思想是:当数组只有一个元素的时候,我们可以认为该数组是有序的。所以我们可以选择一个元素放进数组(一般为待排数据的第一个元素)…...
二级评论列表-Java实现
二级评论列表是很常见的功能,文章记录了新手用Java实现的具体逻辑。 整体实现逻辑是先用2个sql,分别查出两层数据。然后用java在service中实现数据组装,返给前端。这种实现思路好处是SQL简洁,逻辑分明,便于维护。 一…...
如何基于区块链进行虚拟电厂运营平台建设?
本项目旨在基于区块链技术建设虚拟电厂运营平台,以提升省内大用户及工业企业和工业园区的需求响应能力,优化能源结构配置,并推动能源交易、需求响应和现货交易等新型业态的发展。通过建设虚拟电厂,项目将实现工业企业及园区各供用…...
大学英语四级选词填空练习题解析
一、引言 大学英语四级考试中的选词填空部分,一直是不少同学的 “拦路虎”。它不仅要求我们有扎实的词汇基础,还需要具备良好的语境分析能力。今天,我们就通过一道练习题来深入剖析这类题型的解题思路。 二、题目内容 Leisure activities …...
基于控制台的小车导航游戏开发详解(C++实现)
本文将详细讲解一个基于C控制台的小车导航游戏项目。通过该项目可以学习二维数组操作、队列数据结构应用以及游戏循环控制等核心编程概念,特别适合刚接触游戏开发的初学者学习。 一、项目概述 1.1 游戏规则 玩家可创建多辆具有不同初始位置和移动速度的小车 每辆…...
详讲Linux下进程等待
3.进程等待 引言:什么是进程等待 想象有两个小伙伴,一个是 “大强”(父进程 ),一个是 “小强”(子进程 )。大强给小强安排了任务,比如去收集一些石头。 …...
数字孪生火星探测车,星际探索可视化
运用图扑构建数字孪生火星探测车,高精度还原外观与内部构造。实时映射探测车在火星表面的移动、探测作业及设备状态,助力科研人员远程监测、分析数据,为火星探索任务提供可视化决策支持。...
《JVM考古现场(二十三):归零者·重启奇点的终极奥义》
目录 楔子:归零者文明觉醒 上卷十维弦理论破译 第一章:JVM弦论代码考古 第二章:超膜引用解析算法 第三章:量子真空涨落监控 中卷归零者心法实战 第四章:宇宙重启倒计时引擎 第五章:内存奇点锻造术 第…...
设计测试用例模板
面试时问你一个场景,要你设计测试用例,你会怎么回答? 面试官让你设计一个功能的测试用例,比如“上传文件功能”,其实就是想考你: 思维是否全面能不能抓住重点会不会分类和使用测试方法有没有考虑异常情况…...
java怎么找bug?Arthas原理与实战指南
Arthas原理与实战指南 1. Arthas简介 Arthas是阿里巴巴开源的Java诊断工具,其名字取自《魔兽世界》的人物阿尔萨斯。它面向线上问题定位,被广泛应用于性能分析、定位问题、安全审计等场景。Arthas的核心价值在于它能够在不修改应用代码、不重启Java进程…...
2024年国考
数学 一,逻辑符号表示(3 分) 1,只有获得奥运会资格的运动员才可以参加奥运会,参加奥运会的云动员不一定获奖。 设: ( Q(x) ):运动员 ( x ) 获得奥运会资格( P(x) ):运动员 ( x )…...
fastlio用mid360录制的bag包离线建图,提示消息类型错误
我用mid360录制的bag包,激光雷达的数据类型是sensor_msgs::PointCloud2,但是运行fast_lio中的mid360 launch文件,会报错(没截图),显示无法从livox_ros_driver2::CustomMsg转换到sensor_msgs::PointCloud2。…...
015-C语言字符函数和字符串函数
C语言字符函数和字符串函数 文章目录 C语言字符函数和字符串函数1. 字符分类函数2. 字符转换函数3. strlen4. strcpy5. strcat6. strcmp7. strncpy8. strncat9. strncmp10. strstr11. strtok12. strerror 1. 字符分类函数 C语言中有一系列函数是专门做字符分类的,也…...
docker 搭建nacos 2.2.1版本单机版
通过docker-compose搭建 services:nacos:image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nacos/nacos-server:v2.2.1container_name: nacosenvironment:- PREFER_HOST_MODEhostname- MODEstandalone- NACOS_AUTH_ENABLEtrue- NACOS_AUTH_IDENTITY_KEYxiaogang- N…...
stack,queue和priority_queue
1. stack 1.1 stack 的介绍 栈是一种容器适配器,专门设计用于LIFO环境(后进先出),其中元素仅从容器的一端插入和提取。 容器适配器,也就是使用特定容器类的封装对象作为其底层容器,提供一组特定的成员函…...
服务治理-搭建Nacos注册中心
运行nacos.sql文件。 将准备好的nacos目录和nacos.tar包上传。 192.168.59.101是我的虚拟机ip,8848是我们设置的访问端口号。...
《Operating System Concepts》阅读笔记:p738-p747
《Operating System Concepts》学习第 63 天,p738-p747 总结,总计 10 页。 一、技术总结 1.network structure local-area networks (LAN)、wide-area networks (WAN). 2.communication structure naming and name resolution、communication proto…...
【Datawhale AI春训营】Java选手初探数据竞赛
自然语言处理基础: 自然语言处理(Natural Language Processing,NLP) 是计算机科学与人工智能领域中的一个极具挑战性和应用前景的研究方向。 它旨在使计算机能够理解、生成和处理人类的自然语言,从而实现人机高效交互。 NLP技术在搜索引擎、…...
React-useRef
如果我们想在hooks里面获同步取最新的值,那么则可以使用useRef, 关键源码如下: function mountRef<T>(initialValue: T): {|current: T|} {const hook mountWorkInProgressHook();const ref {current: initialValue};hook.memoizedState ref;re…...
得物官网sign签名逆向分析
打开得物官网,点击鞋类,可以看到请求 直接搜sign function p(e) {return f()("".concat(e ? s()(e).sort().reduce(function(t, n) {return "".concat(t).concat(n).concat(e[n])}, "") : "", "048a9…...
网络爬虫和前端相关知识
一 爬虫发展历史,概念与反爬机制 (一)爬虫发展历史 早期爬虫(1990s)起源 :早期的爬虫主要是为了构建搜索引擎。典型案例: Yahoo!人工目录 → 谷歌PageRank算法驱动的自动化爬虫。功能特点 :这些爬虫的功能比较单一,主要以抓取网页的文本内容为主,采用简单的广度优先…...
山东科技大学人工智能原理考试回忆复习资料
全部资料:https://mbd.pub/o/around_01 考试回忆版 简答题 人工智能定义 图灵测试基本思想 人工智能主要研究领域写八个以上 专家系统是什么及其特点 遗传算法流程图 综合题 语义网络表示 书上习题 证明S不满足性 用归结原理求问题 谁说真话假话的 书上例题…...
单元测试的一般步骤
Qt Test Qt Test 是 Qt 开发人员发布的一个单元测试框架,用于测试基于 Qt 框架的应用程序或库。它提供了单元测试框架中常见的所有功能以及用于测试图形用户界面的扩展。 1.自动化测试包络ui测试>接口测试>单元测试;现问如何使用Qt进行单元测试&…...
c++题目_P1443 马的遍历
P1443 马的遍历 # P1443 马的遍历 ## 题目描述 有一个 $n \times m$ 的棋盘,在某个点 $(x, y)$ 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。 ## 输入格式 输入只有一行四个整数,分别为 $n, m, x, y$。 ## 输出格式 …...
并发网路通信-套接字通信
套接字通信就是网络通信 在网络通信时,客户端和服务器的比例是N:1 服务器如何处理多个客户端的请求 并发处理方式 1.多线程并发处理->线程池并发处理,线程池可以对多个线程进行管理 2.多进程->进程池 3.io多路转接,使用select或者epoch进行处理,使用io转接函数…...
STM32N6如何调试下载代码
关键词:STM32N6、调试代码、开发者模式、外部Flash模式 文章目录 前言一、开发者模式调试1.1 CubeIDE调试1.2 IAR调试 二、如何下载代码(外部FLASH模式)2.1 准备工作2.2 编译下载2.2.1 Appli编译头文件下载2.2.2 FSBL编译头文件下载 2.3 运行…...
【2025】Datawhale AI春训营-蛋白质预测(AI+生命科学)-Task2笔记
【2025】Datawhale AI春训营-蛋白质预测(AI生命科学)-Task2笔记 本文对Task2使用的代码进行理解。 任务描述 Task2的任务仍然是通过对反应中包含的蛋白质残基信息,运用深度学习模型构建蛋白质3D结构的隐式模型,从而达成准确预测…...
Python基于知识图谱的医疗问答系统【附源码、文档说明】
博主介绍:✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇&…...
Unreal 从入门到进阶 之 如何实现Pixel Streaming
文章目录 前言核心概念工作原理主要应用场景Pixel Streaming和Pixel Streaming2部署准备编辑器推流打包推流最后前言 Unreal Engine 5 (UE5) 的 Pixel Streaming 是一项基于云端的实时流媒体技术,允许将 UE5 应用的高质量图形渲染结果通过 WebRTC 协议传输到用户的浏览器或轻…...
作业2 CNN实现手写数字识别
# 导入必要库 import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 用于高级可视化 from tensorflow import keras from tensorflow.keras import layers from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import time # 用于…...
Linux驱动开发--阻塞、非阻塞I/O
2. 阻塞、非阻塞I/O IO 指的是 Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到…...
运筹学之模拟退火
目录 一、历史二、精髓思想三、案例与代码实现 一、历史 问:谁在什么时候提出模拟退火?答:模拟退火算法(Simulated Annealing,SA)是由斯图尔特柯尔斯基(Scott Kirkpatrick) 等人在 …...
JavaScript 的演变:2023-2025 年的新特性解析
随着Web技术的飞速发展,ECMAScript(简称ES)作为JavaScript的语言标准,也在不断进化。 本文将带你学习 ECMAScript 2023-2025 的新特性。 一、ECMAScript 2023 新特性 1.1 数组的扩展 Array.prototype.findLast()/Array.protot…...
CSS继承
CSS继承 CSS继承是一种机制,允许子元素自动继承父元素的某些样式属性,从而减少重复代码。 以下是一些常见的具有继承性的CSS属性: color : 文字颜色 font-family : 字体族名称 font-size : 字体大小 font-weight &am…...