第3章 小功能大用处-事务与Lua

为了保证多条命令组合的原子性,Redis提供了简单的事务功能以及集成Lua脚本来解决这个问题。
首先简单介绍Redis中事务的使用方法以及它的局限性,之后重点介绍Lua语言的基本使用方法,以及如何将Redis和Lua脚本进行集成,最后给出Redis管理Lua脚本的相关命令
1.事务
事务表示一组动作,要么全部执行,要么全部不执行。例如在社交网站上用户A关注了用户B,那么需要在用户A的关注表中加入用户B,并且在用户B的粉丝表中添加用户A,这两个行为要么全部执行,要么全部不执行,否则会出现数据不一致的情况。
Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi和exec两个命令之间。multi命令代表事务开始,exec命令代表事务结束,它们之间的命令是原子顺序执行的,例如下面操作实现了上述用户关注问题。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> sadd user:b:fans user:a
QUEUED

可以看到sadd命令此时的返回结果是QUEUED,代表命令并没有真正执行,而是暂时保存在Redis中。如果此时另一个客户端执行sismember user:a:follow user:b返回结果应该为0。

127.0.0.1:6379> sismember user:a:follow user:b
(integer) 0

只有当exec执行后,用户A关注用户B的行为才算完成,如下所示返回的两个结果对应sadd命令。

127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 1

如果要停止事务的执行,可以使用discard命令代替exec命令即可。

127.0.0.1:6379> discard
OK
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 0

如果事务中的命令出现错误,Redis的处理机制也不尽相同

  • 命令错误
    例如下面操作错将set写成了sett,属于语法错误,会造成整个事务无法执行,key和counter的值未发生变化:
127.0.0.1:6388> mget key counter
1) "hello"
2) "100"
127.0.0.1:6388> multi
OK
127.0.0.1:6388> sett key world
(error) ERR unknown command 'sett'
127.0.0.1:6388> incr counter
QUEUED
127.0.0.1:6388> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6388> mget key counter
1) "hello"
2) "100"
  • 运行时错误

例如用户B在添加粉丝列表时,误把sadd命令写成了zadd命令,这种就是运行时命令,因为语法是正确的:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
201
127.0.0.1:6379> zadd user:b:fans 1 user:a
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 1

可以看到Redis并不支持回滚功能,sadd user:a:follow user:b命令已经执行成功,开发人员需要自己修复这类问题。
有些应用场景需要在事务之前,确保事务中的key没有被其他客户端修改过,才执行事务,否则不执行(类似乐观锁)。Redis提供了watch命令来解决这类问题,表3-2展示了两个客户端执行命令的时序
在这里插入图片描述
可以看到“客户端-1”在执行multi之前执行了watch命令,“客户端-2”在“客户端-1”执行exec之前修改了key值,造成事务没有执行(exec结果为nil),整个代码如下所示:

#T1:客户端1
127.0.0.1:6379> set key "java"
OK
#T2:客户端1
127.0.0.1:6379> watch key
OK
#T3:客户端1
127.0.0.1:6379> multi
OK
#T4:客户端2
127.0.0.1:6379> append key python
(integer) 11
#T5:客户端1
127.0.0.1:6379> append key jedis
QUEUED
#T6:客户端1
127.0.0.1:6379> exec
(nil)
#T7:客户端1
127.0.0.1:6379> get key
"javapython"

Redis提供了简单的事务,之所以说它简单,主要是因为它不支持事务中的回滚特性,同时无法实现命令之间的逻辑关系计算,当然也体现了Redis的“keep it simple”的特性,下一小节介绍的Lua脚本同样可以实现事务的相关功能,但是功能要强大很多。
2.Lua用法简述
Lua语言是由C语言实现的,虽然简单小巧但是功能强大,所以许多应用都选用它作为脚本语言,尤其是在游戏领域,Web服务器Nginx将Lua语言作为扩展,增强自身功能。Redis将Lua作为脚本语言可帮助开发者定制自己的Redis命令。
2.1.数据类型及其逻辑处理
Lua语言提供了如下几种数据类型:booleans(布尔)、numbers(数值)、strings(字符串)、tables(表格),和许多高级语言相比,相对简单。下面将结合例子对Lua的基本数据类型和逻辑处理进行说明。
(1)字符串
下面定义一个字符串类型的数据:

local strings val = "world"

其中,local代表val是一个局部变量,如果没有local代表是全局变量。print函数可以打印出变量的值,例如下面代码将打印world,其中"–"是Lua语言的注释。

