Redis Lua脚本好处、Redis执行Lua的两种方式、Redis缓存Lua脚本、Redis Lua原子性验证、Lua脚本IP限流、Lua脚本自乘

  • Redis cli两种运行方式
  • Redis Lua脚本好处
  • Redis执行Lua的两种方式
    • 1.交互式执行Lua
      • Redis客户端执行Lua脚本命令
      • Lua脚本中怎么执行Redis命令
    • 2.命令式执行Lua
      • Lua脚本文件注释编写
      • Lua脚本文件编写
      • 命令式执行简单Lua脚本文件
  • Lua脚本案例
    • 带参数的:Lua脚本实现IP限流
    • 不带参数的:Lua脚本实现自乘
  • Redis缓存Lua脚本
  • Redis Lua原子性验证

Redis cli两种运行方式

redis cli两种运行方式:交互式、命令式

Redis Lua脚本好处

Lua脚本是Redis 2.6之后引入的

  1. 批量执行命令
  2. 原子性
  3. 操作集合的复用

Lua脚本实现原子性的原理,就是执行一个Lua脚本的时候,里面有多个命令,这个时候会阻塞其它任何命令的执行和其它任何客户端的请求,也就是说,执行Lua的时候,只会执行Lua里的命令,以此来达到Lua里的多条命令成为一个整体来执行。

Redis执行Lua的两种方式

一种是连接客户端,交互式执行Lua,一种是编写Lua脚本文件,用命令行的方式执行。
有很多坑,先了解一下
1、用交互式方式(redis-cli进入客户端再执行命令)执行eval命令的时候,后面不能跟lua脚本(只能跟Lua语言),会报错如下:

# test2.lua 0: test2.lua脚本路径 0参数个数
127.0.0.1:6379> eval test2.lua 0
(error) ERR Error compiling script (new function): user_script:1: '=' expected near '<eof>'

2、用命令式方式(redis-cli后面跟执行的命令)执行命令的时候,如果你的Redis设置了密码,在连接的时候会输出认证提示

# -r表示重复执行;这里连接认证之后执行3次ping命令
[pdx_haokai@VM-0-3-centos bin]$ redis-cli -a Pass9612 -r 3 ping
# 可以看到会有Warning: Using a password with '-a' or '-u'输出
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
PONG
PONG
PONG

这种输出会影响Lua脚本取值,但不影响正常运行
需要redis输入密码时去除提示Warning: Using a password提示,拼接如下命令可以去除提示

2>/dev/null

1.交互式执行Lua

Redis客户端执行Lua脚本命令

EVAL script numkeys [key [key ...]] [arg [arg ...]]
  • EVAL代表执行Lua语言的命令
  • script代表Lua脚本内容,内容引号引起来,建议用双引号
  • numkeys表示参数中有多少个key,需要注意Redis中key是从1开始的,如果没有key的参数,写0
  • [key [key …]]是key作为参数传递给Lua语言,也可以不填,但是需要[key [key …]]和numkeys的个数对应起来
  • [arg [arg …]]这些参数传递给Lua语言,它们可填可不填
# "return '2234'"是Lua脚本内容,numkeys为0
127.0.0.1:6379> eval "return '2234'" 0
"2234"
127.0.0.1:6379>

Lua脚本中怎么执行Redis命令

Lua的使用,就是为了能够执行Redis命令
通过redis.call来执行Redis命令

redis.call(command,key[param1,param2,...])
  • command是Redis命令,包括set、get、del等
  • key是被操作的键
  • key[param1,param2,…]代表给key的参数
# --eval:用于执行lua脚本
127.0.0.1:6379> eval "return redis.call('set','key001','567')" 0
OK
127.0.0.1:6379> get key001
"567"
# KEYS[1],ARGV[1]中KEYS和ARGV是形参名,大写不能改
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 key002 789
OK
127.0.0.1:6379> get key002
"789"
# KEYS[1],ARGV[1]中KEYS和ARGV是形参名,KEYS改为小写,直接不识别参数
127.0.0.1:6379> eval "return redis.call('set',keys[1],ARGV[1])" 1 key002 710
(error) ERR Error running script (call to f_a9e55ca6c2982f8ce741628e9c3df21db06f4edb): @enable_strict_lua:15: user_script:1: Script attempted to access unexisting global variable 'keys'

