Lua 脚本在 Redis 中的运用-23(Lua 脚本语法教程)

在 Redis 中编写和执行 Lua 脚本

Lua 脚本是在 Redis 中执行自定义逻辑的强大功能,可以直接在 Redis 服务器上执行。这减少了延迟,提高了性能,并能够实现客户端脚本难以或不可能实现的原子操作。通过在 Redis 中嵌入 Lua 脚本,您可以执行复杂的数据操作和业务逻辑,而无需为每个单独的命令承担网络通信的开销。本课将涵盖在 Redis 中编写和执行 Lua 脚本的基础知识,为您提供有效利用这一功能的知识。

Redis 中的 Lua 脚本简介

Redis 使用 Lua 作为其脚本语言。Lua 是一种轻量级、可嵌入的脚本语言,以其速度和简洁性而闻名。Redis 在服务器端执行 Lua 脚本,这意味着脚本直接在 Redis 实例内运行。这有几个优势:

  • 减少延迟: 避免了客户端和服务器之间为每个命令进行的网络往返。
  • 原子性: 确保整个脚本作为一个单一的原子操作执行,防止竞态条件和数据不一致。这类似于事务的工作方式,但增加了自定义逻辑的灵活性。
  • 代码复用性: 脚本可以存储在 Redis 中,并在多个客户端和会话之间复用。
  • 简化逻辑: 复杂操作可以封装在单个脚本中,使客户端代码更清晰且易于维护。

为什么是 Lua?

Lua 被选为 Redis 脚本语言的原因是:

  • 简洁性: Lua 的语法相对容易学习,特别是对于那些熟悉其他脚本语言的人。
  • 速度: Lua 以其性能而闻名,适合在 Redis 中进行服务器端执行。
  • 可嵌入性: Lua 被设计为可以轻松嵌入到其他应用程序中,使其成为 Redis 的理想选择。
  • 安全性: Redis 为 Lua 脚本提供了一个受保护的环境,阻止它们访问文件系统或进行网络调用(某些需要显式配置的例外情况除外)。

为 Redis 编写 Lua 脚本

Redis 中的 Lua 脚本通过一个特殊的 redis 对象与 Redis 数据存储交互。该对象提供了与标准 Redis 命令相对应的函数。

基础 Lua 语法

在深入 Redis 特定脚本之前,让我们复习一些基本的 Lua 语法:

  • 变量: 变量无需指定类型声明。

    local my_variable = "Hello, Redis!"
    local my_number = 123
    
  • 数据类型: Lua 支持多种数据类型,包括:

    • string: 文本数据。
    • number: 数值数据(整数和浮点数)。
    • booleantrue 或 false.
    • table: 一种通用的数据结构,可以用作数组或字典。
    • nil: 表示值不存在。
  • 注释: 使用 -- 进行单行注释。

    -- This is a comment
    
  • 控制流: Lua 提供了标准的控制流语句:

    • if-then-else:

      local x = 10
      if x > 5 then-- Code to execute if x is greater than 5
      else-- Code to execute otherwise
      end
      
    • for 循环:

      for i = 1, 10 do-- Code to execute 10 times
      end
      
    • while 循环:

      local i = 1
      while i <= 10 do-- Code to execute while i is less than or equal to 10i = i + 1
      end
      
  • 功能: 功能使用 function 关键字定义。

    function add(a, b)return a + b
    endlocal result = add(5, 3) -- result will be 8
    

redis 对象

redis 对象是从 Lua 脚本中与 Redis 交互的主要接口。它提供了一个 call() 函数,允许你执行 Redis 命令。

local value = redis.call('GET', 'mykey')

在这个例子中,redis.call('GET', 'mykey') 对键 mykey 执行 GET 命令并返回值。

示例:递增计数器

这是一个简单的 Lua 脚本,用于增加存储在 Redis 中的计数器:

-- Get the current value of the counter
local current_value = redis.call('GET', KEYS[1])-- If the counter doesn't exist, initialize it to 0
if not current_value thencurrent_value = 0
end-- Increment the counter
local new_value = tonumber(current_value) + 1-- Set the new value in Redis
redis.call('SET', KEYS[1], new_value)-- Return the new value
return new_value

在这个脚本中:

  • KEYS[1] 指的是传递给脚本的第一个键。 Redis 中的 Lua 脚本以数组形式接收键和参数。 KEYS 是一个包含键名称的数组,而 ARGV 是一个包含参数值的数组。
  • redis.call('GET', KEYS[1]) 获取计数器的当前值。
  • tonumber(current_value) 将从 Redis 获取的值(始终是字符串)转换为数字。
  • redis.call('SET', KEYS[1], new_value) 设置计数器的新值。
  • 脚本返回计数器的新值。

示例:账户间原子转账

考虑一个需要原子方式在两个账户间转账的场景。这可以使用 Lua 脚本实现:

-- KEYS[1]: Source account key
-- KEYS[2]: Destination account key
-- ARGV[1]: Amount to transferlocal source_balance = tonumber(redis.call('GET', KEYS[1]))
local destination_balance = tonumber(redis.call('GET', KEYS[2]))
local amount = tonumber(ARGV[1])if source_balance and source_balance >= amount thenredis.call('DECRBY', KEYS[1], amount)redis.call('INCRBY', KEYS[2], amount)return {1, "OK"} -- Success
elsereturn {0, "Insufficient funds"} -- Failure
end

在这个脚本中:

  • KEYS[1] 是源账户余额的密钥。
  • KEYS[2] 是目标账户余额的密钥。
  • ARGV[1] 是转账金额。
  • 脚本检查源账户是否有足够的资金。
  • 如果资金充足,它会减少源账户余额并增加目标账户余额。
  • 脚本返回成功或失败消息。

Lua 脚本的错误处理

Lua 脚本在执行过程中可能会遇到错误。优雅地处理这些错误非常重要。如果 Lua 脚本遇到错误,Redis 将自动撤销脚本所做的任何更改,确保原子性。

您可以使用 pcall(受保护调用)来捕获脚本中的错误:

local status, result = pcall(function()-- Your code herereturn redis.call('GET', 'nonexistent_key')
end)if status then-- Code to handle successful executionif result then-- Process the resultend
else-- Code to handle errorsredis.log(redis.LOG_WARNING, "Error: " .. result)
end

在这个例子中:

  • pcall 在受保护的环境中执行匿名函数。
  • 如果函数执行成功,status 将为 trueresult 将包含函数的返回值。
  • 如果函数遇到错误,status 将是 false,而 result 将包含错误信息。
  • redis.log 用于将错误信息记录到 Redis 日志中。

在 Redis 中执行 Lua 脚本

在 Redis 中执行 Lua 脚本主要有两种方式:

  1. EVAL: 通过将脚本代码直接传递给 Redis 服务器来执行脚本。
  2. EVALSHA: 通过将脚本的 SHA1 哈希值传递给 Redis 服务器来执行脚本。这需要首先使用 SCRIPT LOAD 命令将脚本加载到 Redis 脚本缓存中。

使用 EVAL

EVAL 命令接受以下参数:

EVAL script numkeys key [key ...] arg [arg ...]
  • script : 要执行的 Lua 脚本。
  • numkeys: 脚本将访问的键的数量。这些键必须立即作为参数传递给 numkeys
  • key [key ...]: 脚本将访问的键。
  • arg [arg ...]: 脚本可使用的额外参数。

示例:

redis-cli EVAL "return redis.call('GET', KEYS[1])" 1 mykey

该命令执行一个 Lua 脚本,获取键 mykey 的值。1 表示该脚本访问一个键,即 mykey

使用 EVALSHA

EVALSHA 命令接受以下参数:

