Redis Lua脚本的详细介绍以及使用入门
Redis Lua脚本的详细介绍以及使用入门。
文章目录
- Redis Lua脚本的引入
- 开源软件的可扩展性
- Redis的扩展性脚本
- Redis Lua脚本的基本使用
- 通过EVAL命令执行Lua脚本
- 通过脚本与Redis交互
- Java中调用Redis Lua脚本
- Java调用Lua脚本的方式
- Redis Lua脚本的使用建议
- 脚本缓存
- 脚本缓存稳定性
- 脚本参数化
- 脚本超时处理
- Redis集群中Lua脚本的使用
- 其他限制
- Redis 7 Functions
- Lua脚本相关常用命令
- 总结
- 参考
本次的主要分享主题是:Redis Lua脚本的使用入门,主要分为三个方面:
- Redis Lua脚本的介绍和基本使用语法。
- Java如何中调用Lua脚本操作Redis。
- Lua脚本的相关使用建议。
Redis Lua脚本的引入
开源软件的可扩展性
流行的开源软件或者框架,都会非常注重一个特性,那就是“可扩展性”,或者说“可编程性”。
例如Spring框架,就提供了非常多的扩展点,各种PostProcessor、Aware、Lifecycle等等扩展接口,让开发者能够自定义程序运行的逻辑。
又比如Dubbo,它的插件式架构的精髓,就在于开发者可以基于它的SPI机制任意的替换例如Protocol、LoadBalance等等几乎所有的Dubbo组件。
一个框架或者软件的可扩展性够强,往往意味着其足够的灵活,能够满足更多用户的定制化需求或者说二次开发的需求,进而影响到其在开源市场的占有率。
Redis的扩展性脚本
Redis作为最常见的缓存数据库,它的大多数命令专门用于以不同的方式操作不同的核心数据类型,这些命令并不能在一次调用过程中组合使用。
Redis中的可扩展性(可编程性),实际上就是指服务器能够执行任意用户定义的逻辑片段,我们把这样的逻辑片段称为脚本,Redis在2.6.0版本开始引入脚本的功能。
用户编写的脚本实际上在Redis中由一个嵌入式执行引擎执行。目前,Redis只支持一个脚本引擎,即Lua 5.1解释器,但是计划在未来支持更多的脚本引擎。
Redis Lua脚本的好处有很多,最为人熟知的亮点包括:
- 很多复杂的业务需要包含多步Redis操作,这些操作常被拆分为一个个单独的API方法。开发人员可以使用Redis脚本的功能实现一个健壮的、特定于应用程序的api。这样的api可以封装业务逻辑,并跨多个键和不同的数据结构维护数据模型。
- 复杂业务的多步Redis操作,一般情况下需要多次与Redis服务器交互,需要多次网络IO开销。使用一个Redis脚本来完成多步操作的封装,然后发送给Redis服务器执行,因为脚本在同一个服务器中执行,所以从脚本中读取和写入数据非常有效,仅需要少量的网络IO的开销,给我们带来更好的性能。
- Redis脚本和Redis命令一样,它被Redis服务器端单线程的执行,这样就具备了天然的原子性,常常可以被用于实现基于Redis的分布式锁。
Redis Lua脚本的基本使用
Lua 是一种轻量小巧的嵌入式脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入其他应用程序中,从而为应用程序提供灵活的扩展和定制功能。常见应用如游戏开发中的脚本。
关于Lua的基本语法,实际上比较的简单,在此不做赘述:。下面介绍如何在Redis中执行Lua脚本。
通过EVAL命令执行Lua脚本
我们可以使用EVAL命令在服务器上执行自定义Lua脚本。例如:
>EVAL "return 'Hello, scripting!'" 0
"Hello, scripting!"
该命令要求返回"Hello, scripting!"字符串。
EVAL命令的完整格式为:
EVAL script numkeys [key [key ...]] [arg [arg ...]]
- script:Lua脚本。
- numkeys:指定KEYS[]参数的数量,非负整数。
- KEYS[]:传入的Redis键参数。
- ARGV[]:传入的脚本参数。KEYS[]与ARGV[]的索引均从1开始。
在上面的案例中,我们使用参数值0,表示该Lua脚本需要0个KEYS参数。
Lua脚本中支持动态传递参数,执行上下文通过KEYS和ARGV全局运行时变量来传递使脚本使用的参数。KEYS表示预先填充了脚本执行前提供给脚本的所有键名参数,而ARGV则用于类似的目的,但通常用于预先填充常规参数。
注意,KEYS和ARGV数组的参数取值从1开始。
例如,一个较为完整的EVAL命令的格式例如:
>EVAL "return { KEYS[1], KEYS[2], ARGV[1], ARGV[2], ARGV[3] }" 2 key1 key2 arg1 arg2 arg31) "key1"2) "key2"3) "arg1"4) "arg2"5) "arg3"
该命令表示返回一个数组,内部的元素均来自于KEYS和ARGV数组参数。
通过脚本与Redis交互
redis单例是一个所有脚本中都可以访问的对象实例。它提供了从脚本与Redis交互的API。
在redis对象的函数中,最常见的调用的函数就是redis.call(),该函数将会调用给定的Redis命令并返回其应答,他的语法如下:
redis.call(command [,arg...])
- command:redis命令,必须。
- arg:动态传递的参数,可选。arg支持通过KEYS和ARGV动态传递。
例如,以下命令要求Redis服务端设置一个key为zhuzhan,value为v3的缓存:
>EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 zhuzhan v3
"OK"
以下命令要求从Redis服务端获取key为zhuzhan的缓存:
>EVAL "return redis.call('get', KEYS[1])" 1 zhuzhan
"v3"
更复杂一些的,我们实现“判断如果某个key不存在,则设置它”的功能,也就是常见的分布式锁获取锁:
>EVAl "return redis.call('set',KEYS[1], ARGV[1], ARGV[2], ARGV[3] , ARGV[4])" 1 lock lock nx px 60000
"OK"
然后我们实现“如果存在某个key并且值相等,则删除key”的功能,也就是分布式锁的解锁:
EVAL "if (redis.call('get',KEYS[1])==ARGV[1]) then return redis.call('DEL',KEYS[1])
end return 0"
1 x y"1"
更多操作参见Lua API:https://redis.io/docs/manual/programmability/lua-api/
Java中调用Redis Lua脚本
Java调用Lua脚本的方式
上面我们学习了在Redis客户端中直接执行Lua脚本的方法,但是更常见的,我们是直接通过Java客户端操作Redis,那么Java如何通过Lua脚本操作Redis呢?
实际上,通过Java客户端调用Lua脚本相比于命令行客户端更加简单。
比如最常见的Jedis客户端,我们可以调用jedis#eval方法执行Lua脚本,例如:
public static final String prefix = "test:";
public static final List KEYS = Collections.singletonList(prefix + "jedis");
public static final List ARGV = Stream.of("jedis", "nx", "px", "60000").collect(Collectors.toList());Object eval = jedis.eval("return redis.call('set',KEYS[1], ARGV[1], ARGV[2], ARGV[3] , ARGV[4])", KEYS, ARGV);
System.out.println(eval);
另外,如果是springboot项目,那么更加推荐使用redisTemplate客户端来执行Lua脚本。
- 首先我们需要创建一个DefaultRedisScript对象,该对象表示Lua脚本的抽象。
- 然后设置设置DefaultRedisScript的返回值类型,支持List、Boolean、Long、String类型,默认是返回String类型。
- 然后将Lua脚本代码可以直接设置进去,也可以选择从文件中加载。
- 最后通过redisTemplate#execute方法执行脚本获取结果。
DefaultRedisScript<Boolean> objectDefaultRedisScript = new DefaultRedisScript<>();
//返回值类型
objectDefaultRedisScript.setResultType(Boolean.class);
//直接设置Lua
//objectDefaultRedisScript.setScriptText("return redis.call('set',KEYS[1], ARGV[1], ARGV[2], ARGV[3] , ARGV[4])");
//设置Lua资源
objectDefaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/lock.lua")));
//执行脚本
Boolean execute = redisTemplate.execute(objectDefaultRedisScript, KEYS, ARGV.toArray());
System.out.println(execute);
Redis Lua脚本的使用建议
脚本缓存
通过EVAL命令执行Lua脚本的时候,每次都需要将脚本的源代码包含在请求中。重复调用EVAL命令来执行同一脚本,既浪费了网络带宽,也在Redis中有一些开销。当然,节省网络和计算资源是关键,因此,Redis为脚本提供了一种缓存机制。
默认情况下,使用EVAL执行的每个脚本都存储在服务器的专用缓存中,缓存的脚本内容通过脚本源代码的SHA1校验和来唯一标识( 缓存在底层的server.lua_scripts 字典中)。
我们可以通过先运行EVAL命令执行一个新的Lua脚本,随后调用INFO memory命令来验证此缓存行为。我们会注意到used_memory_scripts_human和number_of_cached_scripts等指标会随着每一个新脚本的执行而增长。
即使是通过EVAL执行相同的脚本,也会花费额外的开销,例如词法分析、语法分析等等。
对于这个问题,Redis提供了一个优化措施。
我们可以先调用SCRIPT LOAD script加载Lua脚本, 该命令会返回给客户端一个跟该Lua脚本向关联的SHA1校验和, 该命令并不会执行脚本,而是编译脚本并将其加载到服务器的缓存中。
后续调用该Lua脚本的时候, 只需通过EVALSHA SHA1 numkeys key [key …] arg [arg …]命令, 将SHA1当做参数进行传递,使用从服务器返回的SHA1摘要执行缓存的脚本即可。
下面是一个加载并执行缓存脚本的例子:
>SCRIPT LOAD "return 'cache script'"
"7d55b8f344d4d16a2d72bf255c993b3640f730ad">EVALSHA 7d55b8f344d4d16a2d72bf255c993b3640f730ad 0
"cache script"
脚本缓存稳定性
默认情况下Redis将会一直在内存中缓存执行过的Lua脚本。但是,Eval脚本被视为客户端应用程序的一部分,Lua脚本的缓存并不会在Redis服务端进行持久化,也就是说Redis脚本缓存总是不稳定的。
因为缓存脚本没有提供持久化能力,其可以在服务器重新启动时清除,也可以在副本承担主角色时的故障转移期间清除,或者通过SCRIPT FLUSH命令显式清除。
在使用EVALSHA命令时,若SHA1值对应的脚本未缓存至Redis中,Redis会返回NOSCRIPT错误,此时我们需要通过EVAL或SCRIPT LOAD命令将目标脚本缓存至Redis中后进行重试。
>EVALSHA ffffffffffffffffffffffffffffffffffffffff 0
"NOSCRIPT No matching script. Please use EVAL."
实际上,通过redisTemplate#execute()方法执行的Lua脚本,其内部就是使用的此逻辑:首先计算出Lua脚本的SHA1校验和,后续调用时,直接通过EVALSHA命令执行。因此,对于方法参数DefaultRedisScript,也是推荐单例实现的,即不必每次调用该方法创建一个新的DefaultRedisScript。
脚本参数化
基于脚本缓存的考虑,在应用程序运行时不断的动态生成脚本并且通过Eval发送给Redis执行很可能会耗尽主机用于缓存脚本的内存资源,动态生成脚本是一种反模式。
因此,脚本应该尽可能通用,也就是参数化,将共同的部分作为一个脚本源代码,对于动态变更的数据尽量作为参数传递,而不是每次动态生成一个新的脚本发给Redis执行。
这样的好处除了是可以节省脚本缓存资源,还可以节省网络带宽开销和计算开销。
例如,下面两个命令就是让应用程序根据需要动态生成不同的脚本源代码:
> EVAL "return 'Hello'" 0
"Hello"> EVAL "return 'Scripting!'" 0
"Scripting!"
但这样会让Redis缓存两个Lua脚本,我们对其进行参数化,变化的部分数据通过参数传递,如下:
>SCRIPT LOAD "return ARGV[1]"
"098e0f0d1448c0a81dafe820f66d460eb09263da">EVALSHA 098e0f0d1448c0a81dafe820f66d460eb09263da 0 Hello
"Hello">EVALSHA 098e0f0d1448c0a81dafe820f66d460eb09263da 0 Parameterization!
"Parameterization!"
这样,Redis就只需要在第一次调用的时候缓存改脚本并获取SHA1 校验和,对于后续的执行,只需要使用EVALSHA命令输入脚本的 SHA1 校验和以及脚本参数,就可以调用之前存储的脚本。
脚本超时处理
Lua脚本在Redis中和普通命令一样是原子执行的,Lua脚本执行过慢,将会导致Redis阻塞,将会影响所有连接的客户端。
脚本还有一个最大执行时间限制,它的默认值是5s,可以通过编辑redis.conf 文件或者使用 CONFIG GET parameter 和 CONFIG SET parameter value 命令来修改 lua-time-limit 选项来控制(单位ms)。
执行时间超过了最大执行时间的Lua脚本被称为慢脚本。
即使一个脚本执行超时,因为 Redis 必须保证脚本执行的原子性,所以它并不会自动停止。
因此,当脚本运行的时间超过最大执行时间后,以下动作会被执行:
- Redis 开始重新接受其他客户端的命令请求,但是只有SCRIPT KILL 和 SHUTDOWN NOSAVE两个命令会被处理,对于其他命令请求, Redis 服务器只是简单地返回 BUSY 错误::BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
- SCRIPT KILL命令只能用于在执行期间没有修改数据集的脚本,因为停止只读脚本并不违反脚本引擎的保证原子性,对于有写入数据的脚本无法停止。
- 如果脚本已经执行过写命令,那么唯一允许执行的操作就是SHUTDOWN NOSAVE ,它通过停止服务器来阻止当前数据集写入磁盘。
超时处理简单的原理是:
如果配置了lua-time-limit 选项,那么Reids执行Lua脚本之前会在Lua环境里面设置一个超时处理钩子(hook),超时处理钩子在脚本运行期间,会定期检查脚本已经运行了多长时间。
如果超过了最大执行时间,那么超时钩子将定期查看是否有SCRIPT KILL命令或者SHUTDOWN NOSAVE 命令到达服务器,然后执行后续判断。
Redis集群中Lua脚本的使用
一般的Lua脚本在Redis单节点模式以及主从哨兵模式下都能正常使用。
如果是Redis Cluster集群模式下,每个node节点会分配到不同的哈希槽(hash slot,默认16384个),存储数据的时候,会根据CRC16算法计算出key的哈希值,然后对16384取余计算出该key对应的slot,从而实现数据分片。
如果使用Redis集群一次性对多个key执行操作,例如mset,mget命令,包括对Lua脚本中的多个key执行调用时,要求所有操作的key必须在同一个节点上,否则会执行出错。
因此Redis 集群对使用Lua脚本的使用增加了额外的限制:
- 为了防止key不再同一个node,Redis在执行某个Lua脚本之前,会对KEYS中的所有key进行判断,如果所有的key不是位于同一个slot上,那么报错xxxx command keys must be in same slot。注意,即使key所属的不同slot位于同一个node,也还是会报错。
- 我们可以通过CLUSTER KEYSLOT命令获取目标Key的哈希槽(hash slot)。
- 通过EVAL或者SCRIPT LOAD命令缓存的脚本不会同步到其他节点中。
更好的办法是让需要Lua脚本操作的key都位于同一个slot,hashTag是Redis集群支持的一个特性,是一种确保在同一个slot中分配多个key的方法,目的是为了让Redis集群支持多key操作。
为key设置hashTag的方式很简单,我们只需要在key中追加{xxx}字符串即可,Redis会取key中第一个遇到的{}中的字符串来进行slot计算。详细逻辑见:https://redis.io/docs/reference/cluster-spec/#hash-tags
例如,原来的key为:key1和key2,如果按照正常计算方式,他们可能不会位于同一个slot,为他们分配相同的hashTag之后变成:{test}key1 {test}key2。此时集群会根据hashTag决定key分配到的slot,因为他们的hashTag都是test,因此它们会被分配到同一个slot。
例如集群模式下,执行如下命令:
EVAL "redis.call('mset',KEYS[1],ARGV[1],KEYS[2],ARGV[2])" 2 key1 key2 value1 value2
将会收到报错 ERR ‘key1’ and ‘key2’ not in the same slot。
我们采用hashTag改写key之后,将会正常执行:
EVAL "redis.call('mset',KEYS[1],ARGV[1],KEYS[2],ARGV[2])" 2 {test}key1 {test}key2 value1 value2
将会执行成功!
其他限制
Redis包含一个嵌入式Lua 5.1解释器。解释器运行用户定义的临时脚本和函数。脚本在一个沙箱上下文中运行,只能访问特定的Lua包:https://redis.io/docs/manual/programmability/lua-api/。
不要访问系统环境: Lua脚本脚本永远不应该尝试访问Redis服务器的底层主机系统。这包括文件系统、网络和执行系统调用的任何其他尝试,而不是API所支持的那些尝试。我们的Lua脚本应该只对存储在Redis中的数据进行操作。
无法定义全局变量和函数: 所有变量和函数定义都必须声明为局部的。为此,需要在声明前加上local关键字。默认支持的全局变量有:redis对象、KEYS、ARGV
只通过KEYS传递key: 为了确保在独立部署和集群部署环境中正确执行脚本(上面讲到的Redis集群环境执行Lua脚本的问题),脚本访问的所有Redis的key的必须显式通过KEYS参数传递。
Redis 7 Functions
如果有条件,那么使用Lua的更好的方式是使用Redis 7 Functions机制,Reids Funtions是从 7.0版开始提供的另一种可编程的方法,可以看是持久化的Lua函数,类似于关系型数据库的函数。
Redis Funtions支持在Redis服务端定义函数,其好处如下:
- 在服务端维护,支持不同的客户端远程调用共同的函数,Lua脚本则需要维护在各个客户端中。一旦Lua脚本更新,那么所有的客户端都需要更改。
- 支持函数的嵌套调用,Lua脚本不能在一个脚本中调用另一个脚本。
- 另外还支持函数的持久化,Lua脚本则不支持服务端持久化。
关于更多的Redis 7 Functions的信息,后续有机会再介绍。
Lua脚本相关常用命令
| EVAL | EVAL script numkeys [key [key …]] [arg [arg …]] | 执行给定的脚本和参数,并返回结果。参数说明:script:Lua脚本。numkeys:指定KEYS[]参数的数量,非负整数。KEYS[]:传入的Redis键参数。ARGV[]:传入的脚本参数。KEYS[]与ARGV[]的索引均从1开始。 |
|---|---|---|
| EVALSHA | EVALSHA sha1 numkeys key [key …] arg [arg …] | 给定脚本的SHA1校验和,Redis将再次执行脚本。使用EVALSHA命令时,若sha1值对应的脚本未缓存至Redis中,Redis会返回NOSCRIPT错误,请通过EVAL或SCRIPT LOAD命令将目标脚本缓存至Redis中后进行重试。 |
| SCRIPT LOAD | SCRIPT LOAD script | 将给定的script脚本缓存在Redis中,并返回该脚本的SHA1校验和。该命令并不会执行脚本。 |
| SCRIPT EXISTS | SCRIPT EXISTS script [script …] | 给定一个(或多个)脚本的SHA1,返回每个SHA1对应的脚本是否已缓存在当前Redis服务。脚本已存在则返回1,不存在则返回0。 |
| SCRIPT KILL | SCRIPT KILL | 停止正在运行的Lua脚本。该命令是中断长时间运行的脚本(也就是慢脚本)的唯一方法,除非关闭服务器。一旦脚本的执行持续时间超过了配置的最大执行时间阈值(默认5s),脚本就被视为慢脚本。SCRIPT KILL命令只能用于在执行期间没有修改数据集的脚本(因为停止只读脚本并不违反脚本引擎的保证原子性),对于有写入数据的脚本无法停止。 |
| SCRIPT FLUSH | SCRIPT FLUSH | 清空当前Redis服务器中的所有Lua脚本缓存。该命令是强制Redis刷新脚本缓存的唯一方法。 |
总结
本次我们介绍了Redis Lua脚本的入门概念,使用方式,以及一些核心知识点,当然还有一些知识点没有详细讲解,比如Redis 7 函数、Lua脚本的复制等等。
更多的内容在Redis官网都有详细介绍,下来后可以相互交流讨论。
参考
https://redis.io/topics/cluster-spec/
https://redis.io/commands/eval/
https://redis.io/docs/manual/programmability/
https://redis.io/docs/manual/programmability/lua-api/
相关文章:
Redis Lua脚本的详细介绍以及使用入门
Redis Lua脚本的详细介绍以及使用入门。 文章目录Redis Lua脚本的引入开源软件的可扩展性Redis的扩展性脚本Redis Lua脚本的基本使用通过EVAL命令执行Lua脚本通过脚本与Redis交互Java中调用Redis Lua脚本Java调用Lua脚本的方式Redis Lua脚本的使用建议脚本缓存脚本缓存稳定性脚…...
synchronized和ReentrantLock有什么区别呢?
第15讲 | synchronized和ReentrantLock有什么区别呢? 从今天开始,我们将进入 Java 并发学习阶段。软件并发已经成为现代软件开发的基础能力,而 Java 精心设计的高效并发机制,正是构建大规模应用的基础之一,所以考察并发…...
SVHN数据集下载及使用方法
街景门牌号数据集(SVHN),这是一个现实世界数据集,用于开发目标检测算法。它需要最少的数据预处理过程。它与 MNIST 数据集有些类似,但是有着更多的标注数据(超过 600,000 张图像)。这些数据是从…...
产业安全公开课:2023年DDoS攻击趋势研判与企业防护新思路
2023年,全球数字化正在加速发展,网络安全是数字化发展的重要保障。与此同时,网络威胁日益加剧。其中,DDoS攻击作为网络安全的主要威胁之一,呈现出连年增长的态势,给企业业务稳定带来巨大挑战。2月21日&…...
Docker 容器命令 和安装各种镜像环境
CentOS安装Docker 1.1.卸载(可选) 如果之前安装过旧版本的Docker,可以使用下面命令卸载: yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotat…...
【数据结构】顺序表的深度剖析
🌇个人主页:平凡的小苏 📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情 🛸C语言专栏:https://blog.csdn.net/vhhhbb/category_12174730.html 🚀数据结构专栏ÿ…...
当面试官问“你的SQL能力怎么样”时,怎么回答才不会掉进应聘陷阱?
在某平台看到一个比较实际的问题,在这里分享给职场新人。 SQL已经是职场最常用的一种编程语言,所以应聘技术或非技术岗位,都可能会被问道一个问题:你的SQL能力怎么样? 对于职场新人来说(SQL高手可以无视下…...
AI作画—中国画之山水画
山水画,简称“山水”,中国画的一种,描写山川自然景色为主体的绘画。山水画在我国绘画史中占有重要的地位。 山水画形成于魏晋南北朝时期,但尚未从人物画中完全分离。隋唐时始终独立,五代、北宋时趋于成熟,…...
Java:Java与Python — 编码大战
Java和Python是目前市场上最热门的两种编程语言,因为它们具有通用性、高效性和自动化能力。两种语言都有各自的优点和缺点,但主要区别在于Java 是静态类型的,Python是动态类型的。它们有相似之处,因为它们都采用了“一切都是对象”…...
山东专精特新各地市扶持政策
青岛市奖励政策:新认定为市隐形、省“专精特新”及省瞪羚、角兽的我市企业,分别给予50万元、30万元、50万元、300万元的一次性奖励。奖励金额:省级30万济南市奖励政策:对被认定的国家专精特新 “小巨人”企业一次性给予200万元奖励…...
持续事务管理过程中的事件驱动
比较官方的定义:事件驱动是指在持续事务管理过程中,进行决策的一种策略,即跟随当前时间点上出现的事件,调动可用资源,执行相关任务,使不断出现的问题得以解决,防止事务堆积。在计算机编程、公共…...
【手把手一起学习】(三) Altium Designer 20 原理图库添加元件
1 添加元件 元件符号是元件在原理图上的表现形式,主要由边框、管脚、名称等组成,原理图库中的元件管脚(顺序,间距等)与电子元件实物的引脚严格对应,绘制原理图库时,一定参考元件规格书和芯片数据手册中的说明…...
设计模式-行为型模式:观察者模式
目录 1、简介 2、组成部分 3、优缺点 4、使用场景 5、代码实现 1、简介 观察者模式是一种软件设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象,当主题对象发生变化时,所有的观察者对象都会得到…...
Springboot 为了偷懒,我封装了一个自适配的数据单位转换工具类
前言 平时做一些统计数据,经常从数据库或者是从接口获取出来的数据,单位是跟业务需求不一致的。 比如, 我们拿出来的 分, 实际上要是元 又比如,我们拿到的数据需要 乘以100 返回给前端做 百分比展示 又比如ÿ…...
正则表达式
当我们需要对字符串进行判断的时候,使用正则表达式能大大提高编程效率。比如,当我们需要找出所有“像邮箱”的字符串(包含"" "." ".com",且顺序一致),我们需要一个某种模式的…...
java进阶Map 集合
通过之前的学习我们知道Map是一个双列集合,就是以键值对的形式进行数据存储 java进阶—集合 Map 下面有 三个子接口,HashMap , HashTable 以及 TreeMap 提醒一点:Map不是Collection下的集合,Collection是单列集合&am…...
Java 方法超详细整理,适合新手入门
目录 一、什么是方法呢? 二、方法的优点 三、带返回值方法定义 语法: 示例: 四、带返回值方法调用 语法: 示例: 五、结果示例 一、什么是方法呢? Java方法是语句的集合,它们在一起执行…...
软考学习笔记(题目知识记录)
答案为 概要设计阶段 本题涉及软件工程的概念 软件工程的任务是基于需求分析的结果建立各种设计模型,给出问题的解决方案 软件设计可以分为两个阶段: 概要设计阶段和详细设计阶段 结构化设计方法中,概要设计阶段进行软件体系结构的设计&…...
2021.3.3idea创建Maven项目
首先new - project - 找到Maven 然后按下图操作:先勾选使用骨架,再找到Maven-archetype-webapp,选中,然后next填写自己想要创建的项目名,然后选择自己的工作空间①、选择自己下载的Maven插件②、选择选择Maven里的sett…...
ASP.NET MVC | 创建应用程序
目录 首先 NO.1 No.2 App_Data 文件夹 Content 文件夹 Controllers 文件夹 Models 文件夹 Views 文件夹 Scripts 文件夹 最后 首先 一步一步的来,电脑上需要安装vs2019软件,版本高低无所谓,就是功能多少而已。 长这样的࿰…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
