Redis(二) 可编程性

结合上一期 Redis(一) Redis简介(Redis(一) Redis简介-CSDN博客)

目录

Redis 可编程性

运行脚本

只读脚本

只读脚本历史记录 

最长执行时间

Redis 函数

加载库和函数

输入键和常规参数

扩展库 

重用库中的代码

Lua 脚本 

脚本参数化

从脚本与 Redis 交互 

脚本缓存

在 Redis 中调试 Lua 脚本


Redis 可编程性

使用 Lua 和 Redis 函数扩展 Redis供了一个编程接口,允许您在服务器本身上执行自定义脚本。在 Redis 7 及更高版本中,您可以使用 Redis functions 来管理和运行脚本。在 Redis 6.2 及更低版本中,您可以使用 带有EVAL命令的 Lua 编写脚本对服务器进行编程。

Redis 是一种“抽象数据类型的特定领域语言”。 Redis 使用的语言由其命令组成。 大多数命令专门用于以不同的方式操作核心数据类型。 在许多情况下,这些命令提供了开发人员在 Redis 中管理应用程序数据所需的所有功能。

Redis 中的术语可编程性是指能够由服务器执行任意用户定义的逻辑。 我们将这些逻辑片段称为脚本。 在我们的例子中,脚本可以处理它所在的数据,也就是数据局部性。 此外,在 Redis 服务器中负责任地嵌入编程工作流有助于减少网络流量并提高整体性能。 开发人员可以使用此功能来实现可靠的、特定于应用程序的 API。 此类 API 可以封装业务逻辑,并跨多个键和不同数据结构维护数据模型。

用户脚本由嵌入式沙盒脚本引擎在 Redis 中执行。 目前,Redis 支持单个脚本引擎,即 Lua 5.1 解释器。

运行脚本

Redis 提供了两种运行脚本的方法。

首先,从 Redis 2.6.0 开始,EVAL命令允许运行服务器端脚本。 Eval 脚本提供了一种快速而直接的方法,让 Redis 临时运行您的脚本。 但是,使用它们意味着脚本逻辑是应用程序的一部分(而不是 Redis 服务器的扩展)。 运行脚本的每个应用程序实例都必须具有随时可加载脚本的源代码。 这是因为脚本仅由服务器缓存,并且是易失性的。 随着应用程序的增长,此方法可能更难开发和维护。

其次,在 v7.0 中添加的 Redis 函数本质上是作为一类数据库元素的脚本。 因此,函数将脚本与应用程序逻辑分离,并支持脚本的独立开发、测试和部署。 要使用函数,需要先加载它们,然后它们可供所有连接的客户端使用。 在这种情况下,将函数加载到数据库将成为管理部署任务(例如加载 Redis 模块),这会将脚本与应用程序分开。

有关详细信息,请参阅以下页面:

  • Redis EVAL脚本
  • Redis 函数

在运行脚本或函数时,Redis 保证其原子执行。 脚本的执行会在整个时间内阻止所有服务器活动,这与事务的语义类似。 这些语义意味着脚本的所有效果要么尚未发生,要么已经发生。 已执行脚本的阻塞语义始终适用于所有连接的客户端。

请注意,这种阻塞方法的潜在缺点是执行慢速脚本不是一个好主意。 创建快速脚本并不难,因为脚本的开销非常低。 但是,如果您打算在应用程序中使用慢速脚本,请注意,所有其他客户端都会被阻止,并且在运行时无法执行任何命令。

只读脚本

只读脚本是仅执行不修改 Redis 中任何键的命令的脚本。 可以通过向脚本添加标志或使用只读脚本命令变体之一(EVAL_ROEVALSHA_RO 或 FCALL_RO )执行脚本来执行只读脚本。 它们具有以下属性:no-writes

  • 它们始终可以在副本上执行。
  • 它们始终可以通过 SCRIPT KILL 命令杀死。
  • 当 redis 超过内存限制时,它们永远不会因 OOM 错误而失败。
  • 它们在写入暂停期间不会被阻止,例如在协调故障转移期间发生的暂停。
  • 它们不能执行任何可能修改数据集的命令。
  • 目前,PUBLISH 、SPUBLISH 和 PFCOUNT 也被视为脚本中的写入命令,因为它们可以尝试将命令传播到副本和 AOF 文件。

