Redis 7.x 系列【31】LUA 脚本
有道无术,术尚可求,有术无道,止于术。
本系列Redis 版本 7.2.5
源码地址:https://gitee.com/pearl-organization/study-redis-demo
文章目录
- 1. 概述
- 2. 常用命令
- 2.1 EVAL
- 2.2 SCRIPT LOAD
- 2.3 EVALSHA
- 2.4 SCRIPT FLUSH
- 2.5 其他
- 3. 脚本复制
- 4. 案例演示
- 4.1 限流
- 4.2 分布式锁
1. 概述
官方文档
Lua
语言是一种小巧的、高效的、可嵌入的脚本编程语言,由巴西里约热内卢天主教大学的一个研究小组于 1993
年开发。设计目的是为了通过灵活嵌入应用程序中,为应用程序提供灵活的扩展和定制功能。
参考资料:
- Lua 官网
- 菜鸟教程
Redis 2.6
版本开始支持用户在服务器上传和执行 Lua
脚本,从而实现复杂的操作逻辑。脚本在 Redis
中是以字符串的形式传递给服务器的,然后服务器会执行这些脚本并返回结果。脚本在 Redis
中由内置的执行引擎执行。目前,仅支持一个脚本引擎,即 Lua 5.1
解释器。
Lua
脚本的优势:
- 原子性:保证一个
Lua
脚本在执行期间是原子性的,即脚本执行期间不会被其他命令打断。在执行脚本期间,服务器的所有活动都会被阻塞,直到脚本运行结束。 - 减少网络开销:可以将多个命令打包成一个
Lua
脚本执行,减少与Redis
服务器的网络交互次数。 - 复用性:可以将复杂的逻辑编写成
Lua
脚本,然后在多个地方重复使用。 - 高效率:于脚本在服务器上执行,因此从脚本读取和写入数据非常高效。
注意事项:脚本被视为客户端应用程序的一部分,因此它们没有名称、版本或持久性。因此,如果脚本丢失(例如在服务器重新启动、故障切换到副本后等),所有脚本可能需要随时由应用程序重新加载。
2. 常用命令
2.1 EVAL
在服务端执行 Lua
脚本。
语法格式:
EVAL script numkeys [key [key ...]] [arg [arg ...]]
参数说明:
script
:Lua
编写的脚本源代码numkeys
:脚本中用到的key
的数量[key [key ...]]
:key
键名称列表,在脚本中可以使用KEYS[]
进行占位,例如EVAL
的第三个参数会被赋值给KEYS[1]
,多个依次类推[arg [arg ...]]
:参数列表,在脚本中可以使用ARGV[]
进行占位,例如EVAL
的第四个参数会被赋值给ARGV[1]
,多个依次类推
注意事项:
- 禁止滥用
Lua EVAL
,例如,每次调用EVAL
时生成不同的脚本。这些脚本将被添加到Lua
解释器并缓存到服务端,随着时间的推移会消耗大量内存。 - 从
Redis 7.4
开始,使用EVAL
或EVAL_RO
加载的脚本将根据一定数量(按最近最少使用顺序)从Redis
中删除。可以通过INFO
命令查看被驱逐的脚本数量。
为了确保脚本在单机和集群环境中正确执行,还需要注意:
Lua
脚本的操作应该基于传递给脚本的参数进行,而不应该直接访问存储中的数据结构内容,例如哈希表、列表或集合。- 不支持动态键名访问或基于数据内容的键名计算,因此所有访问的键必须在执行脚本时已经确定。这些输入键的名称作为
KEYS
全局运行时变量提供给Lua
脚本使用。
简单示例:
localhost:0>EVAL "return 'Hello, scripting!'" 0
"Hello, scripting!"
示例说明:
return 'Hello, scripting!'
:使用双引号包含的脚本源代码,返回一个字符串0
:没有使用到键
包含参数示例:
localhost:0>EVAL "return ARGV[1]" 0 Hello
"Hello"
示例说明:
0
:没有使用到键Hello
:将Hello
传递给ARGV[1]
包含键、参数示例:
redis> EVAL "return { KEYS[1], KEYS[2], ARGV[1], ARGV[2], ARGV[3] }" 2 key1 key2 arg1 arg2 arg3
1) "key1"
2) "key2"
3) "arg1"
4) "arg2"
5) "arg3"
示例说明:
2
:包含两个键key1
:将Hello
传递给KEYS[1]
,按照位置顺序依次类推arg1
:将arg1
传递给ARGV[1]
,按照位置顺序依次类推
可以通过 redis.call()
或 redis.pcall()
在 Lua
脚本中调用 Redis
命令,区别在于处理运行时错误(例如语法错误)的方式:
call()
:如果发生错误,错误会直接返回给执行它的客户端pcall()
:遇到的错误会返回到脚本的执行环境
示例:
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 foo bar
OK
2.2 SCRIPT LOAD
每次调用 EVAL
命令时,请求中都会包括脚本的源代码,重复调用来执行相同的脚本集会浪费网络带宽,并且在 Redis
中也会产生一些额外开销,因此 Redis
提供了脚本的缓存机制。
使用 SCRIPT LOAD
可以将 Lua
脚本加载到 Redis
服务器,服务器不会执行脚本,而是仅编译并加载到服务器的缓存中。加载后返回一个 SHA1
摘要,唯一标识了它在缓存中的位置,该摘要可以用于执行已加载的脚本。
语法格式:
SCRIPT LOAD script
注意事项:
- 脚本缓存始终是易失性的,它不被视为数据库的一部分,也不会持久化存储。当服务器重新启动、在故障转移时从副本切换为主服务器时,或者通过调用
SCRIPT FLUSH
命令时,缓存可能会被清空。 - 应用程序应首先用
SCRIPT LOAD
加载脚本,然后再次调用EVALSHA
运行缓存的脚本。大多数Redis
客户端已经提供了自动执行此过程的API
。
示例:
redis> SCRIPT LOAD "return 'Immabe a cached script'"
"c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f"
redis> EVALSHA c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f 0
"Immabe a cached script
2.3 EVALSHA
用于执行已加载的 Lua
脚本。
语法格式:
EVALSHA <sha1> <numkeys> <key> [key ...] <arg> [arg ...]
参数说明:
<sha1>
:之前已加载缓存的Lua
脚本的SHA1
值。
示例:
127.0.0.1:6379> SCRIPT LOAD "local key = KEYS[1]\nlocal value = ARGV[1]\n\nredis.call('SET', key, value)"
"5b405e1d1f5c91b27e7e2b091380a848d38a99d6"
127.0.0.1:6379> EVALSHA 5b405e1d1f5c91b27e7e2b091380a848d38a99d6 1 mykey "myvalue"
OK
注意事项:
SHA1
对应的脚本不存在时,会报错- 在使用管道时,尽量不要使用
EVALSHA
,管道请求中的命令按发送顺序执行,但其他客户端的命令可能会在这些命令之间交错执行。因此,可能会从管道请求中返回NOSCRIPT
错误,但无法进行处理。因此,在管道中应当使用普通的EVAL
或带参数的EVAL
。
2.4 SCRIPT FLUSH
清空 Lua
脚本缓存。默认情况下,SCRIPT FLUSH
命令会同步清空缓存。从 Redis 6.2
开始,设置 lazyfree-lazy-user-flush
配置指令为 “yes
” 将改变默认的清空模式为异步。
语法格式:
SCRIPT FLUSH [ASYNC | SYNC]
参数说明:
ASYNC
:异步清空缓存SYNC
:同步清空缓存
注意事项:
- 运行命令将完全清空脚本缓存,删除到目前为止执行的所有脚本。
- 在正常操作期间,脚本应该在缓存中无限期地保留。
示例:
localhost:0>SCRIPT FLUSH
"OK"
2.5 其他
其他脚本相关的命令:
EVAL_RO
:EVAL
命令的只读变体,不能执行修改数据的命令。EVALSHA_RO
:EVALSHA
命令的只读变体,不能执行修改数据的命令。SCRIPT DEBUG
:控制内置的Redis Lua
脚本调试器。SCRIPT EXISTS
:判断脚本已存在于缓存中,一个或多个SHA1
摘要作为参数。返回1
表示存在,0
不存在。SCRIPT KILL
:中断长时间运行脚本(即慢脚本)的唯一方式,除非关闭服务器。脚本在执行时间超过配置的最大执行时间阈值后被视为慢脚本。只能用于在执行期间未修改数据集的脚本(因为停止只读脚本不会违反脚本引擎的原子性保证)。
3. 脚本复制
集群部署环境下,至少有三个主节点,每个主节点有一个或多个从节点,主从之间使用完全复制保持数据一致性。由于脚本可以修改数据,Redis
确保脚本执行的所有写操作也会被发送到从节点以保持一致性。
脚本复制有以下两种方式:
- 逐字复制:主节点将脚本的源代码发送到副本,副本然后执行脚本并应用写入效果。
- 效果复制:只复制脚本的数据修改命令,副本随后运行这些命令而不执行任何脚本。
逐字复制模式意味着副本会重新执行主节点已完成的工作,这是一种浪费。更重要的是,它还要求所有写入脚本都是确定性的。效果复制模式虽然在网络流量方面可能更为冗长,但这种复制模式是确定性的,因此不需要特别考虑其他情况。
直到 Redis 3.2
版本之前,逐字脚本复制是唯一支持的模式,在 Redis 3.2
中添加了效果复制。在 Redis 5.0
中,效果复制成为默认模式,截至 Redis 7.0
,不再支持逐字复制。
在效果复制模式下,当 Lua
脚本执行时, Redis
会收集 Lua
脚本引擎实际修改数据集的所有命令。当脚本执行结束时,脚本生成的命令序列会被封装成一个 MULTI/EXEC
事务,并发送到副本和 AOF
。
效果复制的优势:
- 当脚本计算速度较慢,可以由少数写入命令替换时,重新在副本或重新加载
AOF
上计算脚本是一种浪费。在这种情况下,仅复制脚本的效果要好得多。 - 解除了非确定性函数的限制。例如,您可以在脚本中任意使用
TIME
或SRANDMEMBER
命令。 Lua
伪随机数生成器在每次调用时都是随机种子。
4. 案例演示
Redis
命令的执行是单线程的,Lua
脚本在执行期间是原子性的,并且可以同时执行多个命令。特别适用于多线程环境下,需要保证多个复杂操作的原子性的场景。
4.1 限流
演示需求: 基于固定时间窗口进行 IP
限流, 同一 IP
在一分钟内,只允许访问固定的次数。
在 resources
目录下添加限流脚本文件:
local ipKey = KEYS[1]; -- IP地址
local rate = tonumber(ARGV[1]); -- 允许的请求次数
local requestCount = redis.call('incr', ipKey); -- 每次请求 + 1
if (requestCount == 1) then -- 第一次请求redis.call('expire', ipKey, 60); -- 设置过期时间(60S)return true ; -- 返回是否允许访问
else -- 不是第一次请求if (requestCount > rate) then -- 如果当前请求次数大于允许的请求次数return false ;end
end
使用 Lettuce
客户端执行脚本:
public static void main(String[] args) {// 创建客户端RedisClient redisClient = RedisClient.create("redis://:123456@127.0.0.1:6379/0");// 获取连接try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {// 执行命令RedisCommands<String, String> sync = connection.sync();// 获取脚本流byte[] lua = getResourceAsByteArray("limit.lua"); // 执行脚本boolean eval = sync.eval(lua, ScriptOutputType.BOOLEAN, new String[]{"127.0.0.1"}, "1");// 结果判断if (!eval) {System.out.println("当前访问过于频繁,请稍后重试");} else {System.out.println("允许访问");}} catch (Exception e) {throw new RuntimeException(e);} finally {redisClient.shutdown();}}public static byte[] getResourceAsByteArray(String resourceName) throws IOException {// 使用ClassLoader获取资源作为输入流try (InputStream inputStream = LuaTest.class.getClassLoader().getResourceAsStream(resourceName)) {if (inputStream == null) {return null;}// 将输入流转换为字节数组ByteArrayOutputStream outputStream = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int length;while ((length = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, length);}return outputStream.toByteArray();}}
4.2 分布式锁
演示需求: 商品秒杀防止超卖,使用 Redis
+Lua
实现分布式锁(生产环境请使用成熟的框架)。
获取锁的命令:
SET key value NX EX expire_time
简单的实现原理:
- 使用
SET NX...
命令设置值,设置成功表示获取到的锁,反之未获取(直接返回失败或循环重试),并设置一个过期时间(避免死锁) - 获取到锁后,执行业务逻辑
- 执行完成后释放锁
在 resources
目录下添加尝试获取锁脚本文件 tryLock.lua
:
local lockKey = KEYS[1] -- 锁的key
local lockValue = ARGV[1] -- 锁的值
local lockExpireTime = tonumber(ARGV[2]) -- 锁的过期时间(秒)
local acquiredLock = redis.call('SET', lockKey, lockValue, 'NX', 'EX', lockExpireTime) -- 获取锁
if acquiredLock thenreturn true -- 获取锁成功
elsereturn false -- 获取锁失败
end
添加删除锁脚本文件 unlock.lua
:
local lockKey = KEYS[1] -- 锁的key
redis.call('DEL', lockKey) -- 删除锁
创建工具类封装相关分布式锁逻辑:
public class RedisLockUtils {/*** 尝试获取锁** @param sync 连接* @param key 锁的 Key* @param value 锁的值* @param second 过期时间* @return 是否获取到锁* @throws IOException*/public static boolean tryLock(RedisCommands<String, String> sync, String key, String value, String second) throws IOException {byte[] lua = RedisLockUtils.getResourceAsByteArray("tryLock.lua"); // 获取脚本流return sync.eval(lua, ScriptOutputType.BOOLEAN, new String[]{key}, value, second); // 执行脚本}/*** 删除锁** @param sync 连接* @param key 锁的 Key* @return 是否获取到锁* @throws IOException*/public static void unlock(RedisCommands<String, String> sync, String key) throws IOException {byte[] lua = RedisLockUtils.getResourceAsByteArray("unlock.lua");boolean eval = sync.eval(lua, ScriptOutputType.BOOLEAN, new String[]{key});}public static byte[] getResourceAsByteArray(String resourceName) throws IOException {// 使用ClassLoader获取资源作为输入流try (InputStream inputStream = LuaLimitTest.class.getClassLoader().getResourceAsStream(resourceName)) {if (inputStream == null) {return null;}// 将输入流转换为字节数组ByteArrayOutputStream outputStream = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int length;while ((length = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, length);}return outputStream.toByteArray();}}
}
模拟秒杀,简单测试:
public class LuaLockTest {private static Integer stockCount = 100; // 商品库存数量private static String goodsId = "899632563356632489"; // 商品库存数量public static void main(String[] args) {// 创建客户端RedisClient redisClient = RedisClient.create("redis://:123456@127.0.0.1:6379/0");// 获取连接try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {// 秒杀逻辑try {// 获取到锁if (RedisLockUtils.tryLock(connection.sync(), goodsId, "123456", "10")) {// 减库存if (stockCount > 0) {stockCount = stockCount - 1;System.out.println("减库存成功,剩余库存:" + stockCount);} else {System.out.println("库存不足");}} else {System.out.println("库存不足");}} finally {// 释放锁RedisLockUtils.unlock(connection.sync(), goodsId);}} catch (Exception e) {throw new RuntimeException(e);} finally {redisClient.shutdown();}}
}
相关文章:
Redis 7.x 系列【31】LUA 脚本
有道无术,术尚可求,有术无道,止于术。 本系列Redis 版本 7.2.5 源码地址:https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2. 常用命令2.1 EVAL2.2 SCRIPT LOAD2.3 EVALSHA2.4 SCRIPT FLUSH2.5 其他 3. …...

mysql中You can’t specify target table for update in FROM clause错误
mysql中You can’t specify target table for update in FROM clause错误 You cannot update a table and select directly from the same table in a subquery. mysql官网中有这句话,我们不能在一个语句中先在子查询中从某张表查出一些值,再update这张表…...
Linux Vim最全面的教程
Vim 是一个非常强大的文本编辑器,它在 Linux 环境中尤其受欢迎。Vim 支持高度定制,并且拥有丰富的功能,包括多级撤销、宏、脚本语言支持等。下面是关于 Vim 的一个较为全面的教程。 Vim 的启动 要启动 Vim,你可以在终端中输入 v…...

setsockopt选项对tcp速度
GPT-4 (OpenAI) 每个setsockopt调用都涉及到一个套接字描述符,一个指定网络层的常数(如IPPROTO_IP, IPPROTO_TCP, IPPROTO_IPV6, SOL_SOCKET等),一个指定需配置的选项的常数,一个指向配置值的指针,以及那个…...
HarmonyOS应用开发者高级认证,Next版本发布后最新题库 - 多选题序号3
基础认证题库请移步:HarmonyOS应用开发者基础认证题库 注:有读者反馈,题库的代码块比较多,打开文章时会卡死。所以笔者将题库拆分,单选题20个为一组,多选题10个为一组,题库目录如下,…...

bool数组的理解和应用[C++]
文章目录 bool数组的用法bool数组的定义声明bool数组的初始化访问和修改数组元素遍历数组 运用bool数组简单代码 在今天做题中发现了bool类不仅能用于函数类型还能用于数组类型,好奇查了查发现bool还有很多用处:基本变量,在枚举类型中会用到&…...

JavaScript模拟滑动手势
双击回到顶部 左滑动 右滑动 代码展示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Gesture…...

Text Control 控件教程:使用 .NET C# 中的二维码和条形码增强文档
QR 码和条形码非常适合为文档和 PDF 文件增加价值,因为它们提供轻松的信息访问、验证信息、跟踪项目和提高交互性。条形码可以弥补纸质或数字人类可读文档与网络门户或网络应用程序中的数字信息之间的差距。大多数用户都熟悉 QR 码和条形码,它们在许多过…...

最新爆火的开源AI项目 | LivePortrait 本地安装教程
LivePortrait 本地部署教程,强大且开源的可控人像AI视频生成 1,准备工作,本地下载代码并准备环境,运行命令前需安装git 以下操作不要安装在C盘和容量较小的硬盘,可以找个大点的硬盘装哟 2,需要安装FFmp…...

揭秘Django与Neo4j:构建智能知识图谱的终极指南
揭秘Django与Neo4j:构建智能知识图谱的终极指南 前言 图是一种用于对象之间的成对关系进行建模的数学结构。 它由两个主要元素组成:节点和关系。 节点:节点可以看作是传统数据库中的记录。每个节点代表一个对象或实体,例如一个人或一个地方。节点按标签分类,这有助于根…...

项目一缓存商品
文章目录 概要整体架构流程技术细节小结 概要 因为商品是经常被浏览的,所以数据库的访问量就问大大增加,造成负载过大影响性能,所以我们需要把商品缓存到redis当中,因为redis是存在内存中的,所以效率会比MySQL的快. 整体架构流程 技术细节 我们在缓存时需要保持数据的一致性所…...

SEO与数据中心代理IP的结合能带来哪些便利?
本文将探讨将SEO与数据中心代理IP结合所带来的好处,以及如何利用这种组合来提升网站在搜索引擎中的排名和可见性。 1. 数据中心代理IP的作用和优势 数据中心代理IP指的是由数据中心提供的IP地址,用于隐藏真实服务器的位置和身份。与其他类型的代理IP相…...

《昇思25天学习打卡营第6天|ResNet50图像分类》
写在前面 从本次开始,接触一些上层应用。 本次通过经典的模型,开始本次任务。这里开始学习resnet50网络模型,应该也会有resnet18,估计18的模型速度会更快一些。 resnet 通过对论文的结论进行展示,说明了模型的功能&…...
Activiti 6 兼容openGauss数据库bytes类型不匹配
当前有个项目需要做国产调研,需要适配高斯数据库,项目启动的时候,提示column "bytes_" is type bytea but expression is of type blob byte_字段是act_ge_bytearray表的,openGauss里的类型是bytea,类型是匹…...
缓存技术:提升性能与效率的利器
在当今数字化时代,软件应用的性能与响应速度成为了衡量其成功与否的重要标准之一。随着数据量的爆炸性增长和用户需求的日益多样化,如何高效地处理这些数据并快速响应用户请求成为了软件开发中亟待解决的问题。缓存技术,作为提升系统性能、优…...

LeetCode 637, 67, 399
文章目录 637. 二叉树的层平均值题目链接标签思路代码 67. 二进制求和题目链接标签思路代码 399. 除法求值题目链接标签思路导入value 属性find() 方法union() 方法query() 方法 代码 637. 二叉树的层平均值 题目链接 637. 二叉树的层平均值 标签 树 深度优先搜索 广度优先…...

如何压缩视频大小不改变画质?这5个视频压缩免费软件超好用!
如何压缩视频大小不改变画质?随着生活的水平逐步提高,视频流媒体服务越来越受欢迎。提供简短而引人注目的视频来展示您的产品或服务已成为一种出色的营销手段。然而,当您要准备导出最终视频时,可能会面临一个常见问题:…...

深入理解 Java 虚拟机第三版(周志明)
这次社招选的这本作为 JVM 资料查阅,记录一些重点 1. 虚拟机历史 Sun Classic VM :已退休 HotSpot VM:主流虚拟机,热点代码探测技术 Mobile / Embedded VM :移动端、嵌入式使用的虚拟机 2.2 运行时数据区域 程序计…...

算法 定长按组翻转链表
一、题目 已知一个链表的头部head,每k个结点为一组,按组翻转。要求返回翻转后的头部 k是一个正整数,它的值小于等于链表长度。如果节点总数不是k的整数倍,则剩余的结点保留原来的顺序。示例如下: (要求不…...

安装nfs和rpcbind设置linux服务器共享磁盘
1、安装nfs和rpcbind 1.1 检查服务器是否安装nfs和rpcbind,执行下命令,检查服务器是否安装过。 rpm -qa|grep nfs rpm -qa|grep rpcbind 说明服务器以安装了,如果没有就需要自己安装 2、安装nfs和rpcbind 将rpm安装包: libtirpc-…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...

【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...
【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?
FTP(File Transfer Protocol)本身是一个基于 TCP 的协议,理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况,主要原因包括: ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...