-- 结果是"world"
print(hello)

(2)数组
在Lua中,如果要使用类似数组的功能,可以用tables类型,下面代码使用定义了一个tables类型的变量myArray,但和大多数编程语言不同的是,Lua的数组下标从1开始计算:

local tables myArray = {"redis", "jedis", true, 88.0}
--true
print(myArray[3])

如果想遍历这个数组,可以使用for和while,这些关键字和许多编程语言是一致的
(a)for
下面代码会计算1到100的和,关键字for以end作为结束符:

local int sum = 0
for i = 1, 100
dosum = sum + i
end
-- 输出结果为5050
print(sum)

要遍历myArray,首先需要知道tables的长度,只需要在变量前加一个#号即可:

for i = 1, #myArray
doprint(myArray[i])
end

除此之外,Lua还提供了内置函数ipairs,使用for index,value ipairs(tables)可以遍历出所有的索引下标和值:

for index,value in ipairs(myArray)
doprint(index)print(value)
end

(b)while
下面代码同样会计算1到100的和,只不过使用的是while循环,while循环同样以end作为结束符。

local int sum = 0
local int i = 0
while i <= 100
dosum = sum +ii = i + 1
end
--输出结果为5050
print(sum)

(c)if else
要确定数组中是否包含了jedis,有则打印true,注意if以end结尾,if后紧跟then:

local tables myArray = {"redis", "jedis", true, 88.0}
for i = 1, #myArray
doif myArray[i] == "jedis"thenprint("true")breakelse--do nothingend
end

(3)哈希
如果要使用类似哈希的功能,同样可以使用tables类型,例如下面代码定义了一个tables,每个元素包含了key和value,其中strings1…string2是将两个字符串进行连接:

local tables user_1 = {age = 28, name = "tome"}
--user_1 age is 28
print("user_1 age is " .. user_1["age"])

如果要遍历user_1,可以使用Lua的内置函数pairs:

for key,value in pairs(user_1)
do print(key .. value)
end

函数定义:在Lua中,函数以function开头,以end结尾,funcName是函数名,中间部分是函数体:

function funcName()
...
end
contact函数将两个字符串拼接:
function contact(str1, str2)return str1 .. str2
end
--"hello world"
print(contact("hello ", "world"))

3 Redis与Lua
3.1.在Redis中使用Lua
在Redis中执行Lua脚本有两种方法:eval和evalsha。
(1)eval

eval 脚本内容 key个数 key列表 参数列表

下面例子使用了key列表和参数列表来为Lua脚本提供更多的灵活性:

127.0.0.1:6379> eval 'return "hello " .. KEYS[1] .. ARGV[1]' 1 redis world "hello redisworld"

此时KEYS[1]=“redis”,ARGV[1]=“world”,所以最终的返回结果是"hello redisworld"。
如果Lua脚本较长,还可以使用redis-cli–eval直接执行文件。例如

 //03.lua脚本代码如下:
local name=redis.call("get",KEYS[1])
local age=redis.call("get",KEYS[2])
if name=="LLL" thenredis.call("set",KEYS[1],ARGV[1])redis.call("incr",KEYS[2])
end

把03.lua文件传到服务器/root/application/program/luascript/03.lua目录下

192.168.127.128:6379>get name
"LLL"
192.168.127.128:6379>get age
"15"
//执行改脚本的命令,必须在Linux的命令行,不是在Redis的命令行[root@linux ~]# /root/application/program/redis-tool/redis-cli -h 192.168.127.128 -p 6379 --eval /root/application/program/luascript/03.lua name age , patrickLiu
192.168.127.128:6379>get name"patrickLiu"
192.168.127.128:6379>get age"16"
//说明带参数的执行Lua脚本成功

eval命令和–eval参数本质是一样的,客户端如果想执行Lua脚本,首先在客户端编写好Lua脚本代码,然后把脚本作为字符串发送给服务端,服务端会将执行结果返回给客户端,整个过程如下。
在这里插入图片描述
(2)evalsha
除了使用eval,Redis还提供了evalsha命令来执行Lua脚本。如图3-8所示,首先要将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验和,evalsha命令使用SHA1作为参数可以直接执行对应Lua脚本,避免每次发送Lua脚本的开销。这样客户端就不需要每次执行脚本内容,而脚本也会常驻在服务端,脚本功能得到了复用。
在这里插入图片描述
加载脚本:script load命令可以将脚本内容加载到Redis内存中,例如下面将04.lua加载到Redis中,得到SHA1为:7c1ebd792518eeefe306d1a755ebbc4d6d585963

