当前位置: 首页 > news >正文

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脚本的使用入门,主要分为三个方面:

  1. Redis Lua脚本的介绍和基本使用语法。
  2. Java如何中调用Lua脚本操作Redis。
  3. 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脚本的好处有很多,最为人熟知的亮点包括:

  1. 很多复杂的业务需要包含多步Redis操作,这些操作常被拆分为一个个单独的API方法。开发人员可以使用Redis脚本的功能实现一个健壮的、特定于应用程序的api。这样的api可以封装业务逻辑,并跨多个键和不同的数据结构维护数据模型。
  2. 复杂业务的多步Redis操作,一般情况下需要多次与Redis服务器交互,需要多次网络IO开销。使用一个Redis脚本来完成多步操作的封装,然后发送给Redis服务器执行,因为脚本在同一个服务器中执行,所以从脚本中读取和写入数据非常有效,仅需要少量的网络IO的开销,给我们带来更好的性能。
  3. 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脚本中支持动态传递参数,执行上下文通过KEYSARGV全局运行时变量来传递使脚本使用的参数。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...])
  1. command:redis命令,必须。
  2. 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脚本。

  1. 首先我们需要创建一个DefaultRedisScript对象,该对象表示Lua脚本的抽象。
  2. 然后设置设置DefaultRedisScript的返回值类型,支持List、Boolean、Long、String类型,默认是返回String类型。
  3. 然后将Lua脚本代码可以直接设置进去,也可以选择从文件中加载。
  4. 最后通过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_humannumber_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 必须保证脚本执行的原子性,所以它并不会自动停止。

因此,当脚本运行的时间超过最大执行时间后,以下动作会被执行:

  1. Redis 开始重新接受其他客户端的命令请求,但是只有SCRIPT KILL 和 SHUTDOWN NOSAVE两个命令会被处理,对于其他命令请求, Redis 服务器只是简单地返回 BUSY 错误::BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
  2. SCRIPT KILL命令只能用于在执行期间没有修改数据集的脚本,因为停止只读脚本并不违反脚本引擎的保证原子性,对于有写入数据的脚本无法停止。
  3. 如果脚本已经执行过写命令,那么唯一允许执行的操作就是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脚本的使用增加了额外的限制:

  1. 为了防止key不再同一个node,Redis在执行某个Lua脚本之前,会对KEYS中的所有key进行判断,如果所有的key不是位于同一个slot上,那么报错xxxx command keys must be in same slot注意,即使key所属的不同slot位于同一个node,也还是会报错。
  2. 我们可以通过CLUSTER KEYSLOT命令获取目标Key的哈希槽(hash slot)。
  3. 通过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服务端定义函数,其好处如下:

  1. 在服务端维护,支持不同的客户端远程调用共同的函数,Lua脚本则需要维护在各个客户端中。一旦Lua脚本更新,那么所有的客户端都需要更改。
  2. 支持函数的嵌套调用,Lua脚本不能在一个脚本中调用另一个脚本。
  3. 另外还支持函数的持久化,Lua脚本则不支持服务端持久化。

关于更多的Redis 7 Functions的信息,后续有机会再介绍。

Lua脚本相关常用命令