这里需要注意下,如果用交互式方式(redis-cli进入客户端再执行命令)执行eval命令的时候,后面不能跟lua脚本,是不识别的,会报错如下:

127.0.0.1:6379> eval test2.lua 0
(error) ERR Error compiling script (new function): user_script:1: '=' expected near '<eof>'

2.命令式执行Lua

Lua脚本文件注释编写

在Lua程序中,有两种注释,单行注释和多行注释

1.单行注释
单行注释可以注释整行或者一行中的一部分。他一般不用于连续多行的注释文本,当然,和其他语言一样,也可以用来注释掉多行连续的代码,例如:

-- 注释方式1
if x > 1 thenreturn true;  -- 注释
elsereturn false; -- 注释
end-- 注释方式2
-- if x > 1 then-- return true;
--else-- return false;
-- end

在以上示例中,注释方式2是不规范的,如果要注释多行,尽量的采用多行注释。

2.多行注释
多行注释一般用于连续多行注释,当然也可以用于单行注释,例如:

--[[
if x > 1 then-- 注释1
else-- 注释2
end
--]]

Lua的多行注释是可以嵌套的,这是其他语言没有的优点。

Lua脚本文件编写

Lua语言中执行Redis命令,用起来不是很方便,所以都会使用去执行Lua脚本文件

# 编写操作Redis命令
redis.call(command,key[param1,param2,...])# 调用Lua脚本
redis-cli --eval 脚本名称 参数个数 参数1 参数2 ......

新建一个测试脚本:test.lua

vim test.lua

内容如下

--[[
多行注释:
这是一个测试脚本
--]]
redis.call('set','key1','123')--放入一个数字字符串
redis.call('incryby','key2','1024')--数字字符串增加1024
return redis.call('get','key2')

命令式执行简单Lua脚本文件

以命令式执行Lua脚本文件,交互式只不能执行Lua脚本文件(不识别文件的)。

redis-cli -a Pass9612 2>/dev/null --eval ../../lua/test.lua
  • -a:如配置了密码,可用a选项,无密码可忽略
  • 2>/dev/null:输入密码时去除提示Warning: Using a password提示,方便取值查看,无密码可忽略
  • –eval 你的lua脚本文件路径

Lua脚本案例

案例会用到tonumber函数

tonumber函数会尝试将它的参数转换为数字

如果参数已经是一个数字或者是一个可以转换成数字的字符串,那么这个函数就会返回转换后的数值,否则,返回nil(表示转换失败)。

这个函数有一个额外的参数base可用来指定参数的进制:

(1)默认参数值是10

(2)参数的取值范围是[2, 36]

(3)当参数值超过10时,使用A代表10(大小写都可以),B代表11,以此类推最后Z代表35

带参数的:Lua脚本实现IP限流

每个用户在X秒内只能访问Y次
需要用到三个参数

  • KEYS:key,IP限流,则key需要包含IP去区分
  • ARGV[1]:第一个参数,X,用来设置过期时间
  • ARGV[2]:第二个参数,Y,控制规定时间内访问次数

新建ip.lua文件,内容如下

--[[
1.Lua 中的变量全是全局变量,无论语句块或是函数里,除非用 local 显式声明为局部变量,变量默认值均为nil
2.使用local创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。
(代码块:指的是一个控制结构内,一个函数体,或者一个chunk(变量被声明的那个文件或者文本串))尽可能使用局部变量,有两个好处
a.避免命名冲突
b.访问速度更快(原因是local变量是存放在lua的堆栈里面的是array操作,而全局变量是存放在_G中的table中,效率不及堆栈)
--]]
local num=redis.call('incr',KEYS[1])-- 计数+1,key不存在会新建
if tonumber(num)==1 then--第一次访问,用第一个参数设置过期时间redis.call('expire',KEYS[1],ARGV[1])return 1
elseif tonumber(num)>tonumber(ARGV[2]) then--不是第一次访问,跟第二个参数进行比较,是否超过限制return 0 --超过限制
elsereturn 1 --没有超过限制
end

命令方式执行Lua脚本ip.lua,10s中访问最多6次,appname_test1:ip:192.168.0.1是模拟的key