EVALSHA sha1 numkeys key [key ...] arg [arg ...]
  • sha1: Lua 脚本的 SHA1 哈希值。
  • numkeys: 脚本将访问的键的数量。
  • key [key ...]: 脚本将访问的键。
  • arg [arg ...]: 脚本可使用的额外参数。

在使用 EVALSHA 之前,你必须使用 SCRIPT LOAD 命令将脚本加载到 Redis 脚本缓存中:

redis-cli SCRIPT LOAD "return redis.call('GET', KEYS[1])"

此命令返回脚本的 SHA1 哈希值。然后您可以使用 EVALSHA

redis-cli EVALSHA <sha1_hash> 1 mykey

EVALSHA 的优点

  • 降低带宽:EVALSHA 仅发送脚本的 SHA1 哈希值,这比脚本本身小得多。这减少了网络带宽,特别是对于大型脚本。
  • 性能提升: Redis 可以缓存编译后的脚本,这可以提升性能,特别是如果脚本执行频繁的话。

示例:使用 EVAL 和 EVALSHA

让我们重新审视计数器自增的例子,并使用 EVAL 和 EVALSHA 来执行它。

使用 EVAL:

redis-cli EVAL "local current_value = redis.call('GET', KEYS[1]) if not current_value then current_value = 0 end local new_value = tonumber(current_value) + 1 redis.call('SET', KEYS[1], new_value) return new_value" 1 mycounter

使用 EVALSHA:

首先,加载脚本:

redis-cli SCRIPT LOAD "local current_value = redis.call('GET', KEYS[1]) if not current_value then current_value = 0 end local new_value = tonumber(current_value) + 1 redis.call('SET', KEYS[1], new_value) return new_value"

这将返回脚本的 SHA1 哈希值(例如, a7e5b98b9d4a8a2a3b1c2c3d4e5f6a7b8c9d0e1f )。

然后,使用 EVALSHA 执行脚本:

redis-cli EVALSHA a7e5b98b9d4a8a2a3b1c2c3d4e5f6a7b8c9d0e1f 1 mycounter

管理脚本

Redis 提供了几个用于管理 Lua 脚本的命令:

  • SCRIPT LOAD: 将脚本加载到脚本缓存中,并返回其 SHA1 哈希。
  • SCRIPT EXISTS: 检查是否存在具有给定 SHA1 哈希值的脚本在脚本缓存中。
  • SCRIPT FLUSH: 从脚本缓存中移除所有脚本。
  • SCRIPT KILL: 终止当前正在执行的脚本。这只有在脚本被标记为可中断时才可能实现(这需要 Redis 版本 7 或更高版本,并在脚本中使用 redis.yield() 函数)。

实际练习

  1. 实现一个速率限制器: 编写一个 Lua 脚本,实现一个简单的速率限制器。该脚本应接受一个键(例如,用户 ID)和一个限制值作为参数。它应在指定的时间窗口内允许一定数量的请求。如果超出限制,该脚本应返回错误。使用 Redis 列表来存储请求的时间戳。
  2. 原子增量带过期功能: 编写一个 Lua 脚本,原子性地递增一个计数器,并在该键不存在时设置过期时间。这可以用于跟踪临时事件。
  3. 实现一个简单的布隆过滤器: 编写一个 Lua 脚本,实现一个简化的布隆过滤器。该脚本应接受一个键(布隆过滤器的名称)和一个要插入的值。它应多次对值进行哈希处理,并在布隆过滤器中设置相应的位。

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

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

相关文章

从零实现本地语音识别(FunASR)

FunASR 是达摩院开源的综合性语音处理工具包&#xff0c;提供语音识别&#xff08;ASR&#xff09;、语音活动检测&#xff08;VAD&#xff09;、标点恢复&#xff08;PUNC&#xff09;等全流程功能&#xff0c;支持多种主流模型&#xff08;如 Paraformer、Whisper、SenseVoic…

deepseek开源资料汇总