EVALEVAL script numkeys [key [key …]] [arg [arg …]]执行给定的脚本和参数,并返回结果。参数说明:script:Lua脚本。numkeys:指定KEYS[]参数的数量,非负整数。KEYS[]:传入的Redis键参数。ARGV[]:传入的脚本参数。KEYS[]与ARGV[]的索引均从1开始。
EVALSHAEVALSHA sha1 numkeys key [key …] arg [arg …]给定脚本的SHA1校验和,Redis将再次执行脚本。使用EVALSHA命令时,若sha1值对应的脚本未缓存至Redis中,Redis会返回NOSCRIPT错误,请通过EVAL或SCRIPT LOAD命令将目标脚本缓存至Redis中后进行重试。
SCRIPT LOADSCRIPT LOAD script将给定的script脚本缓存在Redis中,并返回该脚本的SHA1校验和。该命令并不会执行脚本。
SCRIPT EXISTSSCRIPT EXISTS script [script …]给定一个(或多个)脚本的SHA1,返回每个SHA1对应的脚本是否已缓存在当前Redis服务。脚本已存在则返回1,不存在则返回0。
SCRIPT KILLSCRIPT KILL停止正在运行的Lua脚本。该命令是中断长时间运行的脚本(也就是慢脚本)的唯一方法,除非关闭服务器。一旦脚本的执行持续时间超过了配置的最大执行时间阈值(默认5s),脚本就被视为慢脚本。SCRIPT KILL命令只能用于在执行期间没有修改数据集的脚本(因为停止只读脚本并不违反脚本引擎的保证原子性),对于有写入数据的脚本无法停止。
SCRIPT FLUSHSCRIPT 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有什么区别呢&#xff1f; 从今天开始&#xff0c;我们将进入 Java 并发学习阶段。软件并发已经成为现代软件开发的基础能力&#xff0c;而 Java 精心设计的高效并发机制&#xff0c;正是构建大规模应用的基础之一&#xff0c;所以考察并发…...

SVHN数据集下载及使用方法

街景门牌号数据集&#xff08;SVHN&#xff09;&#xff0c;这是一个现实世界数据集&#xff0c;用于开发目标检测算法。它需要最少的数据预处理过程。它与 MNIST 数据集有些类似&#xff0c;但是有着更多的标注数据&#xff08;超过 600,000 张图像&#xff09;。这些数据是从…...

产业安全公开课:2023年DDoS攻击趋势研判与企业防护新思路

2023年&#xff0c;全球数字化正在加速发展&#xff0c;网络安全是数字化发展的重要保障。与此同时&#xff0c;网络威胁日益加剧。其中&#xff0c;DDoS攻击作为网络安全的主要威胁之一&#xff0c;呈现出连年增长的态势&#xff0c;给企业业务稳定带来巨大挑战。2月21日&…...

Docker 容器命令 和安装各种镜像环境

CentOS安装Docker 1.1.卸载&#xff08;可选&#xff09; 如果之前安装过旧版本的Docker&#xff0c;可以使用下面命令卸载&#xff1a; yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotat…...

【数据结构】顺序表的深度剖析

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;别人可以拷贝我的模式&#xff0c;但不能拷贝我不断往前的激情 &#x1f6f8;C语言专栏&#xff1a;https://blog.csdn.net/vhhhbb/category_12174730.html &#x1f680;数据结构专栏&#xff…...

当面试官问“你的SQL能力怎么样”时,怎么回答才不会掉进应聘陷阱?

在某平台看到一个比较实际的问题&#xff0c;在这里分享给职场新人。 SQL已经是职场最常用的一种编程语言&#xff0c;所以应聘技术或非技术岗位&#xff0c;都可能会被问道一个问题&#xff1a;你的SQL能力怎么样&#xff1f; 对于职场新人来说&#xff08;SQL高手可以无视下…...

AI作画—中国画之山水画

山水画&#xff0c;简称“山水”&#xff0c;中国画的一种&#xff0c;描写山川自然景色为主体的绘画。山水画在我国绘画史中占有重要的地位。 山水画形成于魏晋南北朝时期&#xff0c;但尚未从人物画中完全分离。隋唐时始终独立&#xff0c;五代、北宋时趋于成熟&#xff0c;…...

Java:Java与Python — 编码大战

Java和Python是目前市场上最热门的两种编程语言&#xff0c;因为它们具有通用性、高效性和自动化能力。两种语言都有各自的优点和缺点&#xff0c;但主要区别在于Java 是静态类型的&#xff0c;Python是动态类型的。它们有相似之处&#xff0c;因为它们都采用了“一切都是对象”…...

山东专精特新各地市扶持政策