除了所有只读脚本提供的好处外,只读脚本命令还具有以下优点:

  • 它们可用于将 ACL 用户配置为只能执行只读脚本。
  • 对于希望使用副本进行读取扩展的应用程序,许多客户端还更好地支持将只读脚本命令路由到副本。

只读脚本历史记录 

Redis 7.0 中引入了只读脚本和只读脚本命令

  • 在 Redis 7.0.1  PUBLISH 之前, SPUBLISH  和 PFCOUNT  不被视为脚本中的写入命令
  • 在 Redis 7.0.1 之前,该标志并不意味着no-writesallow-oom
  • 在 Redis 7.0.1 之前,该标志不允许脚本在写入暂停期间运行。no-writes

最长执行时间

脚本受最大执行时间的约束(默认设置为 5 秒)。 此默认超时时间很大,因为脚本通常在不到一毫秒的时间内运行。 该限制已到位,用于处理在开发过程中创建的意外无限循环。

可以以毫秒精度修改脚本可以执行的最长时间, 通过或使用 CONFIG SET 命令。 影响最大执行时间的配置参数称为 。redis.confbusy-reply-threshold

当脚本达到超时阈值时,Redis 不会自动终止该脚本。 这样做将违反 Redis 与脚本引擎之间的约定,该约定确保脚本是原子的。 中断脚本的执行可能会使数据集留下半写更改。

因此,当脚本的执行时间超过配置的超时时,会发生以下情况:

  • Redis 会记录脚本运行时间过长的情况。
  • 它再次开始接受来自其他客户端的命令,但会回复所有发送正常命令的客户端的 BUSY 错误。此状态下唯一允许的命令是 SCRIPT KILLFUNCTION KILL 和 。SHUTDOWN NOSAVE
  • 可以使用 SCRIPT KILL 和 FUNCTION KILL 命令终止仅执行只读命令的脚本。这些命令不违反脚本语义,因为脚本尚未将数据写入数据集。
  • 如果脚本已经执行了一次写入操作,则唯一允许的命令是停止服务器而不将当前数据集保存在磁盘上(基本上,服务器已中止)。SHUTDOWN NOSAVE

Redis 函数

Redis 函数是从临时脚本演变而来的一步。

函数提供与脚本相同的核心功能,但却是数据库的一流软件工件。 Redis 将函数作为数据库的一个组成部分进行管理,并通过数据持久化和复制来确保其可用性。 由于函数是数据库的一部分,因此在使用之前声明,因此应用程序不需要在运行时加载它们,也不需要冒中止事务的风险。 使用函数的应用程序仅依赖于其 API,而不依赖于数据库中的嵌入式脚本逻辑。

虽然临时脚本被视为应用程序域的一部分,但函数使用用户提供的逻辑扩展数据库服务器本身。 它们可用于公开由核心 Redis 命令组成的更丰富的 API,类似于模块,开发一次,启动时加载,并被各种应用程序/客户端重复使用。 每个函数都有一个唯一的用户定义名称,可以更轻松地调用和跟踪其执行。

Redis 函数的设计还试图在用于编写函数的编程语言和服务器管理函数之间进行划分。 Lua 是 Redis 目前支持的唯一作为嵌入式执行引擎的语言解释器,它旨在简单易学。 然而,选择 Lua 作为一种语言仍然给许多 Redis 用户带来了挑战。

Redis 函数功能不对实现的语言做出任何假设。 作为函数定义一部分的执行引擎处理运行它。 从理论上讲,引擎可以用任何语言执行函数,只要它遵守几个规则(例如终止执行函数的能力)。

目前,如上所述,Redis 附带了一个嵌入式 Lua 5.1 引擎。 有计划在未来支持更多的引擎。 Redis 函数可以使用 Lua 的所有可用功能来临时脚本, 唯一的例外是 Redis Lua脚本调试器。

函数还通过启用代码共享来简化开发。 每个函数都属于一个库,任何给定的库都可以由多个函数组成。 库的内容是不可变的,并且不允许有选择地更新其函数。 取而代之的是,库作为一个整体进行更新,其所有功能都在一个操作中。 这允许从同一库中的其他函数调用函数,或者使用库内部方法中的通用代码在函数之间共享代码,这些方法也可以采用语言本机参数。

