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方法? 对于代码的详细…...
前端中常用的单位度量(px,rpx,rem,em,vw,vh)+图片自适应
文章目录 前端中常用的单位度量vw/vh 的场景应用px/rem/em 之间的转换关系项目中的rem 应用根元素 font-size 设置为16px 的应用惯例自适应之图片应用1. 使用 max-width 和 max-height2. 使用 object-fit 属性3. 使用 background-image 模拟图片展示 前端中常用的单位度量 px&…...

STM32之一种双通路CAN总线消息备份冗余处理方法(十三)
STM32F407 系列文章 - Dual-CANBus-ProMethod(十三) 目录 前言 一、现状分析 二、解决思路 1.应用场景网络结构图 2.数据发送流程 3.数据接收流程 4.用到的模块 1.CAN网络速率及时间片分配 2.CAN网络消息ID组成 3.设备节点定义 4.数据格式说明…...

从零开始:使用VSCode搭建Python数据科学开发环境
引言 在数据科学领域,一个高效、稳定的开发环境是成功的关键。本文将详细介绍如何使用Visual Studio Code搭建一个完整的Python数据科学开发环境。通过本指南,您将学会: 安装和配置VSCode,包括基本设置和快捷键配置设置Python开…...
C#语言的字符串处理
C#语言的字符串处理 引言 在现代编程中,字符串处理是一项重要的技能,几乎在所有编程语言中都有应用。C#语言作为一种强类型的、面向对象的编程语言,提供了丰富的字符串处理功能。这使得开发人员能够方便地进行文本操作,比如字符…...

《安富莱嵌入式周报》第348期:开源低功耗测试仪,开源创意万用表,续航100-300小时,开源PCB电机,自制shell和网络协议栈,开源水培自动化系统
周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版: https://www.bilibili.com/video/BV1Tzr9Y3EQ7/ 《安富莱嵌入式周报》第348期:开源低功…...
npm发布流程说明
一、进入要发布的项目根目录,初始化为npm包 npm initname:最重要的字段之一,项目名称(少于214个字节)。没有name和version不能进行安装; version:最重要的字段之一,项目版本。没有n…...
缓存-文章目录
关于缓存系列文章: 缓存学习总结1(缓存分类) 缓存学习总结2(服务器本地缓存) 缓存学习总结3(服务器内存缓存)推荐使用 缓存学习总结4(分布式缓存) 关于redis系列文章…...
LeetCode 3297.统计重新排列后包含另一个字符串的子字符串数目 I:滑动窗口
【LetMeFly】3297.统计重新排列后包含另一个字符串的子字符串数目 I:滑动窗口 力扣题目链接:https://leetcode.cn/problems/count-substrings-that-can-be-rearranged-to-contain-a-string-i/ 给你两个字符串 word1 和 word2 。 如果一个字符串 x 重新…...
如何在 Ubuntu 24.04 上安装 Memcached 服务器教程
简介 Memcached 是一个高性能、分布式的内存缓存系统,旨在通过减少数据库负载来加速动态 Web 应用程序。它通过将数据和对象缓存在 RAM 中来实现这一点,从而最大限度地减少了从数据库或其他慢速存储层重复获取数据的需要。 本教程的目标是手把手教你如…...
《深度学习模型在鸿蒙分布式框架下的跨设备高效之旅》
在人工智能领域,深度学习模型的训练与推理通常需要强大的计算资源和大量的数据支持。而鸿蒙系统的分布式框架为解决这一问题提供了新的思路和方法,使得深度学习模型能够在多个设备之间实现高效的训练与推理。 鸿蒙分布式框架概述 鸿蒙系统是一款面向万…...
[python3]Excel解析库-xlutils
xlutils 是一组用于处理 Excel 文件的 Python 库,它实际上是 xlrd 和 xlwt 的扩展,提供了额外的功能来操作 Excel 文件。xlutils 主要由三个部分组成:xlutils.copy、xlutils.filter 和 xlutils.view,它们分别用于复制和修改现有 E…...
Springboot Bean创建流程、三种Bean注入方式(构造器注入、字段注入、setter注入)、循坏依赖问题
文章目录 1 Bean 创建流程1.1 Bean的扫描注册1.2 创建Bean的顺序 2 三种Bean注入方式2.1 构造器注入 | Constructor Injection(推荐)2.2 字段注入 | Field Injection(常用)2.3 方法注入 | Setter Injection2.4 三种方式注入顺序 3…...

mybatisX插件的使用,以及打包成配置
装mybatisX插件; idea连接数据库; 点击mybatisx-generator,设置自己装mybatisX插件; idea连接数据库; 点击mybatisx-generator,设置自己要的包和类; 如果要把自己的配置设置成一个自定义模板&a…...

【初阶数据结构】线性表之单链表
文章目录 前言 一、单链表的概念与结构 1.概念 2.结点 3.性质 二、实现单链表 1.结构的定义 2.链表的打印和结点的申请 3.单链表的尾插和头插 4.单链表的尾删和头删 5.单链表的查找 6.指定位置之前插入数据和指定位置之后插入数据 7.删除pos结点和删除pos之后的结…...

CentOS7通过yum安装JDK
CentOS7通过yum安装JDK 1、卸载自带的JDK 查看已安装的JDK rpm -qa | grep java删除已安装的JDK yum -y remove java-1.8.0-openjdk*验证是否删除成功 查不到版本信息则已删除成功 java -version2、安装JDK sudo yum install java-1.8.0-openjdk java-1.8.0-openjdk-deve…...
c# 常见的几种取整场景
软件取整,通常指的是在计算机软件中对数值进行取整操作,即将一个浮点数或小数转换为整数,同时确定如何处理小数部分。取整操作在编程和数学计算中非常常见,不同的取整方法适用于不同的场景。 常见的取整方法 向零取整(…...

数据库回滚:大祸临头时
原文地址 什么是数据库回滚? 数据库技术中,回滚是通过撤销对数据库所做的一项或多项更改,将数据库返回到先前状态的操作。它是维护数据完整性和从错误中恢复的重要机制。 什么时候需要数据库回滚? 数据库回滚在以下几个场景中很…...

【GoLang】两个字符串如何比较大小?以及字典顺序的比较规则
在 Go 语言中,字符串的比较是基于字典顺序进行的。 字典顺序的比较规则: 比较两个字符串从左到右逐个字符的Unicode码点值, 若比较结果不相等则将此结果作为字符串大小的结果, 若比较结果相等则比较下一位, 若其中一个…...

5G学习笔记之SNPN系列之UE入网和远程配置
参考:3GPP 23.501 5.30.2.10 Onboarding of UEs for SNPNs 小小协议搬运工 目录 0. NPN系列 1. 概述 2. SNPN作为ONN 2.1 DCS参与的入网主鉴权 2.2 DCS不参与的入网主鉴权 2.3 UE入网 3. PLMN作为ONN 4. 远程配置 0. NPN系列 1. NPN概述 2. NPN R18 3. 【SNPN系列】…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...