# 空格的写法需要注意(这是固定语法): key后面+空格+逗号+空格+参数1+空格+参数2
# 我redis有密码,用-a和2>/dev/null屏蔽密码提示
redis-cli -a Pass9612 2>/dev/null --eval ../../lua/ip.lua appname_test1:ip:192.168.0.1 , 10 6

不带参数的:Lua脚本实现自乘

Redis中有自增,但没有自乘。
传入key、arg1。对key乘以arg1,得到乘以的结果。

local curVal = redis.call('get',KEYS[1])
if curVal == false then--根据key获取当前value,没有key则为0curVal = 0
elsecurVal = tonumber(curVal)
end
curVal = curVal*tonumber(ARGV[1])
redis.call('set',KEYS[1],curVal)
return curVal

需要key有值,否则结果都是0

Redis缓存Lua脚本

eval "local curVal = redis.call('get',KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal*tonumber(ARGV[1]) ;redis.call('set',KEYS[1],curVal); return curVal" 1 num 2

以客户端执行乘法脚本为例,在redis客户端执行Lua语言,拼接了很多命令,如果这样的一长执行串命令发给服务端,那么网络通信就会产生比较大的开销,所以Redis支持在服务端直接缓存一部分脚本的内容(它是用脚本生成了一段摘要,服务端可以根据这个摘要去执行脚本内容)。

你可以把Lua脚本里的内容提取出来,转成一行,使用script load进行脚本内容的缓存,注意执行命令时单双引号,脚本中的’get’和命令load "local…"的单双引号不要重复,否则执行会报Invalid argument(s)

script load "local curVal = redis.call('get',KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal*tonumber(ARGV[1]) ;redis.call('set',KEYS[1],curVal); return curVal"
"5d134cb368cf22d88c9e0130cbae1775a22cf348"


需要key有值,否则结果都是0

Redis Lua原子性验证

Lua是保证原子性的,它的执行是排他的,Lua中的命令是一个整体,没执行完毕其它命令都将阻塞。

如果Lua脚本执行很慢,那么其它客户端执行命令都会陷入阻塞中,等待Lua脚本内容执行完毕。

客户端执行一个死循环Lua,不做任何值的修改

eval "while (true) do end" 0


其他客户端再执行命令,提示Redis正在忙于执行脚本,你只能通过SCRIPT KILL或者SHUTDOWN NOSAVE让这个脚本不再执行。

# 提示Redis正在忙于执行脚本,你只能通过SCRIPT KILL或者SHUTDOWN NOSAVE让这个脚本不再执行
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE

由于没有任何值的修改,因此可以直接SCRIPT KILL

客户端执行一个死循环Lua,涉及操作key

eval "redis.call('set','key3','333') while(true) do end" 0


可以看到SCRIPT KILL已经不可以了,因为你有对key进行操作

# 对不起,脚本已经对数据集执行了写命令。您可以等待脚本终止,或者使用SHUTDOWN NOSAVE命令强行终止服务器
UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.

SHUTDOWN NOSAVE是不保存当前的操作并强制Redis停机(正常停机是SHUTDOWN ),这种操作是由破坏性的,会导致一部分内容丢失。

SHUTDOWN NOSAVE之后,重新启动Redis,查看Lua中操作的key,并没有被保存