如上所述,函数旨在更好地支持通过逻辑架构维护数据实体视图的一致视图的用例。 因此,函数与数据本身一起存储。 函数还保留到 AOF 文件中,并从主函数复制到副本,因此它们与数据本身一样持久。 当 Redis 用作临时缓存时,需要其他机制(如下所述)来使函数更持久。

与 Redis 中的所有其他操作一样,函数的执行是原子的。 函数的执行会在整个时间内阻止所有服务器活动,这与事务的语义类似。 这些语义意味着脚本的所有效果要么尚未发生,要么已经发生。 已执行函数的阻塞语义始终适用于所有连接的客户端。 由于运行函数会阻塞 Redis 服务器,因此函数需要快速完成执行,因此应避免使用长时间运行的函数。

加载库和函数

以下代码片段演示了一个简单的库,该库注册了一个名为 knockknock 的函数,并返回字符串回复:

redis.register_function('knockknock',function() return 'Who\'s there?' end
)

输入键和常规参数

在我们转到以下示例之前,了解 Redis 对键名称参数和非键名称参数的区分至关重要。

虽然 Redis 中的键名称只是字符串,但与任何其他字符串值不同,这些值表示数据库中的键。 密钥名称是 Redis 中的一个基本概念,也是操作 Redis 集群的基础。

重要:为确保在独立部署和集群部署中正确执行 Redis 函数,必须显式提供函数访问的所有键名称作为输入键参数。

任何不是键名称的函数输入都是常规输入参数。

现在,让我们假设我们的应用程序将其部分数据存储在 Redis Hashes 中。 我们想要一种类似 HSET 的方式来设置和更新所述哈希中的字段,并将上次修改时间存储在名为 的新字段中。 我们可以实现一个函数来完成所有这些工作。_last_modified_

我们的函数将调用 TIME 来获取服务器的时钟读数,并使用新字段的值和修改的时间戳更新目标哈希值。 我们将实现的函数接受以下输入参数:哈希的键名和要更新的字段值对。

适用于 Redis 函数的 Lua API 使这些输入可作为函数回调的第一个和第二个参数进行访问。 回调的第一个参数是一个 Lua 表,其中填充了函数的所有键名输入。 同样,回调的第二个参数由所有常规参数组成。

以下是我们的函数及其库注册的可能实现:

local function my_hset(keys, args)local hash = keys[1]local time = redis.call('TIME')[1]return redis.call('HSET', hash, '_last_modified_', time, unpack(args))
endredis.register_function('my_hset', my_hset)

扩展库 

可以向库中添加更多函数,以利于我们的应用程序。 在访问哈希数据时,我们添加到哈希的其他元数据字段不应包含在响应中。 另一方面,我们确实希望提供获取给定哈希键的修改时间戳的方法。

我们将向库添加两个新函数来实现这些目标:

  1. Redis 函数将从给定的哈希键名称返回所有字段及其各自的值,不包括元数据(即字段)。my_hgetall_last_modified_
  2. Redis 函数将返回给定哈希键名称的修改时间戳。my_hlastmodified
local function my_hset(keys, args)local hash = keys[1]local time = redis.call('TIME')[1]return redis.call('HSET', hash, '_last_modified_', time, unpack(args))
endlocal function my_hgetall(keys, args)redis.setresp(3)local hash = keys[1]local res = redis.call('HGETALL', hash)res['map']['_last_modified_'] = nilreturn res
endlocal function my_hlastmodified(keys, args)local hash = keys[1]return redis.call('HGET', hash, '_last_modified_')
endredis.register_function('my_hset', my_hset)
redis.register_function('my_hgetall', my_hgetall)
redis.register_function('my_hlastmodified', my_hlastmodified)

重用库中的代码

除了将函数捆绑到数据库管理的软件工件中之外,库还促进了代码共享。 我们可以将一个从其他函数调用的错误处理帮助程序函数添加到我们的库中。 帮助程序函数验证输入表是否具有单个键。 成功后,它会返回 ,否则会返回错误回复。check_keys()nil