//04.lua文件
local b1=redis.call("hgetall",KEYS[1])
return b1
root@iZ2zec3etasicvrmg7svj0Z:/usr/ztt# ls
03.lua  dump.rdb      jdk-8u401-linux-x64.tar.gz  redis-6.2.7.tar.gz
04.lua  jdk1.8.0_401  redis-6.2.7                 rocketmq-all-4.9.6-bin-release.zip
root@iZ2zec3etasicvrmg7svj0Z:/usr/ztt# /usr/local/bin/redis-cli -a 1.tiantian script load "$(cat 04.lua)"
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
"7c1ebd792518eeefe306d1a755ebbc4d6d585963"

执行脚本:evalsha的使用方法如下,参数使用SHA1值,执行逻辑和eval一致。

evalsha 脚本SHA1值 key个数 key列表 参数列表

所以只需要执行如下操作,就可以调用04.lua脚本:

127.0.0.1:6379> hgetall myhash
1) "name"
2) "zhangsan"
3) "sex"
4) "nan"
5) "address"
6) "hebeibaoding"
7) "school"
8) "laiyuanyizhong"
127.0.0.1:6379> evalsha 7c1ebd792518eeefe306d1a755ebbc4d6d585963 1 myhash
1) "name"
2) "zhangsan"
3) "sex"
4) "nan"
5) "address"
6) "hebeibaoding"
7) "school"
8) "laiyuanyizhong" 

3.2.Lua的Redis API
Lua可以使用redis.call函数实现对Redis的访问,例如下面代码是Lua使用redis.call调用了Redis的set和get操作:

redis.call("set", "hello", "world")
redis.call("get", "hello")

放在Redis的执行效果如下:

127.0.0.1:6379> eval 'return redis.call("get", KEYS[1])' 1 hello
"world"

除此之外Lua还可以使用redis.pcall函数实现对Redis的调用,redis.call和redis.pcall的不同在于,如果redis.call执行失败,那么脚本执行结束会直接返回错误,而redis.pcall会忽略错误继续执行脚本,所以在实际开发中要根据具体的应用场景进行函数的选择。
开发提示:Lua可以使用redis.log函数将Lua脚本的日志输出到Redis的日志文件中,但是一定要控制日志级别。
Redis3.2提供了Lua Script Debugger功能用来调试复杂的Lua脚本,具体可以参考:http://redis.io/topics/ldb。
4.案例
Lua脚本功能为Redis开发和运维人员带来如下三个好处:

  • Lua脚本在Redis中是原子执行的,执行过程中间不会插入其他命令。
  • Lua脚本可以帮助开发和运维人员创造出自己定制的命令,并可以将这些命令常驻在Redis内存中,实现复用的效果。
  • Lua脚本可以将多条命令一次性打包,有效地减少网络开销。
  • 下面以一个例子说明Lua脚本的使用,当前列表记录着热门用户的id,
    假设这个列表有5个元素,如下所示:
127.0.0.1:6379> lpush hot:user:list user:72:ratio user:99:ratio user:3:ratio user:8:ratio user:1:ratio
(integer) 5
127.0.0.1:6379> lrange hot:user:list 0 -1
1) "user:1:ratio"
2) "user:8:ratio"
3) "user:3:ratio"
4) "user:99:ratio"
5) "user:72:ratio"

user:{id}:ratio代表用户的热度,它本身又是一个字符串类型的键:

127.0.0.1:6379> mset  user:1:ratio 986  user:8:ratio 762  user:3:ratio 556  user:99:ratio 400 user:72:ratio 101
OK
127.0.0.1:6379> mget  user:1:ratio user:8:ratio user:3:ratio user:99:ratio user:72:ratio
1) "986"
2) "762"
3) "556"
4) "400"
5) "101"

现要求将列表内所有的键对应热度做加1操作,并且保证是原子执行,此功能可以利用Lua脚本来实现。
1)将列表中所有元素取出,赋值给mylist:

local mylist = redis.call("lrange", KEYS[1], 0, -1)

2)定义局部变量count=0,这个count就是最后incr的总次数:

local count = 0

3)遍历mylist中所有元素,每次做完count自增,最后返回count:

for index,key in ipairs(mylist)
doredis.call("incr",key)count = count + 1
end
return count

将上述脚本写入lrange_and_mincr.lua文件中,并执行如下操作,返回结果为5。

redis-cli --eval lrange_and_mincr.lua hot:user:list
(integer) 5

执行后所有用户的热度自增1:

127.0.0.1:6379> mget user:1:ratio user:8:ratio user:3:ratio user:99:ratio user:72:ratio
1) "987"
2) "763"
3) "557"
4) "401"
5) "102"

lrange_and_mincr.lua文件的汇总内容为如下:

local mylist = redis.call("lrange", KEYS[1], 0, -1)
local count = 0
for index,key in ipairs(mylist)
doredis.call("incr",key)count = count + 1
end
return count

lrange_and_mincr.lua文件上传到/usr/ztt目录下

root@iZ2zec3etasicvrmg7svj0Z:/usr/ztt# rzroot@iZ2zec3etasicvrmg7svj0Z:/usr/ztt# /usr/local/bin/redis-cli -a 1.tiantian --eval lrange_and_mincr.lua hot:user:list
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
(integer) 5

执行后所有用户的热度自增1:

127.0.0.1:6379> mget user:1:ratio user:8:ratio user:3:ratio user:99:ratio
user:72:ratio
1) "987"
2) "763"
3) "557"
4) "401"
5) "102"

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

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

相关文章

项目实训-vue(十三)

项目实训-vue&#xff08;十三&#xff09; 文章目录 项目实训-vue&#xff08;十三&#xff09;1.概述2.处理按钮 1.概述 本篇博客将记录我在图片上传页面中的工作。 2.处理按钮 实现了图片的上传之后&#xff0c;还需要设置具体的上传按钮。 这段代码使用 Element UI 的 …

Spring学习02-[Spring容器核心技术IOC学习]

Spring容器核心技术IOC学习 什么是bean?如何配置bean?Component方式bean配合配置类的方式import导入方式 什么是bean? 被Spring管理的对象就是bean,和普通对象的区别就是里面bean对象里面的属性也被注入了。 如何配置bean? Component方式、bean配合配置类的方式、import…

C语言 | Leetcode C语言题解之第190题颠倒二进制位

题目&#xff1a; 题解&#xff1a; const uint32_t M1 0x55555555; // 01010101010101010101010101010101 const uint32_t M2 0x33333333; // 00110011001100110011001100110011 const uint32_t M4 0x0f0f0f0f; // 00001111000011110000111100001111 const uint32_t M8…

【containerd】Containerd高阶命令行工具nerdctl

前言 对于习惯了使用docker cli的用户来说&#xff0c;containerd的命令行工具ctr使用起来不是很顺手&#xff0c;此时别慌&#xff0c;还有另外一个命令行工具项目nerdctl可供我们选择。 nerdctl是一个与docker cli风格兼容的containerd的cli工具。 nerdctl已经作为子项目加入…

秋招突击——6/24——复习{完全背包问题——买书,状态转换机——股票买卖V}——新作{两数相除,LRU缓存实现}

文章目录 引言复习完全背包问题——买书个人实现 状态转换机——股票买卖V个人实现参考实现 新作两数相除个人实现 新作LRU缓存实现个人实现unordered_map相关priority_queue相关 参考实现自己复现 总结 引言 今天知道拼多多挂掉了&#xff0c;难受&#xff0c;那实习就是颗粒无…

汪汪队短视频:成都柏煜文化传媒有限公司

汪汪队短视频&#xff1a;萌宠与冒险的交织乐章 在数字时代的浪潮中&#xff0c;短视频以其短小精悍、内容丰富的特点&#xff0c;迅速占领了人们的闲暇时光。而在这些琳琅满目的短视频中&#xff0c;有一类作品以其独特的魅力吸引了无数观众的目光&#xff0c;那就是以萌宠为…

单门户上集成多种数据库查询入口

&#xff08;作者&#xff1a;陈玓玏&#xff09; 开源项目&#xff0c;欢迎star哦&#xff0c;https://github.com/tencentmusic/cube-studio 在一家公司&#xff0c;我们通常会有多种数据库&#xff0c;每种数据库因为其特性承担不同的角色&#xff0c;比如mysql这种轻量…

AI-024人工智能指数报告(三):经济

概述 人工智能融入经济会引发许多很迷人的问题。有人预测人工智能会推动生产力得到改进&#xff0c;但其影响程度仍未确定。其中一个主要关切是大规模劳动替代的可能性——工作究竟会在多大程度上被自动化还是人工智能主要起到增强作用&#xff1f;各个行业的企业已经在用各种…

基于FPGA的温湿度检测