参考&#xff1a;DeepSeek“开源周”收官&#xff0c;连续五天到底都发布了什么? 目录 一、首日开源-FlashMLA 二、Day2 DeepEP 三、Day3 DeepGEMM 四、Day4 DualPipe & EPLB 五、Day5 3FS & Smallpond 总结 一、首日开源-FlashMLA 多头部潜在注意力机制&#x…

【C++ Qt】认识Qt、Qt 项目搭建流程(图文并茂、通俗易懂)

每日激励&#xff1a;“不设限和自我肯定的心态&#xff1a;I can do all things。 — Stephen Curry” 绪论​&#xff1a; 本章将开启Qt的学习&#xff0c;Qt是一个较为古老但仍然在GUI图形化界面设计中有着举足轻重的地位&#xff0c;因为它适合嵌入式和多种平台而被广泛使用…

AI应用 Markdown 渲染对比与原生实现方案

DeepSeek、豆包、腾讯元宝、ChatGPT 渲染实现对比表 产品解析方式渲染引擎/库UI 组件架构Markdown支持范围流程图/导图支持扩展架构及裁剪流式解析渲染DeepSeek原生解析&#xff08;非WebView&#xff09;采用 CommonMark 标准解析器&#xff08;推测使用 Markwon 库&#xff…

Ubuntu20.04系统安装,使用系统盘安装

1、系统安装 Ubuntu20.04系统安装&#xff0c;使用系统盘安装 查看ubuntu系统版本 lsb_release -a&#xff1a;显示发行版名称、版本号及代号 (base) rootai-System-Product-Name:/media/ai/wh/clash-for-linux-master# lsb_release -a No LSB modules are available. Distri…

(自用)Java学习-5.19(地址管理,三级联动,预支付)

1. 地址管理模块 地址展示 前端&#xff1a;通过 showAddress() 发起 Ajax GET 请求&#xff0c;动态渲染地址列表表格&#xff0c;使用 #{tag}、#{name} 等占位符替换真实数据。 后端&#xff1a; 控制器层调用 AddressService&#xff0c;通过 AddressMapper 查询用户地址数…

Spring 循环依赖:从原理到解决方案的全面解析

Spring 循环依赖&#xff1a;从原理到解决方案的全面解析 一、循环依赖的定义与分类 1. 什么是循环依赖&#xff1f; 在 Spring 框架中&#xff0c;循环依赖指的是多个 Bean 之间形成了依赖闭环。例如&#xff1a; Bean A 依赖 Bean BBean B 依赖 Bean CBean C 又依赖 Bean…

n 阶矩阵 A 可逆的充分必要条件是 ∣ A ∣ ≠ 0

n 阶矩阵 A 可逆的充分必要条件是 ∣ A ∣ ≠ 0 |A| \neq 0 ∣A∣0 的几何意义 1. 行列式的几何意义回顾 行列式 ∣ A ∣ |A| ∣A∣&#xff08;或 det ⁡ ( A ) \det(A) det(A)&#xff09;表示矩阵 A A A 所对应的线性变换对空间的体积缩放因子&#xff1a; ∣ A ∣ &…

Rockey Linux 安装ffmpeg

1.环境准备 Rockey linux 9.2 ffmpeg 静态资源包 这个是我自己的&#xff1a; https://download.csdn.net/download/liudongyang123/90920340https://download.csdn.net/download/liudongyang123/90920340 这个是官网的 Releases BtbN/FFmpeg-Builds GitHub 以上两个资…

wordcount在集群上的测试

1.将louts.txt文件从cg计算机复制到master节点上面&#xff0c;存放在/usr/local/hadoop 需要输入密码&#xff1a;83953588abc scp /root/IdeaProjects/mapReduceTest/lotus.txt root172.18.0.2:/usr/local/hadoop /WordCountTest/input 2.将lotus.txt文件从master这台机器…