local function check_keys(keys)local error = nillocal nkeys = table.getn(keys)if nkeys == 0 thenerror = 'Hash key name not provided'elseif nkeys > 1 thenerror = 'Only one key name is allowed'endif error ~= nil thenredis.log(redis.LOG_WARNING, error);return redis.error_reply(error)endreturn nil
endlocal function my_hset(keys, args)local error = check_keys(keys)if error ~= nil thenreturn errorendlocal hash = keys[1]local time = redis.call('TIME')[1]return redis.call('HSET', hash, '_last_modified_', time, unpack(args))
endlocal function my_hgetall(keys, args)local error = check_keys(keys)if error ~= nil thenreturn errorendredis.setresp(3)local hash = keys[1]local res = redis.call('HGETALL', hash)res['map']['_last_modified_'] = nilreturn res
endlocal function my_hlastmodified(keys, args)local error = check_keys(keys)if error ~= nil thenreturn errorendlocal hash = keys[1]return redis.call('HGET', keys[1], '_last_modified_')
endredis.register_function('my_hset', my_hset)
redis.register_function('my_hgetall', my_hgetall)
redis.register_function('my_hlastmodified', my_hlastmodified)

Lua 脚本 

Redis 允许用户在服务器上上传和执行 Lua 脚本。 脚本可以采用编程控制结构,并在执行访问数据库时使用大多数命令。 由于脚本在服务器中执行,因此从脚本读取和写入数据非常高效。

Redis 保证脚本的原子执行。 在执行脚本时,所有服务器活动都会在其整个运行时被阻止。 这些语义意味着脚本的所有效果要么尚未发生,要么已经发生。

脚本提供了几个属性,这些属性在许多情况下都很有价值。 这些包括:

  • 通过在数据所在的位置执行逻辑来提供局部性。数据局部性可降低整体延迟并节省网络资源。
  • 确保脚本原子执行的阻塞语义。
  • 启用 Redis 中缺少的简单功能的组合,或者这些功能太小众而无法成为其中的一部分。

Lua 允许您在 Redis 中运行部分应用程序逻辑。 此类脚本可以跨多个键执行条件更新,可能以原子方式组合几种不同的数据类型。

尽管服务器执行它们,但 Eval 脚本被视为客户端应用程序的一部分,这就是它们未命名、版本控制或持久化的原因。 因此,如果缺少所有脚本,应用程序可能需要随时重新加载(在服务器重新启动、故障转移到副本等之后)。 从版本 7.0 开始, 提供了一种可编程性的替代方法,它允许使用额外的编程逻辑来扩展服务器本身。

脚本参数化

应用程序可以发送这两个完全不同但同时完全相同的脚本:

redis> EVAL "return 'Hello'" 0
"Hello"
redis> EVAL "return 'Scripting!'" 0
"Scripting!"

尽管 Redis 未阻止此操作模式,但由于脚本缓存考虑,它是一种反模式(更多内容见下文)。 您可以参数化它们并传递执行它们所需的任何参数,而不是让应用程序生成相同脚本的细微变体。

以下示例演示了如何通过参数化实现与上述相同的效果:

redis> EVAL "return ARGV[1]" 0 Hello
"Hello"
redis> EVAL "return ARGV[1]" 0 Parameterization!
"Parameterization!"

从脚本与 Redis 交互 

可以通过 ​​​​​​​redis.call() 或 redis.pcall() 从 Lua 脚本调用 Redis 命令。

两者几乎相同。 两者都执行 Redis 命令及其提供的参数(如果这些参数表示格式正确的命令)。 但是,这两个函数之间的区别在于处理运行时错误(例如语法错误)的方式。 调用函数引发的错误将直接返回给执行该函数的客户端。 相反,调用函数时遇到的错误将返回到脚本的执行上下文,以便进行可能的处理。

> EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 foo bar
OK

脚本缓存

每当我们调用 EVAL 时,我们还会在请求中包含脚本的源代码。 重复调用 EVAL 来执行同一组参数化脚本,既浪费了网络带宽,又在 Redis 中产生了一些开销。 当然,节省网络和计算资源是关键,因此,Redis 为脚本提供了缓存机制。

使用EVAL执行的每个脚本都存储在服务器保留的专用缓存中。 缓存的内容按脚本的 SHA1 摘要总和进行组织,因此脚本的 SHA1 摘要总和在缓存中唯一标识它。 您可以通过运行 EVAL 并在之后调用 INFO 来验证此行为。 您会注意到used_memory_scripts_eval 和 number_of_cached_scripts 指标会随着执行的每个新脚本而增长。

如上所述,动态生成的脚本是一种反模式。 在应用程序运行时生成脚本可能会(并且可能会)耗尽主机的内存资源来缓存它们。 相反,脚本应该尽可能通用,并通过其参数提供自定义执行。