青岛市奖励政策&#xff1a;新认定为市隐形、省“专精特新”及省瞪羚、角兽的我市企业&#xff0c;分别给予50万元、30万元、50万元、300万元的一次性奖励。奖励金额&#xff1a;省级30万济南市奖励政策&#xff1a;对被认定的国家专精特新 “小巨人”企业一次性给予200万元奖励…...

持续事务管理过程中的事件驱动

比较官方的定义&#xff1a;事件驱动是指在持续事务管理过程中&#xff0c;进行决策的一种策略&#xff0c;即跟随当前时间点上出现的事件&#xff0c;调动可用资源&#xff0c;执行相关任务&#xff0c;使不断出现的问题得以解决&#xff0c;防止事务堆积。在计算机编程、公共…...

【手把手一起学习】(三) Altium Designer 20 原理图库添加元件

1 添加元件 元件符号是元件在原理图上的表现形式&#xff0c;主要由边框、管脚、名称等组成&#xff0c;原理图库中的元件管脚(顺序&#xff0c;间距等)与电子元件实物的引脚严格对应&#xff0c;绘制原理图库时&#xff0c;一定参考元件规格书和芯片数据手册中的说明&#xf…...

设计模式-行为型模式:观察者模式

目录 1、简介 2、组成部分 3、优缺点 4、使用场景 5、代码实现 1、简介 观察者模式是一种软件设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听一个主题对象&#xff0c;当主题对象发生变化时&#xff0c;所有的观察者对象都会得到…...

Springboot 为了偷懒,我封装了一个自适配的数据单位转换工具类

前言 平时做一些统计数据&#xff0c;经常从数据库或者是从接口获取出来的数据&#xff0c;单位是跟业务需求不一致的。 比如&#xff0c; 我们拿出来的 分&#xff0c; 实际上要是元 又比如&#xff0c;我们拿到的数据需要 乘以100 返回给前端做 百分比展示 又比如&#xff…...

正则表达式

当我们需要对字符串进行判断的时候&#xff0c;使用正则表达式能大大提高编程效率。比如&#xff0c;当我们需要找出所有“像邮箱”的字符串&#xff08;包含"" "." ".com"&#xff0c;且顺序一致&#xff09;&#xff0c;我们需要一个某种模式的…...

java进阶Map 集合

通过之前的学习我们知道Map是一个双列集合&#xff0c;就是以键值对的形式进行数据存储 java进阶—集合 Map 下面有 三个子接口&#xff0c;HashMap &#xff0c; HashTable 以及 TreeMap 提醒一点&#xff1a;Map不是Collection下的集合&#xff0c;Collection是单列集合&am…...

Java 方法超详细整理,适合新手入门

目录 一、什么是方法呢&#xff1f; 二、方法的优点 三、带返回值方法定义 语法&#xff1a; 示例&#xff1a; 四、带返回值方法调用 语法&#xff1a; 示例&#xff1a; 五、结果示例 一、什么是方法呢&#xff1f; Java方法是语句的集合&#xff0c;它们在一起执行…...

软考学习笔记(题目知识记录)

答案为 概要设计阶段 本题涉及软件工程的概念 软件工程的任务是基于需求分析的结果建立各种设计模型&#xff0c;给出问题的解决方案 软件设计可以分为两个阶段&#xff1a; 概要设计阶段和详细设计阶段 结构化设计方法中&#xff0c;概要设计阶段进行软件体系结构的设计&…...

2021.3.3idea创建Maven项目

首先new - project - 找到Maven 然后按下图操作&#xff1a;先勾选使用骨架&#xff0c;再找到Maven-archetype-webapp&#xff0c;选中&#xff0c;然后next填写自己想要创建的项目名&#xff0c;然后选择自己的工作空间①、选择自己下载的Maven插件②、选择选择Maven里的sett…...

ASP.NET MVC | 创建应用程序

目录 首先 NO.1 No.2 App_Data 文件夹 Content 文件夹 Controllers 文件夹 Models 文件夹 Views 文件夹 Scripts 文件夹 最后 首先 一步一步的来&#xff0c;电脑上需要安装vs2019软件&#xff0c;版本高低无所谓&#xff0c;就是功能多少而已。 长这样的&#xff0…...