AI+制造:中小企业的低成本智能化转型

文章内容过长&#xff0c;可以考虑直接跳转到文章末尾查看概要图 在制造业竞争日益激烈的今天&#xff0c;中小企业正面临着前所未有的挑战&#xff1a;人力成本持续攀升、能源消耗居高不下、质量控制难度增加。与此同时&#xff0c;数字化转型已成为行业共识&#xff0c;但高…

Linux C/C++编程 —— 线程技术总结

一、线程基本概念 线程是进程内的一个执行单元&#xff0c;多个线程共享进程的资源&#xff08;如内存、文件描述符等&#xff09;&#xff0c;但每个线程拥有自己的寄存器、栈等。与进程相比&#xff0c;线程的创建、切换开销较小&#xff0c;能更高效地利用 CPU 资源。 二、…

Femap许可证与网络安全策略

随着科技的快速发展&#xff0c;网络安全问题已成为各行各业关注的焦点。在电磁仿真领域&#xff0c;Femap作为一款领先的软件&#xff0c;其许可证的安全性和网络策略的重要性不言而喻。本文将探讨Femap许可证与网络安全策略的关系&#xff0c;确保您的电磁仿真工作能够在一个…

深度解析:SQLynx 如何筑牢数据库安全防线​

在数据驱动业务发展的时代&#xff0c;数据库作为企业核心资产的 “保险箱”&#xff0c;其安全性至关重要。一旦数据库遭遇攻击、数据泄露或被误操作&#xff0c;将给企业带来不可估量的损失。而 SQLynx 作为一款专注于数据库安全管理的工具&#xff0c;凭借其多项创新技术与功…

更新时间相差8个小时

下面的java代码在updateFill方法里面生成的modifiedTime时间是当前时间是正确的&#xff0c;为什么到service层testCommonFieldAutoUpdate方法里面去更新的时候modifiedTime就差8个小时呢&#xff1f;代码如下所示&#xff1a; Slf4j Component public class MpMetaObjectHand…

Windows逆向工程提升之IMAGE_TLS_DIRECTORY

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 TLS的作用 TLS的实现 静态 TLS​​ 动态 TLS​​ 内部实现 回调机制 TLS Directory 的结构 TLS的作用 TLS (Thread Local Storage) 是一种用于为多线程应用程序提供线程独立存储空…

云效流水线Flow使用记录

概述 最近在频繁使用阿里云云效的几款产品&#xff0c;如流水线。之前写过一篇&#xff0c;参考云效流水线缓存问题。 这篇文章来记录更多问题。 环境变量 不管是云效流水线Flow还是应用交付AppStack&#xff08;基于流水线&#xff0c;后文不再赘述&#xff09;&#xff0…

Android中获取控件尺寸进阶方案

在Android开发中,很多场景都需要获取控件(View)的宽高信息,比如动态布局调整、动画效果实现等。然而,直接在Activity的onCreate()中调用控件的getWidth()或getHeight()`方法,得到结果却是0,因为控件还没完成布局测量。 本文总结了几种获取控件大小的实用方法,并对各方…

android 输入系统

一、输入系统的核心角色与分层架构 Android 输入系统的本质是桥梁&#xff1a;一端连接硬件驱动产生的原始事件&#xff0c;另一端将事件精准派发给应用窗口。整个过程涉及三层架构和多个关键组件&#xff0c;可类比为 “快递分拣系统”&#xff1a; 1. 硬件与内核层&#xf…

ubuntu中,c和c+程序,预编译、编译、链接和运行命令

目录 安装编译器一.c编译运行&#xff08;粗暴简单&#xff09;1.编写 C 程序&#xff1a;2. 预处理&#xff1a;3.编译&#xff1a;4. 汇编&#xff1a;5. 链接&#xff1a;6.运行 二.c编译运行&#xff08;粗暴简单&#xff09;1.编写 C 程序&#xff1a;2.预处理&#xff1a…