Redis进阶五之妙用Lua脚本(redis-lua)
mhr18 2024-10-26 10:52 37 浏览 0 评论
Redis使用Lua的优势
(1) 原子操作: Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说在编写脚本的过程中无需担心会出现竞态条件,也就无需使用事务。事务可以完成的所有功能都可以用脚本来实现。
(2) 复用: 客户端发送的脚本会永久存储在Redis中,这就意味着其他客户端(可以是其他语言开发的项目)可以复用这一脚本而不需要使用代码完成同样的逻辑。
(3) 速度快:见 与其它语言的性能比较, 还有一个 JIT编译器可以显著地提高多数任务的性能; 对于那些仍然对性能不满意的人, 可以把关键部分使用C实现, 然后与其集成, 这样还可以享受其它方面的好处。
Redis Lua脚本的应用场景
(1)复杂查询:对于一些复杂的查询需求,使用Lua脚本可以快速地实现,避免了在客户端进行数据处理的麻烦。
(2)计算逻辑:对于一些需要进行计算逻辑的场景,即使在Redis中没有提供相应的计算命令,也可以通过Lua脚本实现自定义的计算逻辑。
(3)事务操作:Lua脚本可以保证一组Redis命令的原子性,这使得在Redis上实现事务操作成为可能。
(4)实时统计:Lua脚本可以实时统计Redis中的数据,例如计算实时UV、PV等数据。
Lua简述
Lua 语言是在1993年由巴西一个大学研究小组发明,其设计目标是作为嵌入式程序移植到其他应用程序,它是由C语言实现的,虽然简单小巧但是功能强大,所以许多应用程序都选用它作为脚本语言,尤其是在游戏领域,例如大名鼎鼎的暴雪公司将Lua语言引入到“魔兽世界“这款游戏中,Rovio公司将Lua语言作为“愤怒的小鸟”这款火爆游戏的关卡升级引擎,Web服务器Nginx将Lua语言作为扩展,增强自身功能。Redis将Lua作为脚本语言可帮助开发者定制自己的Redis命令,在这之前,必须修改源代码。在介绍如何在Redis中使用Lua脚本之前,有必要对Lua语言的使用做一个基本介绍。
工欲善其事,必先利其器,我们先介绍 Lua 环境安装 和 简单使用。
Lua 环境安装
Linux 系统上安装
Linux & Mac上安装 Lua 非常简单,只需要下载源码包并在终端解压编译即可,本文使用了5.3.0版本进行安装:
curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz
tar zxf lua-5.3.0.tar.gz
cd lua-5.3.0
make linux test
make install
Mac OS X 系统上安装
curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz
tar zxf lua-5.3.0.tar.gz
cd lua-5.3.0
make macosx test
make install
安装参考
$ cd lua-5.3.0
$ ls
Makefile README doc src
$ make macosx test
cd src && /Library/Developer/CommandLineTools/usr/bin/make macosx
/Library/Developer/CommandLineTools/usr/bin/make all SYSCFLAGS="-DLUA_USE_MACOSX" SYSLIBS="-lreadline" CC=cc
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lapi.o lapi.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lcode.o lcode.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lctype.o lctype.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o ldebug.o ldebug.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o ldo.o ldo.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o ldump.o ldump.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lfunc.o lfunc.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lgc.o lgc.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o llex.o llex.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lmem.o lmem.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lobject.o lobject.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lopcodes.o lopcodes.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lparser.o lparser.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lstate.o lstate.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lstring.o lstring.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o ltable.o ltable.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o ltm.o ltm.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lundump.o lundump.c
lundump.c:233:33: warning: adding 'int' to a string does not append to the string [-Wstring-plus-int]
checkliteral(S, LUA_SIGNATURE + 1, "not a"); /* 1st char already checked */
~~~~~~~~~~~~~~^~~
lundump.c:233:33: note: use array indexing to silence this warning
checkliteral(S, LUA_SIGNATURE + 1, "not a"); /* 1st char already checked */
^
& [ ]
1 warning generated.
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lvm.o lvm.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lzio.o lzio.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lauxlib.o lauxlib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lbaselib.o lbaselib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lbitlib.o lbitlib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lcorolib.o lcorolib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o ldblib.o ldblib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o liolib.o liolib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lmathlib.o lmathlib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o loslib.o loslib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lstrlib.o lstrlib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o ltablib.o ltablib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lutf8lib.o lutf8lib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o loadlib.o loadlib.c
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o linit.o linit.c
ar rcu liblua.a lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o lauxlib.o lbaselib.o lbitlib.o lcorolib.o ldblib.o liolib.o lmathlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o loadlib.o linit.o
ranlib liblua.a
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o lua.o lua.c
cc -o lua lua.o liblua.a -lm -lreadline
cc -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX -c -o luac.o luac.c
cc -o luac luac.o liblua.a -lm -lreadline
src/lua -v
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
$ make install
cd src && mkdir -p /usr/local/bin /usr/local/include /usr/local/lib /usr/local/man/man1 /usr/local/share/lua/5.3 /usr/local/lib/lua/5.3
mkdir: /usr/local/include: Permission denied
mkdir: /usr/local/man/man1: Permission denied
mkdir: /usr/local/share/lua/5.3: Permission denied
mkdir: /usr/local/lib/lua/5.3: Permission denied
make: *** [install] Error 1
a123456@luludeMBP-2 lua-5.3.0 % sudo make install
Password:
cd src && mkdir -p /usr/local/bin /usr/local/include /usr/local/lib /usr/local/man/man1 /usr/local/share/lua/5.3 /usr/local/lib/lua/5.3
cd src && install -p -m 0755 lua luac /usr/local/bin
cd src && install -p -m 0644 lua.h luaconf.h lualib.h lauxlib.h lua.hpp /usr/local/include
cd src && install -p -m 0644 liblua.a /usr/local/lib
cd doc && install -p -m 0644 lua.1 luac.1 /usr/local/man/man1
入门案例
接下来我们创建一个 HelloWorld.lua 文件,代码如下:
print("Hello World!")
执行以下命令:
$ lua HelloWorld.lua
输出结果为:
Hello World!
数据类型及其逻辑处理
Lua 语言提供了如下几种数据类型:booleans 布尔,numbers 数值,strings 字符串,tables 表格,和许多高级语言相比,相对简单。下面将结合例子对Lua的基本数据类型和逻辑处理进行说明。
字符串
下面定义一个字符串类型的数据:
local string val = "world"
其中local 代表 val 是一个局部变量,如果没有loacl 代表是全局变量。print 函数可以打印出变量的值,例如下面代码将打印 world , 其中 "--" 是Lua语言的注释。
print(val) --结果是 "world"
Lua 语言详细学习,请参考:「链接」
Redis Lua执行流程图
Redis eval 命令
Redis eval 命令使用 Lua 解释器执行脚本。
语法
eval 脚本内容 key个数 key列表 参数列表
redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...]
参数说明:
script: 参数是一段 Lua 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数。
numkeys: 用于指定键名参数的个数。
key [key ...]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
arg [arg ...]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。
实例
127.0.0.1:6379>
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],KEYS[2])" 2 name yyp
OK
127.0.0.1:6379> eval "return redis.call('get',KEYS[1])" 1 name
"yyp"
127.0.0.1:6379>
eval 执行流程
Redis evalsha 命令
除了使用eval,Redis 还提供了 evalsha 命令来执行 Lua 脚本。首先要将Lua 脚本加载到Redis 服务器,得到该脚本的SHA1 效验和,evalsha 命令使用 SHA1 作为参数可以直接执行对应的Lua脚本,避免每次发送Lua脚本的开销。这样客户端就不需要每次执行脚本内容,而脚本也会常驻在服务端,脚本功能得到了复用。
加载脚本:script load 命令可以将脚本内容加载到Redis内存中,例如下面将lua_get.lua加载到Redis中,得到SHA1为 "52da8c7de39385e305fb1af2a8ffd21534af996f"。
lua_get.lua脚本内容
return redis.call('get','name')
script load 命令执行
$ ./redis-cli -p 6379 script load "$(cat lua_get.lua)"
"52da8c7de39385e305fb1af2a8ffd21534af996f"
$
执行脚本:evalsha 的使用方法如下,参数使用 SHA1 值,执行逻辑和eval一致。
语法
evalsha 脚本sha1值 key个数 key列表 参数列表
实例
127.0.0.1:6379> evalsha 52da8c7de39385e305fb1af2a8ffd21534af996f 0
"yyp"
127.0.0.1:6379>
evalsha 执行流程
Lua的Redis API
Lua可以使用redis.call 函数实现对 Redis 的访问,例如下面代码Lua使用 redis.call调用了 Redis的 set 和 get 操作。
redis.call("set","hello","world")
redis.call("get","hell0"
放在 Redis 的执行效果如下:
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],KEYS[2])" 2 hello world
OK
127.0.0.1:6379> eval "return redis.call('get',KEYS[1])" 1 hello
"world"
127.0.0.1:6379>
除此之外 Lua 还可以使用 redis.pcall 函数实现对 Redis 的调用,redis.call 和 redis.pcall 的不同在于,如果 redis.call 执行失败,那么脚本执行结果会直接返回错误,而redis.pcall 会忽略错误继续执行脚本,所以在实际开发中要根据具体的应用场景进行函数的选择。
相关推荐
- 如何检查 Linux 服务器是物理服务器还是虚拟服务器?
-
在企业级运维、故障排查和性能调优过程中,准确了解服务器的运行环境至关重要。无论是物理机还是虚拟机,都存在各自的优势与限制。在很多场景下,尤其是当你继承一台服务器而不清楚底层硬件细节时,如何快速辨识它是...
- 第四节 Windows 系统 Docker 安装全指南
-
一、Docker在Windows上的运行原理(一)架构限制说明Docker本质上依赖Linux内核特性(如Namespaces、Cgroups等),因此在Windows系统上无法直...
- C++ std:shared_ptr自定义allocator引入内存池
-
当C++项目里做了大量的动态内存分配与释放,可能会导致内存碎片,使系统性能降低。当动态内存分配的开销变得不容忽视时,一种解决办法是一次从操作系统分配一块大的静态内存作为内存池进行手动管理,堆对象内存分...
- Activiti 8.0.0 发布,业务流程管理与工作流系统
-
Activiti8.0.0现已发布。Activiti是一个业务流程管理(BPM)和工作流系统,适用于开发人员和系统管理员。其核心是超快速、稳定的BPMN2流程引擎。Activiti可以...
- MyBatis动态SQL的5种高级玩法,90%的人只用过3种
-
MyBatis动态SQL在日常开发中频繁使用,但大多数开发者仅掌握基础标签。本文将介绍五种高阶技巧,助你解锁更灵活的SQL控制能力。一、智能修剪(Trim标签)应用场景:动态处理字段更新,替代<...
- Springboot数据访问(整合Mybatis Plus)
-
Springboot整合MybatisPlus1、创建数据表2、引入maven依赖mybatis-plus-boot-starter主要引入这个依赖,其他相关的依赖在这里就不写了。3、项目结构目录h...
- 盘点金州勇士在奥克兰13年的13大球星 满满的全是...
-
见证了两个月前勇士与猛龙那个史诗般的系列赛后,甲骨文球馆正式成为了历史。那个大大的红色标志被一个字母一个字母地移除,在周四,一切都成为了过去式。然而这座,别名为“Roaracle”(译注:Roar怒吼...
- Mybatis入门看这一篇就够了(mybatis快速入门)
-
什么是MyBatisMyBatis本是apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为M...
- Springboot数据访问(整合druid数据源)
-
Springboot整合druid数据源基本概念SpringBoot默认的数据源是:2.0之前:org.apache.tomcat.jdbc.pool.DataSource2.0及之后:com.z...
- Linux 中的 "/etc/profile.d" 目录有什么作用 ?
-
什么是/etc/profile.d/目录?/etc/profile.d/目录是Linux系统不可或缺的一部分保留配置脚本。它与/etc/profile文件相关联,这是一个启动脚本,该脚...
- 企业数据库安全管理规范(企业数据库安全管理规范最新版)
-
1.目的为规范数据库系统安全使用活动,降低因使用不当而带来的安全风险,保障数据库系统及相关应用系统的安全,特制定本数据库安全管理规范。2.适用范围本规范中所定义的数据管理内容,特指存放在信息系统数据库...
- Oracle 伪列!这些隐藏用法你都知道吗?
-
在Oracle数据库中,有几位特殊的“成员”——伪列,它们虽然不是表中真实存在的物理列,但却能在数据查询、处理过程中发挥出意想不到的强大作用。今天给大家分享Oracle伪列的使用技巧,无论...
- Oracle 高效处理数据的隐藏神器:临时表妙用
-
各位数据库搬砖人,在Oracle的代码世界里闯荡,处理复杂业务时,是不是总被数据“搅得头大”?今天给大家安利一个超实用的隐藏神器——临时表!当你需要临时存储中间计算结果,又不想污染正式数据表...
- Oracle 数据库查询:多表查询(oracle多表关联查询)
-
一、多表查询基础1.JOIN操作-INNERJOIN:返回两个表中满足连接条件的匹配行,不保留未匹配数据。SELECTa.emp_id,b.dept_nameFROMempl...
- 一文掌握怎么利用Shell+Python实现多数据源的异地备份程序
-
简介:在信息化时代,数据安全和业务连续性已成为企业和个人用户关注的焦点。无论是网站数据、数据库、日志文件,还是用户上传的文档、图片等,数据一旦丢失,损失难以估量。尤其是当数据分布在多个不同的目录、服务...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 如何检查 Linux 服务器是物理服务器还是虚拟服务器?
- 第四节 Windows 系统 Docker 安装全指南
- C++ std:shared_ptr自定义allocator引入内存池
- Activiti 8.0.0 发布,业务流程管理与工作流系统
- MyBatis动态SQL的5种高级玩法,90%的人只用过3种
- Springboot数据访问(整合Mybatis Plus)
- 盘点金州勇士在奥克兰13年的13大球星 满满的全是...
- Mybatis入门看这一篇就够了(mybatis快速入门)
- Springboot数据访问(整合druid数据源)
- Linux 中的 "/etc/profile.d" 目录有什么作用 ?
- 标签列表
-
- oracle位图索引 (74)
- oracle基目录 (50)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (53)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)