思科设备命令讲解(超基础)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…...

Qt-FFmpeg开发-保存视频流裸流(11)

Qt-FFmpeg开发-保存视频流裸流&#x1f4c0; 文章目录Qt-FFmpeg开发-保存视频流裸流&#x1f4c0;1、概述&#x1f4f8;2、实现效果&#x1f4bd;3、FFmpeg保存裸流代码流程&#x1f4a1;4、主要代码&#x1f50d;5、完整源代码&#x1f4d1;更多精彩内容&#x1f449;个人内容…...

Zebec官方辟谣“我们与Protradex没有任何关系”

近日&#xff0c;流支付协议Zebec Protocol在其官方推特上&#xff0c;发表了一个辟谣澄清声明。该条推特推文表示&#xff0c;“Zebec 与 Protradex 没有任何关系或产生关联。他们&#xff08; Protradex &#xff09;声称Zebec 生态正在支持他们&#xff0c;但这是错误的。随…...

BMS电池管理系统中的各种算法介绍

BMS电池管理系统 是一种用于电池组中的单个电池管理的系统&#xff0c;以确保其安全性、寿命和性能。BMS系统通过采集电池信息并对其进行分析&#xff0c;以确保电池组的正常运行。在BMS电池管理系统中&#xff0c;涉及到了许多算法&#xff0c;包括最大功率点追踪算法、SOC计算…...

stack Overflow 的使用

文章目录优雅的搜索1.1要在特定标签内搜索1.2搜索特定的短语1.3 限定检索位置1.4选择性屏蔽优雅的筛选搜索结果1. 返回的搜索筛选2. 特定时间段的帖子3. 精准的BOOL判断4. 其他的例子优雅的搜索 其实&#xff0c;在Stack OverFlow上的搜索方式&#xff0c;与国内的百度没什么大…...

Vue 在for循环中动态添加类名及style样式集合

介绍 在vue的 for 循环中&#xff0c;经常会使用到动态添加类名或者样式的情况&#xff0c;实现给当前的选中的 div 添加不同的样式。 动态添加类名 提示&#xff1a; 所有动态添加的类名&#xff0c;放在表达式里都需要添加引号&#xff0c;进行包裹。 通过 对象 的形式&a…...

Maven的优势

作用一&#xff1a;个人理解maven主要是用来解决导入java类依赖的jar,编译java项目主要问题。(最早手动导入jar&#xff0c;使用Ant之类的编译java项目)以pom.xml文件中dependency属性管理依赖的jar包&#xff0c;而jar包包含class文件和一些必要的资源文件。当然它可以构建项目…...

uboot,内核,根文件系统的作用

复习了下uboot&#xff0c;内核&#xff0c;根文件系统&#xff0c;简单概括下三者的主要内容。 1 uboot uboot的目的&#xff1a;启动内核。 uboot的功能可以分为两个阶段任务。 1.2.1 uboot第一阶段 uboot第一阶段主要负责硬件相关的初始化&#xff0c;主要在cpu/arm920…...

Vue3通透教程【四】Vue3组合API初体验

文章目录&#x1f31f; 写在前面&#x1f31f; 组合式 API 是什么&#xff1f;&#x1f31f; 直观组合式API&#x1f31f; 写在最后&#x1f31f; 写在前面 专栏介绍&#xff1a; 凉哥作为 Vue 的忠实 粉丝输出过大量的 Vue 文章&#xff0c;应粉丝要求开始更新 Vue3 的相关技…...

coco数据集训练nanodet详细流程

github地址 首先要配置环境 conda create -n nanodet python3.8 -y conda activate nanodet确认一下cuda版本 nvcc -V确认是11.3之后&#xff0c;要安装11.3对应的pytorch版本。 本机装pytorch1.12.1后面运行的时候会报错&#xff08;torch没有经过cuda编译&#xff09;&…...