初始化部分就不过多赘述&#xff0c;我会给出对应的文件&#xff0c;我只说明这部分里面涉及到使用的代码部分 1、数据的读取和校验 数据的读取和检验代码如下 always (posedge clk_us)if (data_temp[7:0] data_temp[39:32] data_temp[31:24] data_temp[23:16] data_te…

SpringBoot+Vue集成富文本编辑器

1.引入 我们常常在各种网页软件中编写文档的时候&#xff0c;常常会有富文本编辑器&#xff0c;就比如csdn写博客的这个页面&#xff0c;包含了富文本编辑器&#xff0c;那么怎么实现呢&#xff1f;下面来详细的介绍&#xff01; 2.安装wangeditor插件 在Vue工程中&#xff0c;…

基于 SpringBoot + Vue 的图书购物商城项目

本项目是一个基于 SpringBoot 和 Vue 的图书购物商城系统。系统主要实现了用户注册、登录&#xff0c;图书浏览、查询、加购&#xff0c;购物车管理&#xff0c;订单结算&#xff0c;会员折扣&#xff0c;下单&#xff0c;个人订单管理&#xff0c;书籍及分类管理&#xff0c;用…

PCL 使用列文伯格-马夸尔特法计算变换矩阵

目录 一、算法原理1、计算过程2、主要函数3、参考文献二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法原理 1、计算过程 2、主要函数 void pcl

EXCEL快速填充空白内容

** EXCEL快速填充空白内容 ** 1.全选所有需要填充的内容&#xff0c;按住电脑的F5或者CTRLG点击定位 2.可以看到空白处被自动选定&#xff0c;之后按电脑和⬆&#xff0c;最后CTRLenter 可以看到空白处已经被填充。

计网入门还没到放弃

TCP报文段格式 源端口&#xff1a;标识报文的返回地址 目的端口&#xff1a;指明计算机上的应用程序接口 序号&#xff1a;通过SYN包传给接收端主机&#xff0c;每传送一次就1&#xff0c;用来解决网络包乱序的问题。 确认号&#xff1a;期望下一次收到的数据的序列号&#xff…

酷开科技用酷开系统打造数字化家庭娱乐与教育的新天地

家庭教育的重要性不言而喻&#xff0c;俗话说&#xff1a;父母是孩子的第一任老师&#xff0c;也是终生的老师。一个人是否能够成长成才&#xff0c;家庭教育是基础和前提&#xff0c;学校教育能够起到促进和引导作用&#xff0c;社会教育也会对一个人的成长产生重要影响。在数…

【YOLOv5/v7改进系列】引入PConv——轻量快速的卷积块

一、导言 《奔跑吧&#xff0c;别走&#xff1a;追求更高的FLOPS以实现更快的神经网络》&#xff0c;主要探讨了如何设计快速且高效的神经网络模型。文章指出&#xff0c;尽管许多研究致力于减少浮点运算次数(FLOPs)来提升模型速度&#xff0c;但FLOPs的减少并不直接等同于延迟…

Zookeeper部署

Zookeeper部署 下载安装包Linux解压安装包修改配置文件编辑zoo.cf配置 启动服务停止服务常用zookeeper指令查看namespace列表创建namespace删除namespace 注意&#xff1a;该文章为简单部署操作&#xff0c;没有复杂的配置内容&#xff0c;用的是3.7.2版本。 下载安装包 进入z…

python爬虫需要什么HTTP代理?

用来爬虫的话&#xff0c;还是建议用高匿名代理&#xff0c;但显然题主用了高匿名代理还是出现了一部分问题&#xff0c;我们可以先找到问题关键再解决它&#xff0c;一般爬虫用了高匿名代理出现被封会有以下几种原因&#xff1a; 1.代理IP的质量不过关 一般来说每个网站都有…

秋招突击——6/20——复习{(单调队列优化)——最大子序列和,背包问题——宠物小精灵收服问题}——新作{两两交换链表中的节点}

文章目录 引言复习单调队列优化——最大子序列和思路分析实现代码参考实现 背包问题——宠物小精灵的收服问题个人实现参考实现 新作两两交换链表中的节点个人实现参考实现 删除有序数组中的重复项个人实现知识补全迭代器的访问和控制vector删除特定的元素erasevector底层删除元…

PHP 数组排序详解与实例

在PHP编程中&#xff0c;数组是一种非常常见和重要的数据结构&#xff0c;而对数组进行排序则是处理和展示数据时必不可少的操作之一。本文将详细介绍PHP中数组排序的各种方法、函数和示例&#xff0c;帮助您掌握如何根据不同需求对数组进行排序。 1. PHP 中的数组排序函数 PH…