Redis Lua脚本好处、Redis执行Lua的两种方式、Redis缓存Lua脚本、Redis Lua原子性验证、Lua脚本IP限流、Lua脚本自乘相关推荐

  1. python3解释器执行not 1 and 1_编程语言的分类,python解释器多版本共存.执行python的两种方式,变量,用户与程序交互...

    一.编程语言的分类? 机器语言:直接使用二进制指令编程,直接操作硬件,必须考虑硬件细节. 汇编语言:用简写的英文标识符取代二进制去编写程序,直接操作硬件,必须考虑硬件细节. 高级语言:通过人类能够理解 ...

  2. python脚本在linux上运行的两种方式_python脚本当作Linux中的服务启动实现方法

    脚本服务化目的: python 在 文本处理中有着广泛的应用,为了满足文本数据的获取,会每天运行一些爬虫抓取数据.但是网上买的服务器会不定时进行维护,服务器会被重启.这样我们的爬虫服务就无法运行.这个 ...

  3. Python 执行代码的两种方式

    1.交互执行即黑屏命令行执行 优点:即时调时程序,调试方便 缺点:无法永久无法保存代码 2.即文件存储代码执行Python代码文件 优点:可以永久保存代码 缺点:调试不方便 转载于:https://w ...

  4. linux执行jar的两种方式

    在打包时一种指定了主类是哪个,一种没有指定,详见配置. 1.指定了的话直接使用下面命令执行: java -jar xxx.jar 参数 2.如果没有指定,则需要运行时手动指定: java -cp xx ...

  5. php操作redis_PHP操作redis的两种方式

    随着redis使用越来越广泛,各种应用系统几乎都会嵌入redis.当然,PHP也不例外.在我接触到的项目中,主要是使用redis作为缓存服务器.但是对于PHP来说,它本身并不支持redis.所以说这里 ...

  6. Unity编辑器开发——通过模板创建Lua脚本的两种方式(二)

    个人学习笔记,如有错误.疑问.建议,欢迎留言. 本文有关代码转载自:Unity3D 扩展编辑器实现创建Lua脚本 - 知乎 (zhihu.com) 声明:本文转载已取得原文章作者同意,有兴趣的可以关注 ...

  7. redis数据持久化到mysql_redis 数据持久化的几种方式

    1.前言 Redis的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上(这称为"半持久化模式"):也可以把每一次数据变化都写入到一个append only file( ...

  8. Redis持久化的两种方式

    文章目录 前言 一.Redis持久化机制 二.RDB 三.AOF 总结 前言 Redis是基于内存的缓存机制,假定Redis服务器中途突然出现故障,那内存的数据就会丢失.针对这个问题,Redis提供了 ...

  9. Linux 开机自动执行脚本的两种方式

    前言 很多情况下,我们都希望服务重启之后,很多应用都能自动启动,那么除了linux 提供的自启动配置之外,我们也可以在开机之后,通过指定 一些脚本的具体路径,或者是某个服务的启动命令具体路径,来进行服 ...

  10. sh执行文件 参数传递_详解shell中脚本参数传递的两种方式

    方式一:$0,$1,$2.. 采用$0,$1,$2..等方式获取脚本命令行传入的参数,值得注意的是,$0获取到的是脚本路径以及脚本名,后面按顺序获取参数,当参数超过10个时(包括10个),需要使用${ ...

最新文章

  1. Python自学路线图之Python进阶
  2. 百度推出飓风算法,严厉打击恶劣采集
  3. python3 装饰器_python3装饰器
  4. linux可以生成pdb调试信息吗,Linux通过使用pdb简单调试python计划
  5. oracle db-link 分布式数据库网络配置协议错误,Oracle学习(18)【DBA向】:分布式数据库...
  6. android高德自定义图标,Android 高德地图显示在线图标
  7. c++ builder 中的 XMLDocument 类详解(2) - 记要
  8. 997西方行政学说 (2)
  9. 很多文章是在下转载贴在此处,是为了自己以后遇到类似问题一时想不起来
  10. python:linux中升级python版本
  11. Struts2中<s:iterator>基本用法及示例
  12. php.ini开启命名空间,Zend Framework教程之模型Model基本规则和使用方法
  13. tplink 2.4g弱信号剔除_科普 l 路由器信号2.4G和5G区别
  14. 浅谈css样式(border、background、table)
  15. Android 蓝牙开发(五)OPP接收文件
  16. 22年国内最牛的Java面试八股文合集(全彩版),不接受反驳
  17. 微型计算机控制技术王艳芳,基于单片机液位控制器的设计与实现最终版(样例3)...
  18. NLP在医学领域的应用(更新中)
  19. 19108期计算机开机号,排列三19108期藏机图诗汇总
  20. DM笔记之安装1:DM7 For NeoKylin A6

热门文章

  1. IDA安装lazyIDA
  2. 解密Cocos2D中的Lua源码
  3. 三角网导线平差实例_三角网间接平差示例
  4. MathType6.0安装教程
  5. 腾讯微博开放平台OAuth1.0授权完整流程(C#)
  6. 对于高级搜索部分的要求
  7. 数学建模写作指导20篇(一)-如何写好数学建模论文?
  8. Cisco(54)——STP理论(2)
  9. 必须安装三星系列android系统智能手机usb驱动程序,三星N9109W Android 5.0 (GALAXY Note 4 电信4G)usb驱动下载安装教程...
  10. IIS写入漏洞利用工具解析