通过调用 SCRIPT LOAD 命令并提供其源代码,将脚本加载到服务器的缓存中。 服务器不执行脚本,而只是编译并将其加载到服务器的缓存中。 加载后,可以使用从服务器返回的 SHA1 摘要执行缓存脚本。

redis> SCRIPT LOAD "return 'Immabe a cached script'"
"c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f"
redis> EVALSHA c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f 0
"Immabe a cached script"

在 Redis 中调试 Lua 脚本

从版本 3.2 开始,Redis 包含一个完整的 Lua 调试器,可以 用于使编写复杂 Redis 脚本的任务变得更加简单。

代号为 LDB 的 Redis Lua 调试器具有以下重要功能:

  • 它使用服务器-客户端模型,因此它是一个远程调试器。 Redis 服务器充当调试服务器,而默认客户端为 . 但是,可以按照服务器实现的简单协议来开发其他客户端。redis-cli
  • 默认情况下,每个新的调试会话都是一个分叉会话。 这意味着在调试 Redis Lua 脚本时,服务器不会阻塞,并且可用于开发或并行执行多个调试会话。 这也意味着在脚本调试会话完成后会回滚更改,因此可以使用与上一个调试会话完全相同的 Redis 数据集再次重新启动新的调试会话。
  • 可按需提供备用同步(非分叉)调试模型,以便可以保留对数据集的更改。 在此模式下,服务器会在调试会话处于活动状态时阻止。
  • 支持分步执行。
  • 支持静态和动态断点。
  • 支持将调试后的脚本记录到调试器控制台中。
  • 检查 Lua 变量。
  • 跟踪脚本执行的 Redis 命令。
  • Redis 和 Lua 值的漂亮打印。
  • 无限循环和长执行检测,模拟断点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/816885.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Thingsboard PE 白标的使用

只有专业版支持白标功能。 使用 ThingsBoard Cloud 或安装您自己的平台实例。 一、介绍 ThingsBoard Web 界面提供了简便的操作,让您能够轻松配置您的公司或产品标识和配色方案,无需进行编码工作或重新启动服务。 系统管理员、租户和客户管理员可以根据需要自定义配色方案、…

精通技术写作:如何写出高质量技术文章?

CSDN 的朋友你们好,我是未来,今天给大家带来专栏【程序员博主教程(完全指南)】的第 7 篇文章“如何撰写高质量技术文章”。本文深入探讨了如何写好一篇技术文章。文章给出了好的技术文章的定义和分析,并提供了从选题、…

Day103:漏洞发现-漏扫项目篇Poc开发Rule语法反链判断不回显检测Yaml生成

目录 Xray&Afrog-Poc开发-环境配置&编写流程 Xray-Poc开发-数据回显&RCE不回显&实验室 Afrog-Poc开发-数据回显&RCE不回显&JDNI注入 HTTP/S数据回显Poc开发-CVE-2023-28432 HTTP/S不回显RCE-Poc开发-CVE-2022-30525 HTTP/S不回显JNDI-Poc开发 知…

Upload-labs(Pass-14 - Pass-16)

Pass-14 (图片马,判断文件类型) 图片的格式在防护中通常是不会使用后缀进行判断的依据,文件头是文件开头的一段二进制码,不同类型的图片也就会有不同的二进制头。   JPEG (jpg),文件头:FF D…

比较有用的C++编译错误解决方法

学习VC++时经常会遇到链接错误LNK2001,该错误非常讨厌,因为对于编程者来说,最好改的错误莫过于编译错误,而一般说来发生连接错误时,编译都已通过。产生连接错误的原因非常多,尤其LNK…

便携式污水采样器的工作环境要求

便携式污水采样器的工作环境要求极为严格,以确保其能够准确、稳定地采集和分析水样。首先,该采样器必须在干燥、通风良好的环境中工作,以避免潮湿和高温对其内部电子元件的损害。同时,为了保证采样器的稳定性和精度,工…

【数据结构(六)】队列

❣博主主页: 33的博客❣ ▶️文章专栏分类:数据结构◀️ 🚚我的代码仓库: 33的代码仓库🚚 🫵🫵🫵关注我带你学更多数据结构知识 目录 1.前言2.概念3.队列的使用4.循环队列5.双端队列6.经典习题6.1队列实现栈6.2栈实现队…

MySQL批量修改记录

我们知道MySQL支持批量添加记录,用法也比较简单,不过需要编码需要注意一下,因为很容易搞错 var jargs require(./argspw.json); const mysql require("mysql"); const mysqlpool mysql.createPool({host:jargs.mysqlip,user:jar…

一款挺不错网站维护页面HTML源码

一款挺不错网站维护页面源码,单HTML不需要数据库,上传到你的虚拟机就可以用做维护页面还不错,用处多。。 源码下载 一款挺不错网站维护页面源码

Ajax使用异步对象发送请求简介

​ 文章目录 一、什么是Ajax二、全局刷新和局部刷新二、Ajax中使用XMLHttpRequest对象(Ajax核心步骤)1、创建异步对象2、给异步对象绑定事件3、异步对象的属性 readyState 表示异步对象请求的状态变化4、初始异步请求对象5、使用异步对象发送请求6、jso…

Android DB锁问题

并发访问事务 在Android中使用SQLite数据库时,如果多个线程尝试同时对同一个数据库开启事务,可能会遇到几个问题: 锁定异常 (android.database.sqlite.SQLiteDatabaseLockedException): 如果一个线程已经在数据库上持有一个写事务&#xff0…

LangChain LangServe 学习笔记

LangChain LangServe 学习笔记 0. 引言1. LangServe 概述2. 特性3. 限制4. 安装5. 示例应用程序6. OpenAPI文档7. Python SDK 客户端8. Playground9. 聊天可运行页面 0. 引言 使用 LangServe 可以立即将您的LLM应用程序变成 API 服务器。 LangServe 使用 FastAPI 构建&#x…

three.js(1):three.js简介

1 什么是three.js three.js,一个WebGL引擎,基于JavaScript,可直接运行GPU驱动游戏与图形驱动应用于浏览器。其库提供的特性与API以绘制3D场景于浏览器。 2 下载地址 three.js下载地址:https://github.com/mrdoob/three.js 3 目录介绍 下载…

【题目】【信息安全管理与评估】2022年国赛高职组“信息安全管理与评估”赛项样题5

【题目】【信息安全管理与评估】2022年国赛高职组“信息安全管理与评估”赛项样题5 第一阶段竞赛项目试题 本文件为信息安全管理与评估项目竞赛-第一阶段试题,第一阶段内容包括:网络平台搭建与设备安全防护。 本次比赛时间为180分钟。 介绍 竞赛阶段…

浅谈函数 fscanf/sscanf 和 fprintf/sprintf

目录 一,fprintf 的介绍和使用1. 函数介绍2. 函数使用 二,fscanf 的介绍和使用1. 函数介绍2. 函数使用 三,sprintf 的介绍和使用1. 函数介绍2. 函数使用 四,sscanf 的介绍和使用1,函数介绍2,函数使用 五&am…

SSL Pinning之双向认证

双向认证处理流程 概述获取证书逆向app 获取证书的KeyStore的 key通过jadx 反编译 app 获取证书:frida hook 证书转换命令行转换portecle 工具使用 charles 配置 p12 格式证书 概述 本篇只介绍怎么解决ssl pinning, 不讲ssl/tls 原理。 为了解决ssl pinn…

BCrypt实现信息加密

1.BCrypt的概述 bcrypt 是一种密码哈希函数,通常用于加密密码。它采用了 Blowfish 加密算法的变种,并结合了盐(salt)和密钥延时(key stretching)等技术,以增加密码破解的难度。 2.BCrypt的使用…

RT-Thread 多级目录 scons 构建

前言 RT-Thread 默认使用 scons 进行工程的构建,虽然 RT-Thread BSP 中的 hello world 例程比较简单,实际项目开发,可能源码的工程会由多级目录,如何让多级的目录参与构建? scons 构建时,除了依赖工程的根…

基于R语言实现的beta二项回归模型【理解与实现】

本实验,创建一组使用二项分布模拟的数据(不带额外的随机性),和另一组使用Beta二项分布模拟的数据(引入了随机成功概率 p,从而增加了数据的离散性。 现在假设我们站在上帝视角,有两组不知道分布…

【C语言】结构体structure

【C语言】结构体structure: C语言可以自定义数据类型。结构体是其中一个自定义的数据类型。结构体类型是复杂的数据类型,将多个不同数据按一定功能进行整体封装,用一个名称来给结构体命名。可用typedef为结构体提供别名。关键字struct。结构…